diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:56 +0000 |
commit | d964cec5e6aa807b75c7a4e7cdc5f11e54b2eda2 (patch) | |
tree | 794bc3738a00b5e599f06d1f2f6d79048d87ff8e /test | |
parent | Initial commit. (diff) | |
download | ansible-lint-upstream.tar.xz ansible-lint-upstream.zip |
Adding upstream version 6.13.1.upstream/6.13.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
347 files changed, 24568 insertions, 0 deletions
diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..2cd01f0 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +"""Use ansiblelint.testing instead for reusable tests.""" diff --git a/test/bar.txt b/test/bar.txt new file mode 100644 index 0000000..e22f90b --- /dev/null +++ b/test/bar.txt @@ -0,0 +1 @@ +Bar file diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..f31967d --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,106 @@ +"""PyTest fixtures for testing the project.""" +from __future__ import annotations + +import os +import shutil +import subprocess +from contextlib import contextmanager +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +# pylint: disable=wildcard-import,unused-wildcard-import +from ansiblelint.testing.fixtures import * # noqa: F403 +from ansiblelint.yaml_utils import FormattedYAML + +if TYPE_CHECKING: + from typing import List # pylint: disable=ungrouped-imports + + from _pytest import nodes + from _pytest.config import Config + from _pytest.config.argparsing import Parser + + +@contextmanager +def cwd(path: str) -> Iterator[None]: + """Context manager for chdir.""" + old_pwd = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(old_pwd) + + +def pytest_addoption(parser: Parser) -> None: + """Add --regenerate-formatting-fixtures option to pytest.""" + parser.addoption( + "--regenerate-formatting-fixtures", + action="store_true", + default=False, + help="Regenerate formatting fixtures with prettier and internal formatter", + ) + + +def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: + """Skip tests based on --regenerate-formatting-fixtures option.""" + do_regenerate = config.getoption("--regenerate-formatting-fixtures") + skip_other = pytest.mark.skip( + reason="not a formatting_fixture test and " + "--regenerate-formatting-fixtures was specified" + ) + skip_formatting_fixture = pytest.mark.skip( + reason="specify --regenerate-formatting-fixtures to " + "only run formatting_fixtures test" + ) + for item in items: + if do_regenerate and "formatting_fixtures" not in item.keywords: + item.add_marker(skip_other) + elif not do_regenerate and "formatting_fixtures" in item.keywords: + item.add_marker(skip_formatting_fixture) + + +def pytest_configure(config: Config) -> None: + """Register custom markers.""" + if config.getoption("--regenerate-formatting-fixtures"): + regenerate_formatting_fixtures() + + +def regenerate_formatting_fixtures() -> None: + """Re-generate formatting fixtures with prettier and internal formatter. + + Pass ``--regenerate-formatting-fixtures`` to run this and skip all other tests. + This is a "test" because once fixtures are regenerated, + we run prettier again to make sure it does not change files formatted + with our internal formatting code. + """ + subprocess.check_call(["which", "prettier"]) + + yaml = FormattedYAML() + + fixtures_dir = Path("test/fixtures/") + fixtures_dir_before = fixtures_dir / "formatting-before" + fixtures_dir_prettier = fixtures_dir / "formatting-prettier" + fixtures_dir_after = fixtures_dir / "formatting-after" + + fixtures_dir_prettier.mkdir(exist_ok=True) + fixtures_dir_after.mkdir(exist_ok=True) + + # Copying before fixtures... + for fixture in fixtures_dir_before.glob("fmt-[0-9].yml"): + shutil.copy(str(fixture), str(fixtures_dir_prettier / fixture.name)) + shutil.copy(str(fixture), str(fixtures_dir_after / fixture.name)) + + # Writing fixtures with prettier... + subprocess.check_call(["prettier", "-w", str(fixtures_dir_prettier)]) + # NB: pre-commit end-of-file-fixer can also modify files. + + # Writing fixtures with ansiblelint.yaml_utils.FormattedYAML() + for fixture in fixtures_dir_after.glob("fmt-[0-9].yml"): + data = yaml.loads(fixture.read_text()) + output = yaml.dumps(data) + fixture.write_text(output) + + # Make sure prettier won't make changes in {fixtures_dir_after} + subprocess.check_call(["prettier", "-c", str(fixtures_dir_after)]) diff --git a/test/custom_rules/__init__.py b/test/custom_rules/__init__.py new file mode 100644 index 0000000..09a0f04 --- /dev/null +++ b/test/custom_rules/__init__.py @@ -0,0 +1 @@ +"""Dummy test module.""" diff --git a/test/custom_rules/example_com/__init__.py b/test/custom_rules/example_com/__init__.py new file mode 100644 index 0000000..a633c75 --- /dev/null +++ b/test/custom_rules/example_com/__init__.py @@ -0,0 +1 @@ +"""A dummy test module #2.""" diff --git a/test/custom_rules/example_com/example_com_rule.py b/test/custom_rules/example_com/example_com_rule.py new file mode 100644 index 0000000..abcb9dd --- /dev/null +++ b/test/custom_rules/example_com/example_com_rule.py @@ -0,0 +1,28 @@ +# Copyright (c) 2020, Ansible Project +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""A dummy custom rule module #2.""" + +from ansiblelint.rules import AnsibleLintRule + + +class ExampleComRule(AnsibleLintRule): + """A dummy custom rule class.""" + + id = "100002" diff --git a/test/custom_rules/example_inc/__init__.py b/test/custom_rules/example_inc/__init__.py new file mode 100644 index 0000000..09a0f04 --- /dev/null +++ b/test/custom_rules/example_inc/__init__.py @@ -0,0 +1 @@ +"""Dummy test module.""" diff --git a/test/custom_rules/example_inc/custom_rule.py b/test/custom_rules/example_inc/custom_rule.py new file mode 100644 index 0000000..15c389f --- /dev/null +++ b/test/custom_rules/example_inc/custom_rule.py @@ -0,0 +1,28 @@ +# Copyright (c) 2020, Ansible Project +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Dummy custom rule module.""" + +from ansiblelint.rules import AnsibleLintRule + + +class CustomRule(AnsibleLintRule): + """Dummy custom rule class.""" + + id = "100001" diff --git a/test/fixtures/__init__.py b/test/fixtures/__init__.py new file mode 100644 index 0000000..6fc2115 --- /dev/null +++ b/test/fixtures/__init__.py @@ -0,0 +1 @@ +"""Fixtures used in tests.""" diff --git a/test/fixtures/ansible-config-invalid.yml b/test/fixtures/ansible-config-invalid.yml new file mode 100644 index 0000000..9eb8fe7 --- /dev/null +++ b/test/fixtures/ansible-config-invalid.yml @@ -0,0 +1,4 @@ +--- +# invalid .ansible-lint config file +- foo +- bar diff --git a/test/fixtures/ansible-config.yml b/test/fixtures/ansible-config.yml new file mode 100644 index 0000000..673d6f1 --- /dev/null +++ b/test/fixtures/ansible-config.yml @@ -0,0 +1,3 @@ +--- +verbosity: 1 +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/config-with-extra-vars.yml b/test/fixtures/config-with-extra-vars.yml new file mode 100644 index 0000000..5d06b6a --- /dev/null +++ b/test/fixtures/config-with-extra-vars.yml @@ -0,0 +1,4 @@ +--- +extra_vars: + foo: bar + knights_favorite_word: NI diff --git a/test/fixtures/config-with-relative-path.yml b/test/fixtures/config-with-relative-path.yml new file mode 100644 index 0000000..f396347 --- /dev/null +++ b/test/fixtures/config-with-relative-path.yml @@ -0,0 +1,4 @@ +--- +exclude_paths: + - ../../examples/roles/test-role/ +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/config-with-write-all.yml b/test/fixtures/config-with-write-all.yml new file mode 100644 index 0000000..a4242c5 --- /dev/null +++ b/test/fixtures/config-with-write-all.yml @@ -0,0 +1,3 @@ +--- +write_list: + - all diff --git a/test/fixtures/config-with-write-none.yml b/test/fixtures/config-with-write-none.yml new file mode 100644 index 0000000..5dacd38 --- /dev/null +++ b/test/fixtures/config-with-write-none.yml @@ -0,0 +1,3 @@ +--- +write_list: + - none diff --git a/test/fixtures/config-with-write-subset.yml b/test/fixtures/config-with-write-subset.yml new file mode 100644 index 0000000..f83149d --- /dev/null +++ b/test/fixtures/config-with-write-subset.yml @@ -0,0 +1,4 @@ +--- +write_list: + - rule-tag + - rule-id diff --git a/test/fixtures/exclude-paths-with-expands.yml b/test/fixtures/exclude-paths-with-expands.yml new file mode 100644 index 0000000..640563c --- /dev/null +++ b/test/fixtures/exclude-paths-with-expands.yml @@ -0,0 +1,5 @@ +--- +exclude_paths: + - ~/.ansible/roles + - $HOME/.ansible/roles +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/exclude-paths.yml b/test/fixtures/exclude-paths.yml new file mode 100644 index 0000000..6af079e --- /dev/null +++ b/test/fixtures/exclude-paths.yml @@ -0,0 +1,4 @@ +--- +exclude_paths: + - ../ +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/formatting-after/fmt-1.yml b/test/fixtures/formatting-after/fmt-1.yml new file mode 100644 index 0000000..118a087 --- /dev/null +++ b/test/fixtures/formatting-after/fmt-1.yml @@ -0,0 +1,47 @@ +--- +# ^ too many newlines before +foo: bar # This is a comment has extra spaces preceding it + +fruits: # unindented sequence: + - apple + - orange +vegetables: # indented sequence: + - onion + - carrot + +quoting: + - that should have double quotes + - that should remain in single quotes + - a string with " inside + # next line has some undesired trailing spaces: + - a string with ' inside + - can't be sure! + # next line should be converted to use double quotes: + - [foo, bar] + +inline-dictionary: + - { foo: bar } # should add some spacing between curly braces and content + - { foo2: bar2 } # should reduce spacing between curly braces and content + +# YAML 1.1 Boolean-hell: https://yaml.org/type/bool.html +booleans-true: + preferred: true # YAML 1.2 compatible! + answer-1.1: true + canonical-1.1: true + canonical-upper-1.1: true + logical-1.1: true + option-1.1: true +booleans-false: + preferred: false # YAML 1.2 compatible! + answer-1.1: false + canonical-1.1: false + canonical-upper-1.1: false + logical-1.1: false + option-1.1: false + +# ^ double newline should be removed +overly-indented-vault-value: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 123466303630313 + +# this file also has 3 newlines at end-of-file instead of one diff --git a/test/fixtures/formatting-after/fmt-2.yml b/test/fixtures/formatting-after/fmt-2.yml new file mode 100644 index 0000000..a162721 --- /dev/null +++ b/test/fixtures/formatting-after/fmt-2.yml @@ -0,0 +1,24 @@ +# preamble/header comment +--- +# initial comment +- foo: bar + +- baz: # over indented + - qwerty + - foobar + animals: # under indented + - crow + - pig + - giraffe + +- nothing: # null + +- octal: + - "0o123" # YAML 1.2 octal + - "0123" # YAML 1.1 octal + +- integer: + - 0 # Not an octal. See #2071 + - 10 + - 9999 + zero: 0 # Not an octal. See #2071 diff --git a/test/fixtures/formatting-after/fmt-3.yml b/test/fixtures/formatting-after/fmt-3.yml new file mode 100644 index 0000000..d8106f7 --- /dev/null +++ b/test/fixtures/formatting-after/fmt-3.yml @@ -0,0 +1,21 @@ +--- +dummy_map: # eol comment + # full line comment not indented + something: + # full line comment indented + # next full line comment indented + - or + # 1 full line comments over indented + # 2 full line comments over indented + - other + - | + # this is part of a string not a yaml comment + # also not a comment + +# comment before top-level +second_key: + - {} # should drop the extra space in flow map + # comment before non top-level + - {} + # comment before non top-level + - [] diff --git a/test/fixtures/formatting-before/fmt-1.yml b/test/fixtures/formatting-before/fmt-1.yml new file mode 100644 index 0000000..0678111 --- /dev/null +++ b/test/fixtures/formatting-before/fmt-1.yml @@ -0,0 +1,53 @@ +--- + + + +# ^ too many newlines before +foo: bar # This is a comment has extra spaces preceding it + +fruits: # unindented sequence: +- apple +- orange +vegetables: # indented sequence: + - onion + - carrot + +quoting: + - 'that should have double quotes' + - 'that should remain in single quotes' + - 'a string with " inside' + # next line has some undesired trailing spaces: + - "a string with ' inside" + - can't be sure! + # next line should be converted to use double quotes: + - ['foo', 'bar'] + +inline-dictionary: + - {foo: bar} # should add some spacing between curly braces and content + - { foo2: bar2 } # should reduce spacing between curly braces and content + +# YAML 1.1 Boolean-hell: https://yaml.org/type/bool.html +booleans-true: + preferred: true # YAML 1.2 compatible! + answer-1.1: YES + canonical-1.1: y + canonical-upper-1.1: Y + logical-1.1: True + option-1.1: on +booleans-false: + preferred: false # YAML 1.2 compatible! + answer-1.1: NO + canonical-1.1: n + canonical-upper-1.1: N + logical-1.1: False + option-1.1: off + + +# ^ double newline should be removed +overly-indented-vault-value: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 123466303630313 + +# this file also has 3 newlines at end-of-file instead of one + + diff --git a/test/fixtures/formatting-before/fmt-2.yml b/test/fixtures/formatting-before/fmt-2.yml new file mode 100644 index 0000000..2941663 --- /dev/null +++ b/test/fixtures/formatting-before/fmt-2.yml @@ -0,0 +1,24 @@ +# preamble/header comment +--- +# initial comment + - foo: bar + + - baz: # over indented + - qwerty + - foobar + animals: # under indented + - crow + - pig + - giraffe + + - nothing: null # null + + - octal: + - 0o123 # YAML 1.2 octal + - 0123 # YAML 1.1 octal + + - integer: + - 0 # Not an octal. See #2071 + - 10 + - 9999 + zero: 0 # Not an octal. See #2071 diff --git a/test/fixtures/formatting-before/fmt-3.yml b/test/fixtures/formatting-before/fmt-3.yml new file mode 100644 index 0000000..c862cc4 --- /dev/null +++ b/test/fixtures/formatting-before/fmt-3.yml @@ -0,0 +1,21 @@ +--- +dummy_map: # eol comment +# full line comment not indented + something: + # full line comment indented + # next full line comment indented + - or + # 1 full line comments over indented + # 2 full line comments over indented + - other + - | + # this is part of a string not a yaml comment + # also not a comment + +# comment before top-level +second_key: + - { } # should drop the extra space in flow map +# comment before non top-level + - {} +# comment before non top-level + - [] diff --git a/test/fixtures/formatting-prettier/fmt-1.yml b/test/fixtures/formatting-prettier/fmt-1.yml new file mode 100644 index 0000000..d74c826 --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-1.yml @@ -0,0 +1,48 @@ +--- +# ^ too many newlines before +foo: bar # This is a comment has extra spaces preceding it + +fruits: # unindented sequence: + - apple + - orange +vegetables: # indented sequence: + - onion + - carrot + +quoting: + - "that should have double quotes" + - "that should remain in single quotes" + - 'a string with " inside' + # next line has some undesired trailing spaces: + - "a string with ' inside" + - can't be sure! + # next line should be converted to use double quotes: + - ["foo", "bar"] + +inline-dictionary: + - { foo: bar } # should add some spacing between curly braces and content + - { foo2: bar2 } # should reduce spacing between curly braces and content + +# YAML 1.1 Boolean-hell: https://yaml.org/type/bool.html +booleans-true: + preferred: true # YAML 1.2 compatible! + answer-1.1: YES + canonical-1.1: y + canonical-upper-1.1: Y + logical-1.1: True + option-1.1: on +booleans-false: + preferred: false # YAML 1.2 compatible! + answer-1.1: NO + canonical-1.1: n + canonical-upper-1.1: N + logical-1.1: False + option-1.1: off + +# ^ double newline should be removed +overly-indented-vault-value: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 123466303630313 + +# this file also has 3 newlines at end-of-file instead of one + diff --git a/test/fixtures/formatting-prettier/fmt-2.yml b/test/fixtures/formatting-prettier/fmt-2.yml new file mode 100644 index 0000000..90ac484 --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-2.yml @@ -0,0 +1,24 @@ +# preamble/header comment +--- +# initial comment +- foo: bar + +- baz: # over indented + - qwerty + - foobar + animals: # under indented + - crow + - pig + - giraffe + +- nothing: null # null + +- octal: + - "0o123" # YAML 1.2 octal + - "0123" # YAML 1.1 octal + +- integer: + - 0 # Not an octal. See #2071 + - 10 + - 9999 + zero: 0 # Not an octal. See #2071 diff --git a/test/fixtures/formatting-prettier/fmt-3.yml b/test/fixtures/formatting-prettier/fmt-3.yml new file mode 100644 index 0000000..658d550 --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-3.yml @@ -0,0 +1,21 @@ +--- +dummy_map: # eol comment + # full line comment not indented + something: + # full line comment indented + # next full line comment indented + - or + # 1 full line comments over indented + # 2 full line comments over indented + - other + - | + # this is part of a string not a yaml comment + # also not a comment + +# comment before top-level +second_key: + - {} # should drop the extra space in flow map + # comment before non top-level + - {} + # comment before non top-level + - [] diff --git a/test/fixtures/list-rules-tests/.yamllint b/test/fixtures/list-rules-tests/.yamllint new file mode 100644 index 0000000..d9e1a25 --- /dev/null +++ b/test/fixtures/list-rules-tests/.yamllint @@ -0,0 +1,2 @@ +--- +{} diff --git a/test/fixtures/parseable.yml b/test/fixtures/parseable.yml new file mode 100644 index 0000000..a1f661a --- /dev/null +++ b/test/fixtures/parseable.yml @@ -0,0 +1,3 @@ +--- +parseable: true +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/quiet.yml b/test/fixtures/quiet.yml new file mode 100644 index 0000000..9bacbc6 --- /dev/null +++ b/test/fixtures/quiet.yml @@ -0,0 +1,3 @@ +--- +quiet: true +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/rulesdir-defaults.yml b/test/fixtures/rulesdir-defaults.yml new file mode 100644 index 0000000..c8884bb --- /dev/null +++ b/test/fixtures/rulesdir-defaults.yml @@ -0,0 +1,5 @@ +--- +rulesdir: + - ./rules +use_default_rules: true +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/rulesdir.yml b/test/fixtures/rulesdir.yml new file mode 100644 index 0000000..77c4c3d --- /dev/null +++ b/test/fixtures/rulesdir.yml @@ -0,0 +1,4 @@ +--- +rulesdir: + - ./rules +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/show-abspath.yml b/test/fixtures/show-abspath.yml new file mode 100644 index 0000000..367caff --- /dev/null +++ b/test/fixtures/show-abspath.yml @@ -0,0 +1,3 @@ +--- +display_relative_path: false +# vim: et:sw=2:syntax=2:ts=2: diff --git a/test/fixtures/show-relpath.yml b/test/fixtures/show-relpath.yml new file mode 100644 index 0000000..684f209 --- /dev/null +++ b/test/fixtures/show-relpath.yml @@ -0,0 +1,3 @@ +--- +display_relative_path: true +# vim: et:sw=2:syntax=2:ts=2: diff --git a/test/fixtures/skip-tags.yml b/test/fixtures/skip-tags.yml new file mode 100644 index 0000000..b9c215b --- /dev/null +++ b/test/fixtures/skip-tags.yml @@ -0,0 +1,4 @@ +--- +skip_list: + - bad_tag +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/strict.yml b/test/fixtures/strict.yml new file mode 100644 index 0000000..00e7aad --- /dev/null +++ b/test/fixtures/strict.yml @@ -0,0 +1,3 @@ +--- +strict: true +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/tags.yml b/test/fixtures/tags.yml new file mode 100644 index 0000000..70dd1b1 --- /dev/null +++ b/test/fixtures/tags.yml @@ -0,0 +1,4 @@ +--- +tags: + - skip_ansible_lint +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/fixtures/unknown-type.yml b/test/fixtures/unknown-type.yml new file mode 100644 index 0000000..54c6d2b --- /dev/null +++ b/test/fixtures/unknown-type.yml @@ -0,0 +1,2 @@ +--- +some: map diff --git a/test/fixtures/verbosity-tests/.yamllint b/test/fixtures/verbosity-tests/.yamllint new file mode 100644 index 0000000..d9e1a25 --- /dev/null +++ b/test/fixtures/verbosity-tests/.yamllint @@ -0,0 +1,2 @@ +--- +{} diff --git a/test/fixtures/verbosity.yml b/test/fixtures/verbosity.yml new file mode 100644 index 0000000..673d6f1 --- /dev/null +++ b/test/fixtures/verbosity.yml @@ -0,0 +1,3 @@ +--- +verbosity: 1 +# vim: et:sw=2:syntax=yaml:ts=2: diff --git a/test/foo.txt b/test/foo.txt new file mode 100644 index 0000000..94f8f1a --- /dev/null +++ b/test/foo.txt @@ -0,0 +1 @@ +Foo file diff --git a/test/local-content/README.md b/test/local-content/README.md new file mode 100644 index 0000000..2b6322a --- /dev/null +++ b/test/local-content/README.md @@ -0,0 +1,6 @@ +The reason that every roles test gets its own directory is that while they +use the same three roles, the way the tests work makes sure that when the +second one runs, the roles and their local plugins from the first test are +still known to Ansible. For that reason, their names reflect the directory +they are in to make sure that tests don't use modules/plugins found by +other tests. diff --git a/test/local-content/collections/ansible_collections/testns/test_collection/galaxy.yml b/test/local-content/collections/ansible_collections/testns/test_collection/galaxy.yml new file mode 100644 index 0000000..4cbaa67 --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/test_collection/galaxy.yml @@ -0,0 +1,4 @@ +--- +namespace: testns +name: test_collection +version: 0.1.0 diff --git a/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py new file mode 100644 index 0000000..58bc269 --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py @@ -0,0 +1,17 @@ +"""A filter plugin.""" +# pylint: disable=invalid-name + + +def a_test_filter(a, b): + """Return a string containing both a and b.""" + return f"{a}:{b}" + + +# pylint: disable=too-few-public-methods +class FilterModule: + """Filter plugin.""" + + @staticmethod + def filters(): + """Return filters.""" + return {"test_filter": a_test_filter} diff --git a/test/local-content/collections/ansible_collections/testns/test_collection/plugins/modules/test_module_2.py b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/modules/test_module_2.py new file mode 100644 index 0000000..a63d06d --- /dev/null +++ b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/modules/test_module_2.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 2!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-collection.yml b/test/local-content/test-collection.yml new file mode 100644 index 0000000..47b097d --- /dev/null +++ b/test/local-content/test-collection.yml @@ -0,0 +1,10 @@ +--- +- name: Use module and filter plugin from local collection + hosts: localhost + tasks: + - name: Use module from local collection + testns.test_collection.test_module_2: + - name: Use filter from local collection + ansible.builtin.assert: + that: + - 1 | testns.test_collection.test_filter(2) == '1:2' diff --git a/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py new file mode 100644 index 0000000..d9012a7 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role1/library/test_module_1_failed_complete.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 1!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml new file mode 100644 index 0000000..680dcab --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_failed_complete: diff --git a/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml new file mode 100644 index 0000000..8646f6b --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_failed_complete: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_failed_complete: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_failed_complete '12345'" diff --git a/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py new file mode 100644 index 0000000..92bd6e7 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py @@ -0,0 +1,19 @@ +"""A test plugin.""" +# pylint: disable=invalid-name + + +def compatibility_in_test(a, b): + """Return True when a is contained in b.""" + return a in b + + +# pylint: disable=too-few-public-methods +class TestModule: + """Test plugin.""" + + @staticmethod + def tests(): + """Return tests.""" + return { + "b_test_failed_complete": compatibility_in_test, + } diff --git a/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py new file mode 100644 index 0000000..4d9de0e --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role3/library/test_module_3_failed_complete.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 3!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml b/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml new file mode 100644 index 0000000..7a36734 --- /dev/null +++ b/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_failed_complete: diff --git a/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py b/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py new file mode 100644 index 0000000..d9012a7 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 1!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-failed/roles/role1/tasks/main.yml b/test/local-content/test-roles-failed/roles/role1/tasks/main.yml new file mode 100644 index 0000000..257493a --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_failed: diff --git a/test/local-content/test-roles-failed/roles/role2/tasks/main.yml b/test/local-content/test-roles-failed/roles/role2/tasks/main.yml new file mode 100644 index 0000000..48daca6 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_failed: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_failed: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_failed '12345'" diff --git a/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py b/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py new file mode 100644 index 0000000..4bb6167 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py @@ -0,0 +1,18 @@ +"""A test plugin.""" + + +def compatibility_in_test(element, container): + """Return True when element is contained in container.""" + return element in container + + +# pylint: disable=too-few-public-methods +class TestModule: + """Test plugin.""" + + @staticmethod + def tests(): + """Return tests.""" + return { + "b_test_failed": compatibility_in_test, + } diff --git a/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py b/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py new file mode 100644 index 0000000..4d9de0e --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 3!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-failed/roles/role3/tasks/main.yml b/test/local-content/test-roles-failed/roles/role3/tasks/main.yml new file mode 100644 index 0000000..ad17eb0 --- /dev/null +++ b/test/local-content/test-roles-failed/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_failed: diff --git a/test/local-content/test-roles-failed/test.yml b/test/local-content/test-roles-failed/test.yml new file mode 100644 index 0000000..08ff0f6 --- /dev/null +++ b/test/local-content/test-roles-failed/test.yml @@ -0,0 +1,7 @@ +--- +- name: Use roles with local module in wrong order, so that Ansible fails + hosts: localhost + roles: + - role2 + - role3 + - role1 diff --git a/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py b/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py new file mode 100644 index 0000000..d9012a7 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role1/library/test_module_1_success.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 1!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-success/roles/role1/tasks/main.yml b/test/local-content/test-roles-success/roles/role1/tasks/main.yml new file mode 100644 index 0000000..ba920af --- /dev/null +++ b/test/local-content/test-roles-success/roles/role1/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 1 + test_module_1_success: diff --git a/test/local-content/test-roles-success/roles/role2/tasks/main.yml b/test/local-content/test-roles-success/roles/role2/tasks/main.yml new file mode 100644 index 0000000..a540cf1 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role2/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_1_success: +- name: Use local module from other role that has been included before this one + # If it has not been included before, loading this role fails! + test_module_3_success: +- name: Use local test plugin + assert: + that: + - "'2' is b_test_success '12345'" diff --git a/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py b/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py new file mode 100644 index 0000000..6cf2bae --- /dev/null +++ b/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py @@ -0,0 +1,18 @@ +"""A test plugin.""" + + +def compatibility_in_test(element, container): + """Return True when element contained in container.""" + return element in container + + +# pylint: disable=too-few-public-methods +class TestModule: + """Test plugin.""" + + @staticmethod + def tests(): + """Return tests.""" + return { + "b_test_success": compatibility_in_test, + } diff --git a/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py b/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py new file mode 100644 index 0000000..4d9de0e --- /dev/null +++ b/test/local-content/test-roles-success/roles/role3/library/test_module_3_success.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +"""A module.""" + +from ansible.module_utils.basic import AnsibleModule + + +def main() -> None: + """Execute module.""" + module = AnsibleModule({}) + module.exit_json(msg="Hello 3!") + + +if __name__ == "__main__": + main() diff --git a/test/local-content/test-roles-success/roles/role3/tasks/main.yml b/test/local-content/test-roles-success/roles/role3/tasks/main.yml new file mode 100644 index 0000000..c77a7c8 --- /dev/null +++ b/test/local-content/test-roles-success/roles/role3/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- name: Use local module 3 + test_module_3_success: diff --git a/test/rules/__init__.py b/test/rules/__init__.py new file mode 100644 index 0000000..28b581d --- /dev/null +++ b/test/rules/__init__.py @@ -0,0 +1 @@ +"""Tests for specific rules.""" diff --git a/test/rules/fixtures/__init__.py b/test/rules/fixtures/__init__.py new file mode 100644 index 0000000..d049bf0 --- /dev/null +++ b/test/rules/fixtures/__init__.py @@ -0,0 +1,3 @@ +"""Test rules resources.""" + +__all__ = ["ematcher", "raw_task", "unset_variable_matcher"] diff --git a/test/rules/fixtures/ematcher.py b/test/rules/fixtures/ematcher.py new file mode 100644 index 0000000..cbae696 --- /dev/null +++ b/test/rules/fixtures/ematcher.py @@ -0,0 +1,15 @@ +"""Custom rule used as fixture.""" +from ansiblelint.rules import AnsibleLintRule + + +class EMatcherRule(AnsibleLintRule): + """BANNED string found.""" + + id = "TEST0001" + description = ( + "This is a test custom rule that looks for lines " + "containing BANNED string" + ) + tags = ["fake", "dummy", "test1"] + + def match(self, line: str) -> bool: + return "BANNED" in line diff --git a/test/rules/fixtures/raw_task.md b/test/rules/fixtures/raw_task.md new file mode 100644 index 0000000..2aa6d22 --- /dev/null +++ b/test/rules/fixtures/raw_task.md @@ -0,0 +1,3 @@ +# raw-task + +This is a test rule that looks in a raw task to flag raw action params. diff --git a/test/rules/fixtures/raw_task.py b/test/rules/fixtures/raw_task.py new file mode 100644 index 0000000..672789d --- /dev/null +++ b/test/rules/fixtures/raw_task.py @@ -0,0 +1,25 @@ +"""Test Rule that needs_raw_task.""" +from __future__ import annotations + +from typing import Any + +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import AnsibleLintRule + + +class RawTaskRule(AnsibleLintRule): + """Test rule that inspects the raw task.""" + + id = "raw-task" + shortdesc = "Test rule that inspects the raw task" + tags = ["fake", "dummy", "test3"] + needs_raw_task = True + + def matchtask( + self, task: dict[str, Any], file: Lintable | None = None + ) -> bool | str: + """Match a task using __raw_task__ to inspect the module params type.""" + raw_task = task["__raw_task__"] + module = task["action"]["__ansible_module_original__"] + found_raw_task_params = not isinstance(raw_task[module], dict) + return found_raw_task_params diff --git a/test/rules/fixtures/unset_variable_matcher.py b/test/rules/fixtures/unset_variable_matcher.py new file mode 100644 index 0000000..c2b1744 --- /dev/null +++ b/test/rules/fixtures/unset_variable_matcher.py @@ -0,0 +1,16 @@ +"""Custom linting rule used as test fixture.""" +from ansiblelint.rules import AnsibleLintRule + + +class UnsetVariableMatcherRule(AnsibleLintRule): + """Line contains untemplated variable.""" + + id = "TEST0002" + description = ( + "This is a test rule that looks for lines " + + "post templating that still contain {{" + ) + tags = ["fake", "dummy", "test2"] + + def match(self, line: str) -> bool: + return "{{" in line diff --git a/test/rules/test_deprecated_module.py b/test/rules/test_deprecated_module.py new file mode 100644 index 0000000..506d91f --- /dev/null +++ b/test/rules/test_deprecated_module.py @@ -0,0 +1,25 @@ +"""Tests for deprecated-module rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.deprecated_module import DeprecatedModuleRule +from ansiblelint.testing import RunFromText + +MODULE_DEPRECATED = """ +- name: Task example + docker: + debug: test +""" + + +def test_module_deprecated() -> None: + """Test for deprecated-module.""" + collection = RulesCollection() + collection.register(DeprecatedModuleRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(MODULE_DEPRECATED) + assert len(results) == 1 + # based on version and blend of ansible being used, we may + # get a missing module, so we future proof the test + assert ( + "couldn't resolve module" not in results[0].message + or "Deprecated module" not in results[0].message + ) diff --git a/test/rules/test_inline_env_var.py b/test/rules/test_inline_env_var.py new file mode 100644 index 0000000..98f337e --- /dev/null +++ b/test/rules/test_inline_env_var.py @@ -0,0 +1,90 @@ +"""Tests for inline-env-var rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.inline_env_var import EnvVarsInCommandRule +from ansiblelint.testing import RunFromText + +SUCCESS_PLAY_TASKS = """ +- hosts: localhost + + tasks: + - name: Actual use of environment + shell: echo $HELLO + environment: + HELLO: hello + + - name: Use some key-value pairs + command: chdir=/tmp creates=/tmp/bobbins warn=no touch bobbins + + - name: Commands can have flags + command: abc --xyz=def blah + + - name: Commands can have equals in them + command: echo "===========" + + - name: Commands with cmd + command: + cmd: + echo "-------" + + - name: Command with stdin (ansible > 2.4) + command: /bin/cat + args: + stdin: "Hello, world!" + + - name: Use argv to send the command as a list + command: + argv: + - /bin/echo + - Hello + - World + + - name: Another use of argv + command: + args: + argv: + - echo + - testing + + - name: Environment variable with shell + shell: HELLO=hello echo $HELLO + + - name: Command with stdin_add_newline (ansible > 2.8) + command: /bin/cat + args: + stdin: "Hello, world!" + stdin_add_newline: false + + - name: Command with strip_empty_ends (ansible > 2.8) + command: echo + args: + strip_empty_ends: false +""" + +FAIL_PLAY_TASKS = """ +- hosts: localhost + + tasks: + - name: Environment variable with command + command: HELLO=hello echo $HELLO + + - name: Typo some stuff + command: cerates=/tmp/blah warn=no touch /tmp/blah +""" + + +def test_success() -> None: + """Positive test for inline-env-var.""" + collection = RulesCollection() + collection.register(EnvVarsInCommandRule()) + runner = RunFromText(collection) + results = runner.run_playbook(SUCCESS_PLAY_TASKS) + assert len(results) == 0 + + +def test_fail() -> None: + """Negative test for inline-env-var.""" + collection = RulesCollection() + collection.register(EnvVarsInCommandRule()) + runner = RunFromText(collection) + results = runner.run_playbook(FAIL_PLAY_TASKS) + assert len(results) == 2 diff --git a/test/rules/test_line_too_long.py b/test/rules/test_line_too_long.py new file mode 100644 index 0000000..3c7517b --- /dev/null +++ b/test/rules/test_line_too_long.py @@ -0,0 +1,20 @@ +"""Tests for line-too-long rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.yaml_rule import YamllintRule +from ansiblelint.testing import RunFromText + +LONG_LINE = """\ +--- +- name: Task example + debug: + msg: 'This is a very long text that is used in order to verify the rule that checks for very long lines. We do hope it was long enough to go over the line limit.' +""" # noqa: E501 + + +def test_long_line() -> None: + """Negative test for long-line.""" + collection = RulesCollection() + collection.register(YamllintRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(LONG_LINE) + assert len(results) == 1 diff --git a/test/rules/test_literal_compare.py b/test/rules/test_literal_compare.py new file mode 100644 index 0000000..6953cb1 --- /dev/null +++ b/test/rules/test_literal_compare.py @@ -0,0 +1,93 @@ +"""Tests for literal-compare rule.""" +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.literal_compare import ComparisonToLiteralBoolRule +from ansiblelint.testing import RunFromText + +PASS_WHEN = """ +- name: Example task + debug: + msg: test + when: my_var + +- name: Another example task + debug: + msg: test + when: + - 1 + 1 == 2 + - true +""" + +PASS_WHEN_NOT_FALSE = """ +- name: Example task + debug: + msg: test + when: not my_var +""" + +PASS_WHEN_NOT_NULL = """ +- name: Example task + debug: + msg: test + when: my_var not None +""" + +FAIL_LITERAL_TRUE = """ +- name: Example task + debug: + msg: test + when: my_var == True +""" + +FAIL_LITERAL_FALSE = """ +- name: Example task + debug: + msg: test + when: my_var == false + +- name: Another example task + debug: + msg: test + when: + - my_var == false +""" + + +@pytest.mark.parametrize( + ("input_str", "found_errors"), + ( + pytest.param( + PASS_WHEN, + 0, + id="pass_when", + ), + pytest.param( + PASS_WHEN_NOT_FALSE, + 0, + id="when_not_false", + ), + pytest.param( + PASS_WHEN_NOT_NULL, + 0, + id="when_not_null", + ), + pytest.param( + FAIL_LITERAL_TRUE, + 1, + id="literal_true", + ), + pytest.param( + FAIL_LITERAL_FALSE, + 2, + id="literal_false", + ), + ), +) +def test_literal_compare(input_str: str, found_errors: int) -> None: + """Test literal-compare.""" + collection = RulesCollection() + collection.register(ComparisonToLiteralBoolRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(input_str) + assert len(results) == found_errors diff --git a/test/rules/test_meta_change_from_default.py b/test/rules/test_meta_change_from_default.py new file mode 100644 index 0000000..31125d3 --- /dev/null +++ b/test/rules/test_meta_change_from_default.py @@ -0,0 +1,42 @@ +"""Tests for meta-incorrect rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.meta_incorrect import MetaChangeFromDefaultRule +from ansiblelint.testing import RunFromText + +DEFAULT_GALAXY_INFO = """ +galaxy_info: + author: your name + description: your description + company: your company (optional) + license: license (GPLv2, CC-BY, etc) +""" + +NO_GALAXY_INFO = """ +galaxy_information: + author: your name + description: your description + company: your company (optional) + license: license (GPLv2, CC-BY, etc) +""" + + +def test_default_galaxy_info() -> None: + """Test for meta-incorrect.""" + collection = RulesCollection() + collection.register(MetaChangeFromDefaultRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(DEFAULT_GALAXY_INFO) + # Disabled check because default value is not passing schema validation + # assert "Should change default metadata: author" in str(results) + assert "Should change default metadata: description" in str(results) + assert "Should change default metadata: company" in str(results) + assert "Should change default metadata: license" in str(results) + + +def test_no_galaxy_info() -> None: + """Test for no galaxy info passed to meta-incorrect.""" + collection = RulesCollection() + collection.register(MetaChangeFromDefaultRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(NO_GALAXY_INFO) + assert results == [] diff --git a/test/rules/test_meta_no_info.py b/test/rules/test_meta_no_info.py new file mode 100644 index 0000000..bbadec6 --- /dev/null +++ b/test/rules/test_meta_no_info.py @@ -0,0 +1,98 @@ +"""Tests for meta-no-info rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.meta_no_info import MetaMainHasInfoRule +from ansiblelint.testing import RunFromText + +NO_GALAXY_INFO = """ +author: the author +description: this meta/main.yml has no galaxy_info +""" + +MISSING_INFO = """ +galaxy_info: + # author: the author + description: Testing if meta contains values + company: Not applicable + + license: MIT + + # min_ansible_version: 2.5 + + platforms: + - name: Fedora + versions: + - 25 + - missing_name: No name + versions: + - 25 +""" + +BAD_TYPES = """ +galaxy_info: + author: 007 + description: ['Testing meta'] + company: Not applicable + + license: MIT + + min_ansible_version: 2.5 + + platforms: Fedora +""" + +PLATFORMS_LIST_OF_STR = """ +galaxy_info: + author: '007' + description: 'Testing meta' + company: Not applicable + + license: MIT + + min_ansible_version: 2.5 + + platforms: ['Fedora', 'EL'] +""" + + +def test_no_galaxy_info() -> None: + """Test meta-no-info with missing galaxy_info.""" + collection = RulesCollection() + collection.register(MetaMainHasInfoRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(NO_GALAXY_INFO) + assert len(results) == 1 + assert "No 'galaxy_info' found" in str(results) + + +def test_missing_info() -> None: + """Test meta-no-info.""" + collection = RulesCollection() + collection.register(MetaMainHasInfoRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(MISSING_INFO) + assert len(results) == 3 + assert "Role info should contain author" in str(results) + assert "Role info should contain min_ansible_version" in str(results) + assert "Platform should contain name" in str(results) + + +def test_bad_types() -> None: + """Test meta-no-info with bad types.""" + collection = RulesCollection() + collection.register(MetaMainHasInfoRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(BAD_TYPES) + assert len(results) == 3 + assert "author should be a string" in str(results) + assert "description should be a string" in str(results) + assert "Platforms should be a list of dictionaries" in str(results) + + +def test_platform_list_of_str() -> None: + """Test meta-no-info with platforms.""" + collection = RulesCollection() + collection.register(MetaMainHasInfoRule()) + runner = RunFromText(collection) + results = runner.run_role_meta_main(PLATFORMS_LIST_OF_STR) + assert len(results) == 1 + assert "Platforms should be a list of dictionaries" in str(results) diff --git a/test/rules/test_meta_video_links.py b/test/rules/test_meta_video_links.py new file mode 100644 index 0000000..3772ea2 --- /dev/null +++ b/test/rules/test_meta_video_links.py @@ -0,0 +1,46 @@ +"""Tests for meta-video-links rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.meta_video_links import MetaVideoLinksRule +from ansiblelint.testing import RunFromText + +META_VIDEO_LINKS = """ +galaxy_info: + video_links: + - url: https://www.youtube.com/watch?v=aWmRepTSFKs&feature=youtu.be + title: Proper format + - url: https://drive.google.com/file/d/1spYR51l8SqQqvAhSdZE7/view + title: Check for VIDEO_REGEXP validity and break + - https://www.youtube.com/watch?v=aWmRepTSFKs&feature=youtu.be + - my_bad_key: https://www.youtube.com/watch?v=aWmRepTSFKs&feature=youtu.be + title: This has a bad key + - url: www.acme.com/vid + title: Bad format of url +""" + +META_NO_GALAXY_INFO = """ +galaxy_information: + video_links: + - url: https://www.youtube.com/watch?v=aWmRepTSFKs&feature=youtu.be +""" + + +def test_video_links() -> None: + """Test meta_video_links.""" + collection = RulesCollection() + collection.register(MetaVideoLinksRule()) + runner = RunFromText(collection) + + results = runner.run_role_meta_main(META_VIDEO_LINKS) + assert "Expected item in 'video_links' to be a dictionary" in str(results) + assert "'video_links' to contain only keys 'url' and 'title'" in str(results) + assert "URL format 'www.acme.com/vid' is not recognized" in str(results) + + +def test_meta_video_links_no_galaxy_info() -> None: + """Test meta_video_links.""" + collection = RulesCollection() + collection.register(MetaVideoLinksRule()) + runner = RunFromText(collection) + + results = runner.run_role_meta_main(META_NO_GALAXY_INFO) + assert len(results) == 0 diff --git a/test/rules/test_no_changed_when.py b/test/rules/test_no_changed_when.py new file mode 100644 index 0000000..c89d8f4 --- /dev/null +++ b/test/rules/test_no_changed_when.py @@ -0,0 +1,23 @@ +"""Tests for no-change-when rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.no_changed_when import CommandHasChangesCheckRule +from ansiblelint.runner import Runner + + +def test_command_changes_positive() -> None: + """Positive test for no-changed-when.""" + collection = RulesCollection() + collection.register(CommandHasChangesCheckRule()) + success = "examples/playbooks/command-check-success.yml" + good_runner = Runner(success, rules=collection) + assert [] == good_runner.run() + + +def test_command_changes_negative() -> None: + """Negative test for no-changed-when.""" + collection = RulesCollection() + collection.register(CommandHasChangesCheckRule()) + failure = "examples/playbooks/command-check-failure.yml" + bad_runner = Runner(failure, rules=collection) + errs = bad_runner.run() + assert len(errs) == 2 diff --git a/test/rules/test_no_relative_paths.py b/test/rules/test_no_relative_paths.py new file mode 100644 index 0000000..6999cbb --- /dev/null +++ b/test/rules/test_no_relative_paths.py @@ -0,0 +1,53 @@ +"""Tests for no-relative-paths rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.no_relative_paths import RoleRelativePath +from ansiblelint.testing import RunFromText + +FAIL_TASKS = """ +- name: Template example + template: + src: ../templates/foo.j2 + dest: /etc/file.conf +- name: Copy example + copy: + src: ../files/foo.conf + dest: /etc/foo.conf +# Removed from test suite as module is no longer part of core +# - name: Some win_template example +# win_template: +# src: ../win_templates/file.conf.j2 +# dest: file.conf +# - name: Some win_copy example +# win_copy: +# src: ../files/foo.conf +# dest: renamed-foo.conf +""" + +SUCCESS_TASKS = """ +- name: Content example with no src + copy: + content: '# This file was moved to /etc/other.conf' + dest: /etc/mine.conf +- name: Copy example + copy: + src: /home/example/files/foo.conf + dest: /etc/foo.conf +""" + + +def test_no_relative_paths_fail() -> None: + """Negative test for no-relative-paths.""" + collection = RulesCollection() + collection.register(RoleRelativePath()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(FAIL_TASKS) + assert len(results) == 2 + + +def test_no_relative_paths_success() -> None: + """Positive test for no-relative-paths.""" + collection = RulesCollection() + collection.register(RoleRelativePath()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(SUCCESS_TASKS) + assert len(results) == 0 diff --git a/test/rules/test_package_latest.py b/test/rules/test_package_latest.py new file mode 100644 index 0000000..5631f02 --- /dev/null +++ b/test/rules/test_package_latest.py @@ -0,0 +1,23 @@ +"""Tests for package-latest rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.package_latest import PackageIsNotLatestRule +from ansiblelint.runner import Runner + + +def test_package_not_latest_positive() -> None: + """Positive test for package-latest.""" + collection = RulesCollection() + collection.register(PackageIsNotLatestRule()) + success = "examples/playbooks/package-check-success.yml" + good_runner = Runner(success, rules=collection) + assert [] == good_runner.run() + + +def test_package_not_latest_negative() -> None: + """Negative test for package-latest.""" + collection = RulesCollection() + collection.register(PackageIsNotLatestRule()) + failure = "examples/playbooks/package-check-failure.yml" + bad_runner = Runner(failure, rules=collection) + errs = bad_runner.run() + assert len(errs) == 4 diff --git a/test/rules/test_risky_shell_pipe.py b/test/rules/test_risky_shell_pipe.py new file mode 100644 index 0000000..b2ecb67 --- /dev/null +++ b/test/rules/test_risky_shell_pipe.py @@ -0,0 +1,94 @@ +"""Tests for risky-shell-pile rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.risky_shell_pipe import ShellWithoutPipefail +from ansiblelint.testing import RunFromText + +FAIL_TASKS = """ +--- +- hosts: localhost + become: no + tasks: + - name: Pipeline without pipefail + shell: false | cat + + - name: Pipeline with or and pipe, no pipefail + shell: false || true | cat + + - shell: | + df | grep '/dev' +""" + +SUCCESS_TASKS = """ +--- +- hosts: localhost + become: no + tasks: + - name: Pipeline with pipefail + shell: set -o pipefail && false | cat + + - name: Pipeline with pipefail, multi-line + shell: | + set -o pipefail + false | cat + + - name: Pipeline with pipefail, complex set + shell: | + set -e -x -o pipefail + false | cat + + - name: Pipeline with pipefail, complex set + shell: | + set -e -x -o pipefail + false | cat + + - name: Pipeline with pipefail, complex set + shell: | + set -eo pipefail + false | cat + + - name: Pipeline with pipefail not at first line + shell: | + echo foo + set -eo pipefail + false | cat + + - name: Pipeline without pipefail, ignoring errors + shell: false | cat + ignore_errors: true + + - name: Non-pipeline without pipefail + shell: "true" + + - name: Command without pipefail + command: "true" + + - name: Shell with or + shell: + false || true + + - shell: | + set -o pipefail + df | grep '/dev' + + - name: Should not fail due to ignore_errors being true + shell: false | cat + ignore_errors: true +""" + + +def test_fail() -> None: + """Negative test for risky-shell-pipe.""" + collection = RulesCollection() + collection.register(ShellWithoutPipefail()) + runner = RunFromText(collection) + results = runner.run_playbook(FAIL_TASKS) + assert len(results) == 3 + + +def test_success() -> None: + """Positive test for risky-shell-pipe.""" + collection = RulesCollection() + collection.register(ShellWithoutPipefail()) + runner = RunFromText(collection) + results = runner.run_playbook(SUCCESS_TASKS) + assert len(results) == 0 diff --git a/test/rules/test_role_names.py b/test/rules/test_role_names.py new file mode 100644 index 0000000..4d55a61 --- /dev/null +++ b/test/rules/test_role_names.py @@ -0,0 +1,86 @@ +"""Test the RoleNames rule.""" +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest +from _pytest.fixtures import SubRequest + +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.role_name import RoleNames +from ansiblelint.runner import Runner + +ROLE_NAME_VALID = "test_role" + +TASK_MINIMAL = """ +- name: Some task + ping: +""" + +ROLE_MINIMAL = {"tasks": {"main.yml": TASK_MINIMAL}} +ROLE_META_EMPTY = {"meta": {"main.yml": ""}} + +ROLE_WITH_EMPTY_META = {**ROLE_MINIMAL, **ROLE_META_EMPTY} + +PLAY_INCLUDE_ROLE = f""" +- hosts: all + roles: + - {ROLE_NAME_VALID} +""" + + +@pytest.fixture(name="test_rules_collection") +def fixture_test_rules_collection() -> RulesCollection: + """Instantiate a roles collection for tests.""" + collection = RulesCollection() + collection.register(RoleNames()) + return collection + + +def dict_to_files(parent_dir: Path, file_dict: dict[str, Any]) -> None: + """Write a nested dict to a file and directory structure below parent_dir.""" + for file, content in file_dict.items(): + if isinstance(content, dict): + directory = parent_dir / file + directory.mkdir() + dict_to_files(directory, content) + else: + (parent_dir / file).write_text(content) + + +@pytest.fixture(name="playbook_path") +def fixture_playbook_path(request: SubRequest, tmp_path: Path) -> str: + """Create a playbook with a role in a temporary directory.""" + playbook_text = request.param[0] + role_name = request.param[1] + role_layout = request.param[2] + role_path = tmp_path / role_name + role_path.mkdir() + dict_to_files(role_path, role_layout) + play_path = tmp_path / "playbook.yml" + play_path.write_text(playbook_text) + return str(play_path) + + +@pytest.mark.parametrize( + ("playbook_path", "messages"), + ( + pytest.param( + (PLAY_INCLUDE_ROLE, ROLE_NAME_VALID, ROLE_WITH_EMPTY_META), + [], + id="ROLE_EMPTY_META", + ), + ), + indirect=("playbook_path",), +) +def test_role_name( + test_rules_collection: RulesCollection, playbook_path: str, messages: list[str] +) -> None: + """Lint a playbook and compare the expected messages with the actual messages.""" + runner = Runner(playbook_path, rules=test_rules_collection) + results = runner.run() + assert len(results) == len(messages) + results_text = str(results) + for message in messages: + assert message in results_text diff --git a/test/rules/test_use_handler_rather_than_when_changed.py b/test/rules/test_use_handler_rather_than_when_changed.py new file mode 100644 index 0000000..2b70bf6 --- /dev/null +++ b/test/rules/test_use_handler_rather_than_when_changed.py @@ -0,0 +1,86 @@ +"""Tests for no-handler rule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.no_handler import UseHandlerRatherThanWhenChangedRule +from ansiblelint.testing import RunFromText + +SUCCESS_TASKS = """ +- name: Print helpful error message + debug: + var: result + when: result.failed + +- name: Do something when hello is output + debug: + msg: why isn't this a handler + when: result.stdout == "hello" + +- name: Never actually debug + debug: + var: result + when: False + +- name: "Don't execute this step" + debug: + msg: "debug message" + when: + - false + +- name: Check when with a list + debug: + var: result + when: + - conditionA + - conditionB +""" + + +FAIL_TASKS = """ +- name: Execute command + command: echo hello + register: result + +- name: This should be a handler + debug: + msg: why isn't this a handler + when: result.changed + +- name: This should be a handler 2 + debug: + msg: why isn't this a handler + when: result|changed + +- name: This should be a handler 3 + debug: + msg: why isn't this a handler + when: result.changed == true + +- name: This should be a handler 4 + debug: + msg: why isn't this a handler + when: result['changed'] == true + +- name: This should be a handler 5 + debug: + msg: why isn't this a handler + when: + - result['changed'] == true + - another_condition +""" + + +def test_no_handler_success() -> None: + """Positive test for no-handler.""" + collection = RulesCollection() + collection.register(UseHandlerRatherThanWhenChangedRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(SUCCESS_TASKS) + assert len(results) == 0 + + +def test_no_handler_fail() -> None: + """Negative test for no-handler.""" + collection = RulesCollection() + collection.register(UseHandlerRatherThanWhenChangedRule()) + runner = RunFromText(collection) + results = runner.run_role_tasks_main(FAIL_TASKS) + assert len(results) == 5 diff --git a/test/schemas/.mocharc.json b/test/schemas/.mocharc.json new file mode 100644 index 0000000..0148197 --- /dev/null +++ b/test/schemas/.mocharc.json @@ -0,0 +1,7 @@ +{ + "colors": true, + "extension": ["ts"], + "require": "ts-node/register", + "slow": "500", + "spec": "src/**/*.spec.ts" +} diff --git a/test/schemas/data/licenses.json b/test/schemas/data/licenses.json new file mode 100644 index 0000000..9c7cdbc --- /dev/null +++ b/test/schemas/data/licenses.json @@ -0,0 +1,5766 @@ +{ + "licenseListVersion": "3.13", + "licenses": [ + { + "reference": "https://spdx.org/licenses/bzip2-1.0.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.6.json", + "referenceNumber": 0, + "name": "bzip2 and libbzip2 License v1.0.6", + "licenseId": "bzip2-1.0.6", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dbzip2.git;a\u003dblob;f\u003dLICENSE;hb\u003dbzip2-1.0.6", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glulxe.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glulxe.json", + "referenceNumber": 1, + "name": "Glulxe License", + "licenseId": "Glulxe", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Glulxe" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-7.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-7.0.0.json", + "referenceNumber": 2, + "name": "The Parity Public License 7.0.0", + "licenseId": "Parity-7.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/7.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OML.json", + "referenceNumber": 3, + "name": "Open Market License", + "licenseId": "OML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Open_Market_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UCL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UCL-1.0.json", + "referenceNumber": 4, + "name": "Upstream Compatibility License v1.0", + "licenseId": "UCL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UCL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/UPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UPL-1.0.json", + "referenceNumber": 5, + "name": "Universal Permissive License v1.0", + "licenseId": "UPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UPL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-Protection.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Protection.json", + "referenceNumber": 6, + "name": "BSD Protection License", + "licenseId": "BSD-Protection", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_Protection_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OCLC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCLC-2.0.json", + "referenceNumber": 7, + "name": "OCLC Research Public License 2.0", + "licenseId": "OCLC-2.0", + "seeAlso": [ + "http://www.oclc.org/research/activities/software/license/v2final.htm", + "https://opensource.org/licenses/OCLC-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/eCos-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/eCos-2.0.json", + "referenceNumber": 8, + "name": "eCos license version 2.0", + "licenseId": "eCos-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/ecos-license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Multics.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Multics.json", + "referenceNumber": 9, + "name": "Multics License", + "licenseId": "Multics", + "seeAlso": [ + "https://opensource.org/licenses/Multics" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/IPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPL-1.0.json", + "referenceNumber": 10, + "name": "IBM Public License v1.0", + "licenseId": "IPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/IPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/IPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPA.json", + "referenceNumber": 11, + "name": "IPA Font License", + "licenseId": "IPA", + "seeAlso": [ + "https://opensource.org/licenses/IPA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/eGenix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/eGenix.json", + "referenceNumber": 12, + "name": "eGenix.com Public License 1.1.0", + "licenseId": "eGenix", + "seeAlso": [ + "http://www.egenix.com/products/eGenix.com-Public-License-1.1.0.pdf", + "https://fedoraproject.org/wiki/Licensing/eGenix.com_Public_License_1.1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glide.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glide.json", + "referenceNumber": 13, + "name": "3dfx Glide License", + "licenseId": "Glide", + "seeAlso": [ + "http://www.users.on.net/~triforce/glidexp/COPYING.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Entessa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Entessa.json", + "referenceNumber": 14, + "name": "Entessa Public License v1.0", + "licenseId": "Entessa", + "seeAlso": [ + "https://opensource.org/licenses/Entessa" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/FSFUL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFUL.json", + "referenceNumber": 15, + "name": "FSF Unlimited License", + "licenseId": "FSFUL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Nunit.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/Nunit.json", + "referenceNumber": 16, + "name": "Nunit License", + "licenseId": "Nunit", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Nunit" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.json", + "referenceNumber": 17, + "name": "Mozilla Public License 2.0 (no copyleft exception)", + "licenseId": "MPL-2.0-no-copyleft-exception", + "seeAlso": [ + "http://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/libpng-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libpng-2.0.json", + "referenceNumber": 18, + "name": "PNG Reference Library version 2", + "licenseId": "libpng-2.0", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.1.json", + "referenceNumber": 19, + "name": "Open LDAP Public License v2.2.1", + "licenseId": "OLDAP-2.2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d4bc786f34b50aa301be6f5600f58a980070f481e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/curl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/curl.json", + "referenceNumber": 20, + "name": "curl License", + "licenseId": "curl", + "seeAlso": [ + "https://github.com/bagder/curl/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD.json", + "referenceNumber": 21, + "name": "ANTLR Software Rights Notice", + "licenseId": "ANTLR-PD", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0.json", + "referenceNumber": 22, + "name": "Creative Commons Attribution Share Alike 2.0 Generic", + "licenseId": "CC-BY-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-P-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-P-1.1.json", + "referenceNumber": 23, + "name": "Licence Libre du Québec – Permissive version 1.1", + "licenseId": "LiLiQ-P-1.1", + "seeAlso": [ + "https://forge.gouv.qc.ca/licence/fr/liliq-v1-1/", + "http://opensource.org/licenses/LiLiQ-P-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/TCP-wrappers.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCP-wrappers.json", + "referenceNumber": 24, + "name": "TCP Wrappers License", + "licenseId": "TCP-wrappers", + "seeAlso": [ + "http://rc.quest.com/topics/openssh/license.php#tcpwrappers" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2016.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2016.json", + "referenceNumber": 25, + "name": "Unicode License Agreement - Data Files and Software (2016)", + "licenseId": "Unicode-DFS-2016", + "seeAlso": [ + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ODbL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODbL-1.0.json", + "referenceNumber": 26, + "name": "Open Data Commons Open Database License v1.0", + "licenseId": "ODbL-1.0", + "seeAlso": [ + "http://www.opendatacommons.org/licenses/odbl/1.0/", + "https://opendatacommons.org/licenses/odbl/1-0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3a.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3a.json", + "referenceNumber": 27, + "name": "LaTeX Project Public License v1.3a", + "licenseId": "LPPL-1.3a", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3a.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.2.json", + "referenceNumber": 28, + "name": "CERN Open Hardware Licence v1.2", + "licenseId": "CERN-OHL-1.2", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ADSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ADSL.json", + "referenceNumber": 29, + "name": "Amazon Digital Services License", + "licenseId": "ADSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AmazonDigitalServicesLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.0.json", + "referenceNumber": 30, + "name": "Common Development and Distribution License 1.0", + "licenseId": "CDDL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/cddl1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Motosoto.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Motosoto.json", + "referenceNumber": 31, + "name": "Motosoto License", + "licenseId": "Motosoto", + "seeAlso": [ + "https://opensource.org/licenses/Motosoto" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BUSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BUSL-1.1.json", + "referenceNumber": 32, + "name": "Business Source License 1.1", + "licenseId": "BUSL-1.1", + "seeAlso": [ + "https://mariadb.com/bsl11/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-1.0.json", + "referenceNumber": 33, + "name": "Open Government Licence v1.0", + "licenseId": "OGL-UK-1.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/1/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/xinetd.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xinetd.json", + "referenceNumber": 34, + "name": "xinetd License", + "licenseId": "xinetd", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xinetd_License" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Imlib2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Imlib2.json", + "referenceNumber": 35, + "name": "Imlib2 License", + "licenseId": "Imlib2", + "seeAlso": [ + "http://trac.enlightenment.org/e/browser/trunk/imlib2/COPYING", + "https://git.enlightenment.org/legacy/imlib2.git/tree/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SNIA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SNIA.json", + "referenceNumber": 36, + "name": "SNIA Public License 1.1", + "licenseId": "SNIA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SNIA_Public_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGTSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGTSL.json", + "referenceNumber": 37, + "name": "Open Group Test Suite License", + "licenseId": "OGTSL", + "seeAlso": [ + "http://www.opengroup.org/testing/downloads/The_Open_Group_TSL.txt", + "https://opensource.org/licenses/OGTSL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/TMate.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TMate.json", + "referenceNumber": 38, + "name": "TMate Open Source License", + "licenseId": "TMate", + "seeAlso": [ + "http://svnkit.com/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OCCT-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCCT-PL.json", + "referenceNumber": 39, + "name": "Open CASCADE Technology Public License", + "licenseId": "OCCT-PL", + "seeAlso": [ + "http://www.opencascade.com/content/occt-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-or-later.json", + "referenceNumber": 40, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/YPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.1.json", + "referenceNumber": 41, + "name": "Yahoo! Public License v1.1", + "licenseId": "YPL-1.1", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.1.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.0.json", + "referenceNumber": 42, + "name": "CeCILL Free Software License Agreement v2.0", + "licenseId": "CECILL-2.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PHP-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.0.json", + "referenceNumber": 43, + "name": "PHP License v3.0", + "licenseId": "PHP-3.0", + "seeAlso": [ + "http://www.php.net/license/3_0.txt", + "https://opensource.org/licenses/PHP-3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BlueOak-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BlueOak-1.0.0.json", + "referenceNumber": 44, + "name": "Blue Oak Model License 1.0.0", + "licenseId": "BlueOak-1.0.0", + "seeAlso": [ + "https://blueoakcouncil.org/license/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.3.json", + "referenceNumber": 45, + "name": "Zimbra Public License v1.3", + "licenseId": "Zimbra-1.3", + "seeAlso": [ + "http://web.archive.org/web/20100302225219/http://www.zimbra.com/license/zimbra-public-license-1-3.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OGC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGC-1.0.json", + "referenceNumber": 46, + "name": "OGC Software License, Version 1.0", + "licenseId": "OGC-1.0", + "seeAlso": [ + "https://www.ogc.org/ogc/software/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NASA-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NASA-1.3.json", + "referenceNumber": 47, + "name": "NASA Open Source Agreement 1.3", + "licenseId": "NASA-1.3", + "seeAlso": [ + "http://ti.arc.nasa.gov/opensource/nosa/", + "https://opensource.org/licenses/NASA-1.3" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SPL-1.0.json", + "referenceNumber": 48, + "name": "Sun Public License v1.0", + "licenseId": "SPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/SPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Intel-ACPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel-ACPI.json", + "referenceNumber": 49, + "name": "Intel ACPI Software License Agreement", + "licenseId": "Intel-ACPI", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Intel_ACPI_Software_License_Agreement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SISSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL-1.2.json", + "referenceNumber": 50, + "name": "Sun Industry Standards Source License v1.2", + "licenseId": "SISSL-1.2", + "seeAlso": [ + "http://gridscheduler.sourceforge.net/Gridengine_SISSL_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-Canada-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-Canada-2.0.json", + "referenceNumber": 51, + "name": "Open Government Licence - Canada", + "licenseId": "OGL-Canada-2.0", + "seeAlso": [ + "https://open.canada.ca/en/open-government-licence-canada" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-US.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-US.json", + "referenceNumber": 52, + "name": "Creative Commons Attribution 3.0 United States", + "licenseId": "CC-BY-3.0-US", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/us/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.1.json", + "referenceNumber": 53, + "name": "copyleft-next 0.3.1", + "licenseId": "copyleft-next-0.3.1", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.json", + "referenceNumber": 54, + "name": "GNU Free Documentation License v1.1 or later - invariants", + "licenseId": "GFDL-1.1-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GL2PS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GL2PS.json", + "referenceNumber": 55, + "name": "GL2PS License", + "licenseId": "GL2PS", + "seeAlso": [ + "http://www.geuz.org/gl2ps/COPYING.GL2PS" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-PL.json", + "referenceNumber": 56, + "name": "Microsoft Public License", + "licenseId": "MS-PL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-PL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SCEA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SCEA.json", + "referenceNumber": 57, + "name": "SCEA Shared Source License", + "licenseId": "SCEA", + "seeAlso": [ + "http://research.scea.com/scea_shared_source_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.5.json", + "referenceNumber": 58, + "name": "Creative Commons Attribution No Derivatives 2.5 Generic", + "licenseId": "CC-BY-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSPL-1.0.json", + "referenceNumber": 59, + "name": "Server Side Public License, v 1", + "licenseId": "SSPL-1.0", + "seeAlso": [ + "https://www.mongodb.com/licensing/server-side-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-86.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-86.json", + "referenceNumber": 60, + "name": "Spencer License 86", + "licenseId": "Spencer-86", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.0.json", + "referenceNumber": 61, + "name": "LaTeX Project Public License v1.0", + "licenseId": "LPPL-1.0", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-only.json", + "referenceNumber": 62, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.json", + "referenceNumber": 63, + "name": "GNU General Public License v2.0 w/Autoconf exception", + "licenseId": "GPL-2.0-with-autoconf-exception", + "seeAlso": [ + "http://ac-archive.sourceforge.net/doc/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Giftware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Giftware.json", + "referenceNumber": 64, + "name": "Giftware License", + "licenseId": "Giftware", + "seeAlso": [ + "http://liballeg.org/license.html#allegro-4-the-giftware-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.json", + "referenceNumber": 65, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", + "licenseId": "CC-BY-NC-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python.json", + "referenceNumber": 66, + "name": "CNRI Python License", + "licenseId": "CNRI-Python", + "seeAlso": [ + "https://opensource.org/licenses/CNRI-Python" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.json", + "referenceNumber": 67, + "name": "GNU Free Documentation License v1.2 or later - no invariants", + "licenseId": "GFDL-1.2-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Afmparse.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Afmparse.json", + "referenceNumber": 68, + "name": "Afmparse License", + "licenseId": "Afmparse", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Afmparse" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-LBNL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-LBNL.json", + "referenceNumber": 69, + "name": "Lawrence Berkeley National Labs BSD variant license", + "licenseId": "BSD-3-Clause-LBNL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/LBNLBSD" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NCGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCGL-UK-2.0.json", + "referenceNumber": 70, + "name": "Non-Commercial Government Licence", + "licenseId": "NCGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/non-commercial-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0+.json", + "referenceNumber": 71, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PHP-3.01.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.01.json", + "referenceNumber": 72, + "name": "PHP License v3.01", + "licenseId": "PHP-3.01", + "seeAlso": [ + "http://www.php.net/license/3_01.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Leptonica.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Leptonica.json", + "referenceNumber": 73, + "name": "Leptonica License", + "licenseId": "Leptonica", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Leptonica" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/bzip2-1.0.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.5.json", + "referenceNumber": 74, + "name": "bzip2 and libbzip2 License v1.0.5", + "licenseId": "bzip2-1.0.5", + "seeAlso": [ + "https://sourceware.org/bzip2/1.0.5/bzip2-manual-1.0.5.html", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NIST-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD-fallback.json", + "referenceNumber": 75, + "name": "NIST Public Domain Notice with license fallback", + "licenseId": "NIST-PD-fallback", + "seeAlso": [ + "https://github.com/usnistgov/jsip/blob/59700e6926cbe96c5cdae897d9a7d2656b42abe3/LICENSE", + "https://github.com/usnistgov/fipy/blob/86aaa5c2ba2c6f1be19593c5986071cf6568cc34/LICENSE.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.0.json", + "referenceNumber": 76, + "name": "Open Software License 1.0", + "licenseId": "OSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/OSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1.json", + "referenceNumber": 77, + "name": "SIL Open Font License 1.1", + "licenseId": "OFL-1.1", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/JasPer-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JasPer-2.0.json", + "referenceNumber": 78, + "name": "JasPer License", + "licenseId": "JasPer-2.0", + "seeAlso": [ + "http://www.ece.uvic.ca/~mdadams/jasper/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Naumen.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Naumen.json", + "referenceNumber": 79, + "name": "Naumen Public License", + "licenseId": "Naumen", + "seeAlso": [ + "https://opensource.org/licenses/Naumen" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-only.json", + "referenceNumber": 80, + "name": "Affero General Public License v1.0 only", + "licenseId": "AGPL-1.0-only", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/C-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/C-UDA-1.0.json", + "referenceNumber": 81, + "name": "Computational Use of Data Agreement v1.0", + "licenseId": "C-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Computational-Use-of-Data-Agreement/blob/master/C-UDA-1.0.md", + "https://cdla.dev/computational-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT.json", + "referenceNumber": 82, + "name": "MIT License", + "licenseId": "MIT", + "seeAlso": [ + "https://opensource.org/licenses/MIT" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/TCL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCL.json", + "referenceNumber": 83, + "name": "TCL/TK License", + "licenseId": "TCL", + "seeAlso": [ + "http://www.tcl.tk/software/tcltk/license.html", + "https://fedoraproject.org/wiki/Licensing/TCL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-only.json", + "referenceNumber": 84, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ECL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-1.0.json", + "referenceNumber": 85, + "name": "Educational Community License v1.0", + "licenseId": "ECL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0.json", + "referenceNumber": 86, + "name": "Mozilla Public License 2.0", + "licenseId": "MPL-2.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-1.0.json", + "referenceNumber": 87, + "name": "Creative Commons Attribution Non Commercial 1.0 Generic", + "licenseId": "CC-BY-NC-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.json", + "referenceNumber": 88, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", + "licenseId": "CC-BY-NC-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3c.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3c.json", + "referenceNumber": 89, + "name": "LaTeX Project Public License v1.3c", + "licenseId": "LPPL-1.3c", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3c.txt", + "https://opensource.org/licenses/LPPL-1.3c" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/JSON.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JSON.json", + "referenceNumber": 90, + "name": "JSON License", + "licenseId": "JSON", + "seeAlso": [ + "http://www.json.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NBPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NBPL-1.0.json", + "referenceNumber": 91, + "name": "Net Boolean Public License v1", + "licenseId": "NBPL-1.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d37b4b3f6cc4bf34e1d3dec61e69914b9819d8894" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.json", + "referenceNumber": 92, + "name": "Cryptographic Autonomy License 1.0 (Combined Work Exception)", + "licenseId": "CAL-1.0-Combined-Work-Exception", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Unlicense.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unlicense.json", + "referenceNumber": 93, + "name": "The Unlicense", + "licenseId": "Unlicense", + "seeAlso": [ + "https://unlicense.org/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.json", + "referenceNumber": 94, + "name": "CNRI Python Open Source GPL Compatible License Agreement", + "licenseId": "CNRI-Python-GPL-Compatible", + "seeAlso": [ + "http://www.python.org/download/releases/1.6.1/download_win/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-2.0.json", + "referenceNumber": 95, + "name": "Technische Universitaet Berlin License 2.0", + "licenseId": "TU-Berlin-2.0", + "seeAlso": [ + "https://github.com/CorsixTH/deps/blob/fd339a9f526d1d9c9f01ccf39e438a015da50035/licences/libgsm.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLPL.json", + "referenceNumber": 96, + "name": "No Limit Public License", + "licenseId": "NLPL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/NLPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-or-later.json", + "referenceNumber": 97, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Beerware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Beerware.json", + "referenceNumber": 98, + "name": "Beerware License", + "licenseId": "Beerware", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Beerware", + "https://people.freebsd.org/~phk/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NGPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NGPL.json", + "referenceNumber": 99, + "name": "Nethack General Public License", + "licenseId": "NGPL", + "seeAlso": [ + "https://opensource.org/licenses/NGPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.1.json", + "referenceNumber": 100, + "name": "Zope Public License 2.1", + "licenseId": "ZPL-2.1", + "seeAlso": [ + "http://old.zope.org/Resources/ZPL/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Saxpath.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Saxpath.json", + "referenceNumber": 101, + "name": "Saxpath License", + "licenseId": "Saxpath", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Saxpath_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.json", + "referenceNumber": 102, + "name": "Creative Commons Attribution Share Alike 2.0 England and Wales", + "licenseId": "CC-BY-SA-2.0-UK", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/uk/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.1.json", + "referenceNumber": 103, + "name": "CeCILL Free Software License Agreement v2.1", + "licenseId": "CECILL-2.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/XFree86-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XFree86-1.1.json", + "referenceNumber": 104, + "name": "XFree86 License 1.1", + "licenseId": "XFree86-1.1", + "seeAlso": [ + "http://www.xfree86.org/current/LICENSE4.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/IBM-pibs.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IBM-pibs.json", + "referenceNumber": 105, + "name": "IBM PowerPC Initialization and Boot Software", + "licenseId": "IBM-pibs", + "seeAlso": [ + "http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003darch/powerpc/cpu/ppc4xx/miiphy.c;h\u003d297155fdafa064b955e53e9832de93bfb0cfb85b;hb\u003d9fab4bf4cc077c21e43941866f3f2c196f28670d" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zlib.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zlib.json", + "referenceNumber": 106, + "name": "zlib License", + "licenseId": "Zlib", + "seeAlso": [ + "http://www.zlib.net/zlib_license.html", + "https://opensource.org/licenses/Zlib" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/StandardML-NJ.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/StandardML-NJ.json", + "referenceNumber": 107, + "name": "Standard ML of New Jersey License", + "licenseId": "StandardML-NJ", + "seeAlso": [ + "http://www.smlnj.org//license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPSL-1.0.json", + "referenceNumber": 108, + "name": "RealNetworks Public Source License v1.0", + "licenseId": "RPSL-1.0", + "seeAlso": [ + "https://helixcommunity.org/content/rpsl", + "https://opensource.org/licenses/RPSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.0.json", + "referenceNumber": 109, + "name": "CeCILL Free Software License Agreement v1.0", + "licenseId": "CECILL-1.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1-fr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-3.0.json", + "referenceNumber": 110, + "name": "Open Government Licence v3.0", + "licenseId": "OGL-UK-3.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-Shortened.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-Shortened.json", + "referenceNumber": 111, + "name": "BSD 4 Clause Shortened", + "licenseId": "BSD-4-Clause-Shortened", + "seeAlso": [ + "https://metadata.ftp-master.debian.org/changelogs//main/a/arpwatch/arpwatch_2.1a15-7_copyright" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Watcom-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Watcom-1.0.json", + "referenceNumber": 112, + "name": "Sybase Open Watcom Public License 1.0", + "licenseId": "Watcom-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Watcom-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Wsuipa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Wsuipa.json", + "referenceNumber": 113, + "name": "Wsuipa License", + "licenseId": "Wsuipa", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Wsuipa" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-1.0.json", + "referenceNumber": 114, + "name": "Technische Universitaet Berlin License 1.0", + "licenseId": "TU-Berlin-1.0", + "seeAlso": [ + "https://github.com/swh/ladspa/blob/7bf6f3799fdba70fda297c2d8fd9f526803d9680/gsm/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Latex2e.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Latex2e.json", + "referenceNumber": 115, + "name": "Latex2e License", + "licenseId": "Latex2e", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Latex2e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-B.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-B.json", + "referenceNumber": 116, + "name": "CeCILL-B Free Software License Agreement", + "licenseId": "CECILL-B", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.0.json", + "referenceNumber": 117, + "name": "European Union Public License 1.0", + "licenseId": "EUPL-1.0", + "seeAlso": [ + "http://ec.europa.eu/idabc/en/document/7330.html", + "http://ec.europa.eu/idabc/servlets/Doc027f.pdf?id\u003d31096" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-or-later.json", + "referenceNumber": 118, + "name": "GNU Free Documentation License v1.2 or later", + "licenseId": "GFDL-1.2-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPL-1.0.json", + "referenceNumber": 119, + "name": "Common Public License 1.0", + "licenseId": "CPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-3.0.json", + "referenceNumber": 120, + "name": "Creative Commons Attribution No Derivatives 3.0 Unported", + "licenseId": "CC-BY-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NTP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP.json", + "referenceNumber": 121, + "name": "NTP License", + "licenseId": "NTP", + "seeAlso": [ + "https://opensource.org/licenses/NTP" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/W3C-19980720.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-19980720.json", + "referenceNumber": 122, + "name": "W3C Software Notice and License (1998-07-20)", + "licenseId": "W3C-19980720", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/copyright-software-19980720.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-only.json", + "referenceNumber": 123, + "name": "GNU Free Documentation License v1.3 only", + "licenseId": "GFDL-1.3-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-4.0.json", + "referenceNumber": 124, + "name": "Creative Commons Attribution Share Alike 4.0 International", + "licenseId": "CC-BY-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.1.json", + "referenceNumber": 125, + "name": "European Union Public License 1.1", + "licenseId": "EUPL-1.1", + "seeAlso": [ + "https://joinup.ec.europa.eu/software/page/eupl/licence-eupl", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl1.1.-licence-en_0.pdf", + "https://opensource.org/licenses/EUPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.json", + "referenceNumber": 126, + "name": "GNU Free Documentation License v1.1 only - no invariants", + "licenseId": "GFDL-1.1-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JPNIC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JPNIC.json", + "referenceNumber": 127, + "name": "Japan Network Information Center License", + "licenseId": "JPNIC", + "seeAlso": [ + "https://gitlab.isc.org/isc-projects/bind9/blob/master/COPYRIGHT#L366" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AMPAS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMPAS.json", + "referenceNumber": 128, + "name": "Academy of Motion Picture Arts and Sciences BSD", + "licenseId": "AMPAS", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD#AMPASBSD" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause.json", + "referenceNumber": 129, + "name": "BSD 3-Clause \"New\" or \"Revised\" License", + "licenseId": "BSD-3-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-3-Clause" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-0.json", + "referenceNumber": 130, + "name": "MIT No Attribution", + "licenseId": "MIT-0", + "seeAlso": [ + "https://github.com/aws/mit-0", + "https://romanrm.net/mit-zero", + "https://github.com/awsdocs/aws-cloud9-user-guide/blob/master/LICENSE-SAMPLECODE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Intel.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel.json", + "referenceNumber": 131, + "name": "Intel Open Source License", + "licenseId": "Intel", + "seeAlso": [ + "https://opensource.org/licenses/Intel" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/O-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/O-UDA-1.0.json", + "referenceNumber": 132, + "name": "Open Use of Data Agreement v1.0", + "licenseId": "O-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Open-Use-of-Data-Agreement/blob/v1.0/O-UDA-1.0.md", + "https://cdla.dev/open-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.0.json", + "referenceNumber": 133, + "name": "Netscape Public License v1.0", + "licenseId": "NPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.5.json", + "referenceNumber": 134, + "name": "Creative Commons Attribution Non Commercial 2.5 Generic", + "licenseId": "CC-BY-NC-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Mup.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Mup.json", + "referenceNumber": 135, + "name": "Mup License", + "licenseId": "Mup", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Mup" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Newsletr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Newsletr.json", + "referenceNumber": 136, + "name": "Newsletr License", + "licenseId": "Newsletr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Newsletr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PDDL-1.0.json", + "referenceNumber": 137, + "name": "Open Data Commons Public Domain Dedication \u0026 License 1.0", + "licenseId": "PDDL-1.0", + "seeAlso": [ + "http://opendatacommons.org/licenses/pddl/1.0/", + "https://opendatacommons.org/licenses/pddl/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SMLNJ.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMLNJ.json", + "referenceNumber": 138, + "name": "Standard ML of New Jersey License", + "licenseId": "SMLNJ", + "seeAlso": [ + "https://www.smlnj.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-1-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-1-Clause.json", + "referenceNumber": 139, + "name": "BSD 1-Clause License", + "licenseId": "BSD-1-Clause", + "seeAlso": [ + "https://svnweb.freebsd.org/base/head/include/ifaddrs.h?revision\u003d326823" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SimPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SimPL-2.0.json", + "referenceNumber": 140, + "name": "Simple Public License 2.0", + "licenseId": "SimPL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/SimPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.2.json", + "referenceNumber": 141, + "name": "Open LDAP Public License v1.2", + "licenseId": "OLDAP-1.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d42b0383c50c299977b5893ee695cf4e486fb0dc7" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xnet.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xnet.json", + "referenceNumber": 142, + "name": "X.Net License", + "licenseId": "Xnet", + "seeAlso": [ + "https://opensource.org/licenses/Xnet" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause.json", + "referenceNumber": 143, + "name": "BSD 2-Clause \"Simplified\" License", + "licenseId": "BSD-2-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-2-Clause" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AML.json", + "referenceNumber": 144, + "name": "Apple MIT License", + "licenseId": "AML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_MIT_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-only.json", + "referenceNumber": 145, + "name": "GNU Free Documentation License v1.2 only", + "licenseId": "GFDL-1.2-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Info-ZIP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Info-ZIP.json", + "referenceNumber": 146, + "name": "Info-ZIP License", + "licenseId": "Info-ZIP", + "seeAlso": [ + "http://www.info-zip.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DSDP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DSDP.json", + "referenceNumber": 147, + "name": "DSDP License", + "licenseId": "DSDP", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/DSDP" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0.json", + "referenceNumber": 148, + "name": "Affero General Public License v1.0", + "licenseId": "AGPL-1.0", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-UC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-UC.json", + "referenceNumber": 149, + "name": "BSD-4-Clause (University of California-Specific)", + "licenseId": "BSD-4-Clause-UC", + "seeAlso": [ + "http://www.freebsd.org/copyright/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-only.json", + "referenceNumber": 150, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0.json", + "referenceNumber": 151, + "name": "SIL Open Font License 1.0", + "licenseId": "OFL-1.0", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDL-1.0.json", + "referenceNumber": 152, + "name": "Common Documentation License 1.0", + "licenseId": "CDL-1.0", + "seeAlso": [ + "http://www.opensource.apple.com/cdl/", + "https://fedoraproject.org/wiki/Licensing/Common_Documentation_License", + "https://www.gnu.org/licenses/license-list.html#ACDL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.3.json", + "referenceNumber": 153, + "name": "Licence Art Libre 1.3", + "licenseId": "LAL-1.3", + "seeAlso": [ + "https://artlibre.org/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sendmail.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail.json", + "referenceNumber": 154, + "name": "Sendmail License", + "licenseId": "Sendmail", + "seeAlso": [ + "http://www.sendmail.com/pdfs/open_source/sendmail_license.pdf", + "https://web.archive.org/web/20160322142305/https://www.sendmail.com/pdfs/open_source/sendmail_license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGDL-Taiwan-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGDL-Taiwan-1.0.json", + "referenceNumber": 155, + "name": "Taiwan Open Government Data License, version 1.0", + "licenseId": "OGDL-Taiwan-1.0", + "seeAlso": [ + "https://data.gov.tw/license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.4.json", + "referenceNumber": 156, + "name": "Zimbra Public License v1.4", + "licenseId": "Zimbra-1.4", + "seeAlso": [ + "http://www.zimbra.com/legal/zimbra-public-license-1-4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Borceux.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Borceux.json", + "referenceNumber": 157, + "name": "Borceux license", + "licenseId": "Borceux", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Borceux" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-3.0.json", + "referenceNumber": 158, + "name": "Open Software License 3.0", + "licenseId": "OSL-3.0", + "seeAlso": [ + "https://web.archive.org/web/20120101081418/http://rosenlaw.com:80/OSL3.0.htm", + "https://opensource.org/licenses/OSL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AMDPLPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMDPLPA.json", + "referenceNumber": 159, + "name": "AMD\u0027s plpa_map.c License", + "licenseId": "AMDPLPA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AMD_plpa_map_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.json", + "referenceNumber": 160, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", + "licenseId": "CC-BY-NC-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.1.json", + "referenceNumber": 161, + "name": "Open LDAP Public License v2.1", + "licenseId": "OLDAP-2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db0d176738e96a0d3b9f85cb51e140a86f21be715" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.json", + "referenceNumber": 162, + "name": "BSD 2-Clause FreeBSD License", + "licenseId": "BSD-2-Clause-FreeBSD", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CPOL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPOL-1.02.json", + "referenceNumber": 163, + "name": "Code Project Open License 1.02", + "licenseId": "CPOL-1.02", + "seeAlso": [ + "http://www.codeproject.com/info/cpol10.aspx" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.0.json", + "referenceNumber": 164, + "name": "Mozilla Public License 1.0", + "licenseId": "MPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.0.html", + "https://opensource.org/licenses/MPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/blessing.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/blessing.json", + "referenceNumber": 165, + "name": "SQLite Blessing", + "licenseId": "blessing", + "seeAlso": [ + "https://www.sqlite.org/src/artifact/e33a4df7e32d742a?ln\u003d4-9", + "https://sqlite.org/src/artifact/df5091916dbb40e6" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-6.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-6.0.0.json", + "referenceNumber": 166, + "name": "The Parity Public License 6.0.0", + "licenseId": "Parity-6.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/6.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-3.0.json", + "referenceNumber": 167, + "name": "Academic Free License v3.0", + "licenseId": "AFL-3.0", + "seeAlso": [ + "http://www.rosenlaw.com/AFL3.0.htm", + "https://opensource.org/licenses/afl-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.0.json", + "referenceNumber": 168, + "name": "SGI Free Software License B v1.0", + "licenseId": "SGI-B-1.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Patent.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Patent.json", + "referenceNumber": 169, + "name": "BSD-2-Clause Plus Patent License", + "licenseId": "BSD-2-Clause-Patent", + "seeAlso": [ + "https://opensource.org/licenses/BSDplusPatent" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-cl8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-cl8.json", + "referenceNumber": 170, + "name": "Artistic License 1.0 w/clause 8", + "licenseId": "Artistic-1.0-cl8", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.json", + "referenceNumber": 171, + "name": "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", + "licenseId": "CC-BY-NC-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Apache-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.1.json", + "referenceNumber": 172, + "name": "Apache License 1.1", + "licenseId": "Apache-1.1", + "seeAlso": [ + "http://apache.org/licenses/LICENSE-1.1", + "https://opensource.org/licenses/Apache-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ErlPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ErlPL-1.1.json", + "referenceNumber": 173, + "name": "Erlang Public License v1.1", + "licenseId": "ErlPL-1.1", + "seeAlso": [ + "http://www.erlang.org/EPLICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-RFN.json", + "referenceNumber": 174, + "name": "SIL Open Font License 1.0 with Reserved Font Name", + "licenseId": "OFL-1.0-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-3.0.json", + "referenceNumber": 175, + "name": "Creative Commons Attribution Non Commercial 3.0 Unported", + "licenseId": "CC-BY-NC-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.0.json", + "referenceNumber": 176, + "name": "Creative Commons Attribution Non Commercial 2.0 Generic", + "licenseId": "CC-BY-NC-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MakeIndex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MakeIndex.json", + "referenceNumber": 177, + "name": "MakeIndex License", + "licenseId": "MakeIndex", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MakeIndex" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Barr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Barr.json", + "referenceNumber": 178, + "name": "Barr License", + "licenseId": "Barr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Barr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.json", + "referenceNumber": 179, + "name": "Creative Commons Attribution Share Alike 2.1 Japan", + "licenseId": "CC-BY-SA-2.1-JP", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.json", + "referenceNumber": 180, + "name": "GNU Free Documentation License v1.2 only - no invariants", + "licenseId": "GFDL-1.2-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Hippocratic-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Hippocratic-2.1.json", + "referenceNumber": 181, + "name": "Hippocratic License 2.1", + "licenseId": "Hippocratic-2.1", + "seeAlso": [ + "https://firstdonoharm.dev/version/2/1/license.html", + "https://github.com/EthicalSource/hippocratic-license/blob/58c0e646d64ff6fbee275bfe2b9492f914e3ab2a/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-2006.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-2006.json", + "referenceNumber": 182, + "name": "Adobe Systems Incorporated Source Code License Agreement", + "licenseId": "Adobe-2006", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobeLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.0.json", + "referenceNumber": 183, + "name": "Open Software License 2.0", + "licenseId": "OSL-2.0", + "seeAlso": [ + "http://web.archive.org/web/20041020171434/http://www.rosenlaw.com/osl2.0.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.json", + "referenceNumber": 184, + "name": "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + "licenseId": "CC-BY-NC-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-or-later.json", + "referenceNumber": 185, + "name": "GNU Lesser General Public License v2.1 or later", + "licenseId": "LGPL-2.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.json", + "referenceNumber": 186, + "name": "PolyForm Noncommercial License 1.0.0", + "licenseId": "PolyForm-Noncommercial-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/noncommercial/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OpenSSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenSSL.json", + "referenceNumber": 187, + "name": "OpenSSL License", + "licenseId": "OpenSSL", + "seeAlso": [ + "http://www.openssl.org/source/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.json", + "referenceNumber": 188, + "name": "GNU General Public License v3.0 w/GCC Runtime Library exception", + "licenseId": "GPL-3.0-with-GCC-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gcc-exception-3.1.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPL-1.0.json", + "referenceNumber": 189, + "name": "Open Public License v1.0", + "licenseId": "OPL-1.0", + "seeAlso": [ + "http://old.koalateam.com/jackaroo/OPL_1_0.TXT", + "https://fedoraproject.org/wiki/Licensing/Open_Public_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Attribution.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Attribution.json", + "referenceNumber": 190, + "name": "BSD with attribution", + "licenseId": "BSD-3-Clause-Attribution", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_with_Attribution" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Rdisc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Rdisc.json", + "referenceNumber": 191, + "name": "Rdisc License", + "licenseId": "Rdisc", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Rdisc_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-RL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-RL.json", + "referenceNumber": 192, + "name": "Microsoft Reciprocal License", + "licenseId": "MS-RL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-RL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EUDatagrid.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUDatagrid.json", + "referenceNumber": 193, + "name": "EU DataGrid Software License", + "licenseId": "EUDatagrid", + "seeAlso": [ + "http://eu-datagrid.web.cern.ch/eu-datagrid/license.html", + "https://opensource.org/licenses/EUDatagrid" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPLLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPLLR.json", + "referenceNumber": 194, + "name": "Lesser General Public License For Linguistic Resources", + "licenseId": "LGPLLR", + "seeAlso": [ + "http://www-igm.univ-mlv.fr/~unitex/lgpllr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.0.json", + "referenceNumber": 195, + "name": "Academic Free License v2.0", + "licenseId": "AFL-2.0", + "seeAlso": [ + "http://wayback.archive.org/web/20060924134533/http://www.opensource.org/licenses/afl-2.0.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-Modern-Variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Modern-Variant.json", + "referenceNumber": 196, + "name": "MIT License Modern Variant", + "licenseId": "MIT-Modern-Variant", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT#Modern_Variants", + "https://ptolemy.berkeley.edu/copyright.htm", + "https://pirlwww.lpl.arizona.edu/resources/guide/software/PerlTk/Tixlic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-only.json", + "referenceNumber": 197, + "name": "GNU Free Documentation License v1.3 only - invariants", + "licenseId": "GFDL-1.3-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-R-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-R-1.1.json", + "referenceNumber": 198, + "name": "Licence Libre du Québec – Réciprocité version 1.1", + "licenseId": "LiLiQ-R-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-R-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CDLA-Permissive-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Permissive-1.0.json", + "referenceNumber": 199, + "name": "Community Data License Agreement Permissive 1.0", + "licenseId": "CDLA-Permissive-1.0", + "seeAlso": [ + "https://cdla.io/permissive-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DRL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DRL-1.0.json", + "referenceNumber": 200, + "name": "Detection Rule License 1.0", + "licenseId": "DRL-1.0", + "seeAlso": [ + "https://github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Source-Code.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Source-Code.json", + "referenceNumber": 201, + "name": "BSD Source Code Attribution", + "licenseId": "BSD-Source-Code", + "seeAlso": [ + "https://github.com/robbiehanson/CocoaHTTPServer/blob/master/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.json", + "referenceNumber": 202, + "name": "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", + "licenseId": "CC-BY-NC-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd-nc/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GLWTPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GLWTPL.json", + "referenceNumber": 203, + "name": "Good Luck With That Public License", + "licenseId": "GLWTPL", + "seeAlso": [ + "https://github.com/me-shaon/GLWTPL/commit/da5f6bc734095efbacb442c0b31e33a65b9d6e85" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/VSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VSL-1.0.json", + "referenceNumber": 204, + "name": "Vovida Software License v1.0", + "licenseId": "VSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/VSL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CPAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPAL-1.0.json", + "referenceNumber": 205, + "name": "Common Public Attribution License 1.0", + "licenseId": "CPAL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPAL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/HaskellReport.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HaskellReport.json", + "referenceNumber": 206, + "name": "Haskell Language Report License", + "licenseId": "HaskellReport", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Haskell_Language_Report_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.1.json", + "referenceNumber": 207, + "name": "Apple Public Source License 1.1", + "licenseId": "APSL-1.1", + "seeAlso": [ + "http://www.opensource.apple.com/source/IOSerialFamily/IOSerialFamily-7/APPLE_LICENSE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-or-later.json", + "referenceNumber": 208, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Modification.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Modification.json", + "referenceNumber": 209, + "name": "BSD 3-Clause Modification", + "licenseId": "BSD-3-Clause-Modification", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:BSD#Modification_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.3.json", + "referenceNumber": 210, + "name": "Open LDAP Public License v2.3", + "licenseId": "OLDAP-2.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dd32cf54a32d581ab475d23c810b0a7fbaf8d63c3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-no-RFN.json", + "referenceNumber": 211, + "name": "SIL Open Font License 1.1 with no Reserved Font Name", + "licenseId": "OFL-1.1-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.0.json", + "referenceNumber": 212, + "name": "BitTorrent Open Source License v1.0", + "licenseId": "BitTorrent-1.0", + "seeAlso": [ + "http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/BitTorrent?r1\u003d1.1\u0026r2\u003d1.1.1.1\u0026diff_format\u003ds" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NRL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NRL.json", + "referenceNumber": 213, + "name": "NRL License", + "licenseId": "NRL", + "seeAlso": [ + "http://web.mit.edu/network/isakmp/nrllicense.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2.json", + "referenceNumber": 214, + "name": "GNU Free Documentation License v1.2", + "licenseId": "GFDL-1.2", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MirOS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MirOS.json", + "referenceNumber": 215, + "name": "The MirOS Licence", + "licenseId": "MirOS", + "seeAlso": [ + "https://opensource.org/licenses/MirOS" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Sleepycat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sleepycat.json", + "referenceNumber": 216, + "name": "Sleepycat License", + "licenseId": "Sleepycat", + "seeAlso": [ + "https://opensource.org/licenses/Sleepycat" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.1.json", + "referenceNumber": 217, + "name": "LaTeX Project Public License v1.1", + "licenseId": "LPPL-1.1", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/WTFPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/WTFPL.json", + "referenceNumber": 218, + "name": "Do What The F*ck You Want To Public License", + "licenseId": "WTFPL", + "seeAlso": [ + "http://www.wtfpl.net/about/", + "http://sam.zoy.org/wtfpl/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.json", + "referenceNumber": 219, + "name": "PolyForm Small Business License 1.0.0", + "licenseId": "PolyForm-Small-Business-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/small-business/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Caldera.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Caldera.json", + "referenceNumber": 220, + "name": "Caldera License", + "licenseId": "Caldera", + "seeAlso": [ + "http://www.lemis.com/grog/UNIX/ancient-source-all.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HTMLTIDY.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HTMLTIDY.json", + "referenceNumber": 221, + "name": "HTML Tidy License", + "licenseId": "HTMLTIDY", + "seeAlso": [ + "https://github.com/htacg/tidy-html5/blob/next/README/LICENSE.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SISSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL.json", + "referenceNumber": 222, + "name": "Sun Industry Standards Source License v1.1", + "licenseId": "SISSL", + "seeAlso": [ + "http://www.openoffice.org/licenses/sissl_license.html", + "https://opensource.org/licenses/SISSL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MITNFA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MITNFA.json", + "referenceNumber": 223, + "name": "MIT +no-false-attribs license", + "licenseId": "MITNFA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MITNFA" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/0BSD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/0BSD.json", + "referenceNumber": 224, + "name": "BSD Zero Clause License", + "licenseId": "0BSD", + "seeAlso": [ + "http://landley.net/toybox/license.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC0-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC0-1.0.json", + "referenceNumber": 225, + "name": "Creative Commons Zero v1.0 Universal", + "licenseId": "CC0-1.0", + "seeAlso": [ + "https://creativecommons.org/publicdomain/zero/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0+.json", + "referenceNumber": 226, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CDLA-Sharing-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Sharing-1.0.json", + "referenceNumber": 227, + "name": "Community Data License Agreement Sharing 1.0", + "licenseId": "CDLA-Sharing-1.0", + "seeAlso": [ + "https://cdla.io/sharing-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.json", + "referenceNumber": 228, + "name": "GNU General Public License v2.0 w/Bison exception", + "licenseId": "GPL-2.0-with-bison-exception", + "seeAlso": [ + "http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-2.0.json", + "referenceNumber": 229, + "name": "Eiffel Forum License v2.0", + "licenseId": "EFL-2.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/eiffel-forum-license-2.html", + "https://opensource.org/licenses/EFL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.1.json", + "referenceNumber": 230, + "name": "Academic Free License v1.1", + "licenseId": "AFL-1.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.1.txt", + "http://wayback.archive.org/web/20021004124254/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.0.json", + "referenceNumber": 231, + "name": "Creative Commons Attribution 2.0 Generic", + "licenseId": "CC-BY-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPL-1.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.5.json", + "referenceNumber": 232, + "name": "Reciprocal Public License 1.5", + "licenseId": "RPL-1.5", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.5" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-1.0.json", + "referenceNumber": 233, + "name": "Mulan Permissive Software License, Version 1", + "licenseId": "MulanPSL-1.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL/", + "https://github.com/yuwenlong/longphp/blob/25dfb70cc2a466dc4bb55ba30901cbce08d164b5/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0+.json", + "referenceNumber": 234, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-variant.json", + "referenceNumber": 235, + "name": "Historical Permission Notice and Disclaimer - sell variant", + "licenseId": "HPND-sell-variant", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sunrpc/auth_gss/gss_generic_token.c?h\u003dv4.19" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSH-OpenSSH.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-OpenSSH.json", + "referenceNumber": 236, + "name": "SSH OpenSSH license", + "licenseId": "SSH-OpenSSH", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/LICENCE#L10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.1.json", + "referenceNumber": 237, + "name": "Open LDAP Public License v1.1", + "licenseId": "OLDAP-1.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d806557a5ad59804ef3a44d5abfbe91d706b0791f" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.1.json", + "referenceNumber": 238, + "name": "BitTorrent Open Source License v1.1", + "licenseId": "BitTorrent-1.1", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BitTorrentOSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0.json", + "referenceNumber": 239, + "name": "Artistic License 1.0", + "licenseId": "Artistic-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SSH-short.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-short.json", + "referenceNumber": 240, + "name": "SSH short notice", + "licenseId": "SSH-short", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/pathnames.h", + "http://web.mit.edu/kolya/.f/root/athena.mit.edu/sipb.mit.edu/project/openssh/OldFiles/src/openssh-2.9.9p2/ssh-add.1", + "https://joinup.ec.europa.eu/svn/lesoll/trunk/italc/lib/src/dsa_key.cpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-AT.json", + "referenceNumber": 241, + "name": "Creative Commons Attribution 3.0 Austria", + "licenseId": "CC-BY-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-CMU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-CMU.json", + "referenceNumber": 242, + "name": "CMU License", + "licenseId": "MIT-CMU", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT?rd\u003dLicensing/MIT#CMU_Style", + "https://github.com/python-pillow/Pillow/blob/fffb426092c8db24a5f4b6df243a8a3c01fb63cd/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.json", + "referenceNumber": 243, + "name": "GNU Free Documentation License v1.3 or later - no invariants", + "licenseId": "GFDL-1.3-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TOSL.json", + "referenceNumber": 244, + "name": "Trusster Open Source License", + "licenseId": "TOSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TOSL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-open-group.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-open-group.json", + "referenceNumber": 245, + "name": "MIT Open Group variant", + "licenseId": "MIT-open-group", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/app/iceauth/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xvinfo/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xsetroot/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xauth/-/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.6.json", + "referenceNumber": 246, + "name": "Open LDAP Public License v2.6", + "licenseId": "OLDAP-2.6", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d1cae062821881f41b73012ba816434897abf4205" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-only.json", + "referenceNumber": 247, + "name": "GNU Free Documentation License v1.1 only", + "licenseId": "GFDL-1.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/FreeBSD-DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeBSD-DOC.json", + "referenceNumber": 248, + "name": "FreeBSD Documentation License", + "licenseId": "FreeBSD-DOC", + "seeAlso": [ + "https://www.freebsd.org/copyright/freebsd-doc-license/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0.json", + "referenceNumber": 249, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Fair.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Fair.json", + "referenceNumber": 250, + "name": "Fair License", + "licenseId": "Fair", + "seeAlso": [ + "http://fairlicense.org/", + "https://opensource.org/licenses/Fair" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.1.json", + "referenceNumber": 251, + "name": "CeCILL Free Software License Agreement v1.1", + "licenseId": "CECILL-1.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/QPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/QPL-1.0.json", + "referenceNumber": 252, + "name": "Q Public License 1.0", + "licenseId": "QPL-1.0", + "seeAlso": [ + "http://doc.qt.nokia.com/3.3/license.html", + "https://opensource.org/licenses/QPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DOC.json", + "referenceNumber": 253, + "name": "DOC License", + "licenseId": "DOC", + "seeAlso": [ + "http://www.cs.wustl.edu/~schmidt/ACE-copying.html", + "https://www.dre.vanderbilt.edu/~schmidt/ACE-copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.2.json", + "referenceNumber": 254, + "name": "Licence Art Libre 1.2", + "licenseId": "LAL-1.2", + "seeAlso": [ + "http://artlibre.org/licence/lal/licence-art-libre-12/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.02.json", + "referenceNumber": 255, + "name": "Lucent Public License v1.02", + "licenseId": "LPL-1.02", + "seeAlso": [ + "http://plan9.bell-labs.com/plan9/license.html", + "https://opensource.org/licenses/LPL-1.02" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-P-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-P-2.0.json", + "referenceNumber": 256, + "name": "CERN Open Hardware Licence Version 2 - Permissive", + "licenseId": "CERN-OHL-P-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/etalab-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/etalab-2.0.json", + "referenceNumber": 257, + "name": "Etalab Open License 2.0", + "licenseId": "etalab-2.0", + "seeAlso": [ + "https://github.com/DISIC/politique-de-contribution-open-source/blob/master/LICENSE.pdf", + "https://raw.githubusercontent.com/DISIC/politique-de-contribution-open-source/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FTL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FTL.json", + "referenceNumber": 258, + "name": "Freetype Project License", + "licenseId": "FTL", + "seeAlso": [ + "http://freetype.fis.uniroma2.it/FTL.TXT", + "http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT", + "http://gitlab.freedesktop.org/freetype/freetype/-/raw/master/docs/FTL.TXT" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Qhull.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Qhull.json", + "referenceNumber": 259, + "name": "Qhull License", + "licenseId": "Qhull", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Qhull" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Clear.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Clear.json", + "referenceNumber": 260, + "name": "BSD 3-Clause Clear License", + "licenseId": "BSD-3-Clause-Clear", + "seeAlso": [ + "http://labs.metacarta.com/license-explanation.html#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.json", + "referenceNumber": 261, + "name": "BSD 3-Clause No Military License", + "licenseId": "BSD-3-Clause-No-Military-License", + "seeAlso": [ + "https://gitlab.syncad.com/hive/dhive/-/blob/master/LICENSE", + "https://github.com/greymass/swift-eosio/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFAP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFAP.json", + "referenceNumber": 262, + "name": "FSF All Permissive License", + "licenseId": "FSFAP", + "seeAlso": [ + "https://www.gnu.org/prep/maintain/html_node/License-Notices-for-Other-Files.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/APL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APL-1.0.json", + "referenceNumber": 263, + "name": "Adaptive Public License 1.0", + "licenseId": "APL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/APL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.8.json", + "referenceNumber": 264, + "name": "Open LDAP Public License v2.8", + "licenseId": "OLDAP-2.8", + "seeAlso": [ + "http://www.openldap.org/software/release/license.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/TORQUE-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TORQUE-1.1.json", + "referenceNumber": 265, + "name": "TORQUE v2.5+ Software License v1.1", + "licenseId": "TORQUE-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TORQUEv1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sendmail-8.23.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail-8.23.json", + "referenceNumber": 266, + "name": "Sendmail License 8.23", + "licenseId": "Sendmail-8.23", + "seeAlso": [ + "https://www.proofpoint.com/sites/default/files/sendmail-license.pdf", + "https://web.archive.org/web/20181003101040/https://www.proofpoint.com/sites/default/files/sendmail-license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/diffmark.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/diffmark.json", + "referenceNumber": 267, + "name": "diffmark license", + "licenseId": "diffmark", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/diffmark" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Frameworx-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Frameworx-1.0.json", + "referenceNumber": 268, + "name": "Frameworx Open License 1.0", + "licenseId": "Frameworx-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Frameworx-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/zlib-acknowledgement.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/zlib-acknowledgement.json", + "referenceNumber": 269, + "name": "zlib/libpng License with Acknowledgement", + "licenseId": "zlib-acknowledgement", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/ZlibWithAcknowledgement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-1.0.json", + "referenceNumber": 270, + "name": "Eiffel Forum License v1.0", + "licenseId": "EFL-1.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/forum.txt", + "https://opensource.org/licenses/EFL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/IJG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IJG.json", + "referenceNumber": 271, + "name": "Independent JPEG Group License", + "licenseId": "IJG", + "seeAlso": [ + "http://dev.w3.org/cvsweb/Amaya/libjpeg/Attic/README?rev\u003d1.2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.json", + "referenceNumber": 272, + "name": "GNU Free Documentation License v1.3 only - no invariants", + "licenseId": "GFDL-1.3-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Noweb.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Noweb.json", + "referenceNumber": 273, + "name": "Noweb License", + "licenseId": "Noweb", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Noweb" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3.json", + "referenceNumber": 274, + "name": "GNU Free Documentation License v1.3", + "licenseId": "GFDL-1.3", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1.json", + "referenceNumber": 275, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/gSOAP-1.3b.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gSOAP-1.3b.json", + "referenceNumber": 276, + "name": "gSOAP Public License v1.3b", + "licenseId": "gSOAP-1.3b", + "seeAlso": [ + "http://www.cs.fsu.edu/~engelen/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-RFN.json", + "referenceNumber": 277, + "name": "SIL Open Font License 1.1 with Reserved Font Name", + "licenseId": "OFL-1.1-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.json", + "referenceNumber": 278, + "name": "GNU General Public License v3.0 w/Autoconf exception", + "licenseId": "GPL-3.0-with-autoconf-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/autoconf-exception-3.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.1.json", + "referenceNumber": 279, + "name": "CERN Open Hardware Licence v1.1", + "licenseId": "CERN-OHL-1.1", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.1.json", + "referenceNumber": 280, + "name": "Academic Free License v2.1", + "licenseId": "AFL-2.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-2.1.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-enna.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-enna.json", + "referenceNumber": 281, + "name": "enna License", + "licenseId": "MIT-enna", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#enna" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-Glyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-Glyph.json", + "referenceNumber": 282, + "name": "Adobe Glyph List License", + "licenseId": "Adobe-Glyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#AdobeGlyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-1.0.json", + "referenceNumber": 283, + "name": "Eclipse Public License 1.0", + "licenseId": "EPL-1.0", + "seeAlso": [ + "http://www.eclipse.org/legal/epl-v10.html", + "https://opensource.org/licenses/EPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Xerox.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xerox.json", + "referenceNumber": 284, + "name": "Xerox License", + "licenseId": "Xerox", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xerox" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.1.json", + "referenceNumber": 285, + "name": "Open LDAP Public License v2.0.1", + "licenseId": "OLDAP-2.0.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db6d68acd14e51ca3aab4428bf26522aa74873f0e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MTLL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MTLL.json", + "referenceNumber": 286, + "name": "Matrix Template Library License", + "licenseId": "MTLL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Matrix_Template_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ImageMagick.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ImageMagick.json", + "referenceNumber": 287, + "name": "ImageMagick License", + "licenseId": "ImageMagick", + "seeAlso": [ + "http://www.imagemagick.org/script/license.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psutils.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psutils.json", + "referenceNumber": 288, + "name": "psutils License", + "licenseId": "psutils", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psutils" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ClArtistic.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ClArtistic.json", + "referenceNumber": 289, + "name": "Clarified Artistic License", + "licenseId": "ClArtistic", + "seeAlso": [ + "http://gianluca.dellavedova.org/2011/01/03/clarified-artistic-license/", + "http://www.ncftp.com/ncftp/doc/LICENSE.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.json", + "referenceNumber": 290, + "name": "GNU Free Documentation License v1.3 or later - invariants", + "licenseId": "GFDL-1.3-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.2.json", + "referenceNumber": 291, + "name": "Apple Public Source License 1.2", + "licenseId": "APSL-1.2", + "seeAlso": [ + "http://www.samurajdata.se/opensource/mirror/licenses/apsl.php" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Apache-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-2.0.json", + "referenceNumber": 292, + "name": "Apache License 2.0", + "licenseId": "Apache-2.0", + "seeAlso": [ + "http://www.apache.org/licenses/LICENSE-2.0", + "https://opensource.org/licenses/Apache-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NIST-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD.json", + "referenceNumber": 293, + "name": "NIST Public Domain Notice", + "licenseId": "NIST-PD", + "seeAlso": [ + "https://github.com/tcheneau/simpleRPL/blob/e645e69e38dd4e3ccfeceb2db8cba05b7c2e0cd3/LICENSE.txt", + "https://github.com/tcheneau/Routing/blob/f09f46fcfe636107f22f2c98348188a65a135d98/README.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Libpng.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Libpng.json", + "referenceNumber": 294, + "name": "libpng License", + "licenseId": "Libpng", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TAPR-OHL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TAPR-OHL-1.0.json", + "referenceNumber": 295, + "name": "TAPR Open Hardware License v1.0", + "licenseId": "TAPR-OHL-1.0", + "seeAlso": [ + "https://www.tapr.org/OHL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ICU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ICU.json", + "referenceNumber": 296, + "name": "ICU License", + "licenseId": "ICU", + "seeAlso": [ + "http://source.icu-project.org/repos/icu/icu/trunk/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.5.json", + "referenceNumber": 297, + "name": "Creative Commons Attribution Share Alike 2.5 Generic", + "licenseId": "CC-BY-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-PDDC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-PDDC.json", + "referenceNumber": 298, + "name": "Creative Commons Public Domain Dedication and Certification", + "licenseId": "CC-PDDC", + "seeAlso": [ + "https://creativecommons.org/licenses/publicdomain/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-only.json", + "referenceNumber": 299, + "name": "GNU Affero General Public License v3.0 only", + "licenseId": "AGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.1.json", + "referenceNumber": 300, + "name": "Open Software License 1.1", + "licenseId": "OSL-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/OSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SugarCRM-1.1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SugarCRM-1.1.3.json", + "referenceNumber": 301, + "name": "SugarCRM Public License v1.1.3", + "licenseId": "SugarCRM-1.1.3", + "seeAlso": [ + "http://www.sugarcrm.com/crm/SPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FreeImage.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeImage.json", + "referenceNumber": 302, + "name": "FreeImage Public License v1.0", + "licenseId": "FreeImage", + "seeAlso": [ + "http://freeimage.sourceforge.net/freeimage-license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C-20150513.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-20150513.json", + "referenceNumber": 303, + "name": "W3C Software Notice and Document License (2015-05-13)", + "licenseId": "W3C-20150513", + "seeAlso": [ + "https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/D-FSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/D-FSL-1.0.json", + "referenceNumber": 304, + "name": "Deutsche Freie Software Lizenz", + "licenseId": "D-FSL-1.0", + "seeAlso": [ + "http://www.dipp.nrw.de/d-fsl/lizenzen/", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/de/D-FSL-1_0_de.txt", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/en/D-FSL-1_0_en.txt", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/deutsche-freie-software-lizenz", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/german-free-software-license", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_de.txt/at_download/file", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_en.txt/at_download/file" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RSA-MD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSA-MD.json", + "referenceNumber": 305, + "name": "RSA Message-Digest License", + "licenseId": "RSA-MD", + "seeAlso": [ + "http://www.faqs.org/rfcs/rfc1321.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.0.json", + "referenceNumber": 306, + "name": "Creative Commons Attribution No Derivatives 2.0 Generic", + "licenseId": "CC-BY-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.json", + "referenceNumber": 307, + "name": "GNU General Public License v2.0 w/GCC Runtime Library exception", + "licenseId": "GPL-2.0-with-GCC-exception", + "seeAlso": [ + "https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-or-later.json", + "referenceNumber": 308, + "name": "GNU Affero General Public License v3.0 or later", + "licenseId": "AGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-or-later.json", + "referenceNumber": 309, + "name": "Affero General Public License v1.0 or later", + "licenseId": "AGPL-1.0-or-later", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/iMatix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/iMatix.json", + "referenceNumber": 310, + "name": "iMatix Standard Function Library Agreement", + "licenseId": "iMatix", + "seeAlso": [ + "http://legacy.imatix.com/html/sfl/sfl4.htm#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Plexus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Plexus.json", + "referenceNumber": 311, + "name": "Plexus Classworlds License", + "licenseId": "Plexus", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Plexus_Classworlds_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-no-RFN.json", + "referenceNumber": 312, + "name": "SIL Open Font License 1.0 with no Reserved Font Name", + "licenseId": "OFL-1.0-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NAIST-2003.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NAIST-2003.json", + "referenceNumber": 313, + "name": "Nara Institute of Science and Technology License (2003)", + "licenseId": "NAIST-2003", + "seeAlso": [ + "https://enterprise.dejacode.com/licenses/public/naist-2003/#license-text", + "https://github.com/nodejs/node/blob/4a19cc8947b1bba2b2d27816ec3d0edf9b28e503/LICENSE#L343" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-feh.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-feh.json", + "referenceNumber": 314, + "name": "feh License", + "licenseId": "MIT-feh", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#feh" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ECL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-2.0.json", + "referenceNumber": 315, + "name": "Educational Community License v2.0", + "licenseId": "ECL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.5.json", + "referenceNumber": 316, + "name": "Creative Commons Attribution 2.5 Generic", + "licenseId": "CC-BY-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/XSkat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XSkat.json", + "referenceNumber": 317, + "name": "XSkat License", + "licenseId": "XSkat", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/XSkat_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-OpenIB.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-OpenIB.json", + "referenceNumber": 318, + "name": "Linux Kernel Variant of OpenIB.org license", + "licenseId": "Linux-OpenIB", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/infiniband/core/sa.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-99.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-99.json", + "referenceNumber": 319, + "name": "Spencer License 99", + "licenseId": "Spencer-99", + "seeAlso": [ + "http://www.opensource.apple.com/source/tcl/tcl-5/tcl/generic/regfronts.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.json", + "referenceNumber": 320, + "name": "BSD 3-Clause No Nuclear License 2014", + "licenseId": "BSD-3-Clause-No-Nuclear-License-2014", + "seeAlso": [ + "https://java.net/projects/javaeetutorial/pages/BerkeleyLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.json", + "referenceNumber": 321, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", + "licenseId": "CC-BY-NC-ND-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.json", + "referenceNumber": 322, + "name": "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", + "licenseId": "CC-BY-NC-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-font-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-font-exception.json", + "referenceNumber": 323, + "name": "GNU General Public License v2.0 w/Font exception", + "licenseId": "GPL-2.0-with-font-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-faq.html#FontException" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Crossword.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Crossword.json", + "referenceNumber": 324, + "name": "Crossword License", + "licenseId": "Crossword", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Crossword" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.2.json", + "referenceNumber": 325, + "name": "Open LDAP Public License 2.2.2", + "licenseId": "OLDAP-2.2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003ddf2cc1e21eb7c160695f5b7cffd6296c151ba188" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.json", + "referenceNumber": 326, + "name": "BSD 2-Clause NetBSD License", + "licenseId": "BSD-2-Clause-NetBSD", + "seeAlso": [ + "http://www.netbsd.org/about/redistribution.html#default" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0+.json", + "referenceNumber": 327, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-4.0.json", + "referenceNumber": 328, + "name": "Creative Commons Attribution 4.0 International", + "licenseId": "CC-BY-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.json", + "referenceNumber": 329, + "name": "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", + "licenseId": "OLDAP-2.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcbf50f4e1185a21abd4c0a54d3f4341fe28f36ea" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NOSL.json", + "referenceNumber": 330, + "name": "Netizen Open Source License", + "licenseId": "NOSL", + "seeAlso": [ + "http://bits.netizen.com.au/licenses/NOSL/nosl.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.1.json", + "referenceNumber": 331, + "name": "Common Development and Distribution License 1.1", + "licenseId": "CDDL-1.1", + "seeAlso": [ + "http://glassfish.java.net/public/CDDL+GPL_1_1.html", + "https://javaee.github.io/glassfish/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.0.json", + "referenceNumber": 332, + "name": "Apple Public Source License 1.0", + "licenseId": "APSL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_Public_Source_License_1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.2.json", + "referenceNumber": 333, + "name": "European Union Public License 1.2", + "licenseId": "EUPL-1.2", + "seeAlso": [ + "https://joinup.ec.europa.eu/page/eupl-text-11-12", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/2020-03/EUPL-1.2%20EN.txt", + "https://joinup.ec.europa.eu/sites/default/files/inline-files/EUPL%20v1_2%20EN(1).txt", + "http://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri\u003dCELEX:32017D0863", + "https://opensource.org/licenses/EUPL-1.2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Nokia.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Nokia.json", + "referenceNumber": 334, + "name": "Nokia Open Source License", + "licenseId": "Nokia", + "seeAlso": [ + "https://opensource.org/licenses/nokia" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/RHeCos-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RHeCos-1.1.json", + "referenceNumber": 335, + "name": "Red Hat eCos Public License v1.1", + "licenseId": "RHeCos-1.1", + "seeAlso": [ + "http://ecos.sourceware.org/old-license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-only.json", + "referenceNumber": 336, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.7.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.7.json", + "referenceNumber": 337, + "name": "Open LDAP Public License v2.7", + "licenseId": "OLDAP-2.7", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d47c2415c1df81556eeb39be6cad458ef87c534a2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Vim.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Vim.json", + "referenceNumber": 338, + "name": "Vim License", + "licenseId": "Vim", + "seeAlso": [ + "http://vimdoc.sourceforge.net/htmldoc/uganda.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SAX-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SAX-PD.json", + "referenceNumber": 339, + "name": "Sax Public Domain Notice", + "licenseId": "SAX-PD", + "seeAlso": [ + "http://www.saxproject.org/copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.json", + "referenceNumber": 340, + "name": "BSD 3-Clause No Nuclear Warranty", + "licenseId": "BSD-3-Clause-No-Nuclear-Warranty", + "seeAlso": [ + "https://jogamp.org/git/?p\u003dgluegen.git;a\u003dblob_plain;f\u003dLICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NetCDF.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NetCDF.json", + "referenceNumber": 341, + "name": "NetCDF license", + "licenseId": "NetCDF", + "seeAlso": [ + "http://www.unidata.ucar.edu/software/netcdf/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/dvipdfm.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/dvipdfm.json", + "referenceNumber": 342, + "name": "dvipdfm License", + "licenseId": "dvipdfm", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/dvipdfm" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.5.json", + "referenceNumber": 343, + "name": "Solderpad Hardware License v0.5", + "licenseId": "SHL-0.5", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.5/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-only.json", + "referenceNumber": 344, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AAL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AAL.json", + "referenceNumber": 345, + "name": "Attribution Assurance License", + "licenseId": "AAL", + "seeAlso": [ + "https://opensource.org/licenses/attribution" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Unicode-TOU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-TOU.json", + "referenceNumber": 346, + "name": "Unicode Terms of Use", + "licenseId": "Unicode-TOU", + "seeAlso": [ + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.2.json", + "referenceNumber": 347, + "name": "LaTeX Project Public License v1.2", + "licenseId": "LPPL-1.2", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/xpp.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xpp.json", + "referenceNumber": 348, + "name": "XPP License", + "licenseId": "xpp", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/xpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.51.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.51.json", + "referenceNumber": 349, + "name": "Solderpad Hardware License, Version 0.51", + "licenseId": "SHL-0.51", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.51/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NCSA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCSA.json", + "referenceNumber": 350, + "name": "University of Illinois/NCSA Open Source License", + "licenseId": "NCSA", + "seeAlso": [ + "http://otm.illinois.edu/uiuc_openSource", + "https://opensource.org/licenses/NCSA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-or-later.json", + "referenceNumber": 351, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0.json", + "referenceNumber": 352, + "name": "Creative Commons Attribution 3.0 Unported", + "licenseId": "CC-BY-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0.json", + "referenceNumber": 353, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C.json", + "referenceNumber": 354, + "name": "W3C Software Notice and License (2002-12-31)", + "licenseId": "W3C", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html", + "https://opensource.org/licenses/W3C" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Aladdin.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Aladdin.json", + "referenceNumber": 355, + "name": "Aladdin Free Public License", + "licenseId": "Aladdin", + "seeAlso": [ + "http://pages.cs.wisc.edu/~ghost/doc/AFPL/6.01/Public.htm" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.json", + "referenceNumber": 356, + "name": "BSD 3-Clause No Nuclear License", + "licenseId": "BSD-3-Clause-No-Nuclear-License", + "seeAlso": [ + "http://download.oracle.com/otn-pub/java/licenses/bsd.txt?AuthParam\u003d1467140197_43d516ce1776bd08a58235a7785be1cc" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-or-later.json", + "referenceNumber": 357, + "name": "GNU Free Documentation License v1.1 or later", + "licenseId": "GFDL-1.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SMPPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMPPL.json", + "referenceNumber": 358, + "name": "Secure Messaging Protocol Public License", + "licenseId": "SMPPL", + "seeAlso": [ + "https://github.com/dcblake/SMP/blob/master/Documentation/License.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1.json", + "referenceNumber": 359, + "name": "GNU Free Documentation License v1.1", + "licenseId": "GFDL-1.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.4.json", + "referenceNumber": 360, + "name": "Open LDAP Public License v1.4", + "licenseId": "OLDAP-1.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dc9f95c2f3f2ffb5e0ae55fe7388af75547660941" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Condor-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Condor-1.1.json", + "referenceNumber": 361, + "name": "Condor Public License v1.1", + "licenseId": "Condor-1.1", + "seeAlso": [ + "http://research.cs.wisc.edu/condor/license.html#condor", + "http://web.archive.org/web/20111123062036/http://research.cs.wisc.edu/condor/license.html#condor" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-only.json", + "referenceNumber": 362, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0.json", + "referenceNumber": 363, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PSF-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PSF-2.0.json", + "referenceNumber": 364, + "name": "Python Software Foundation License 2.0", + "licenseId": "PSF-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Apache-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.0.json", + "referenceNumber": 365, + "name": "Apache License 1.0", + "licenseId": "Apache-1.0", + "seeAlso": [ + "http://www.apache.org/licenses/LICENSE-1.0" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-2.0.json", + "referenceNumber": 366, + "name": "Eclipse Public License 2.0", + "licenseId": "EPL-2.0", + "seeAlso": [ + "https://www.eclipse.org/legal/epl-2.0", + "https://www.opensource.org/licenses/EPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Python-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Python-2.0.json", + "referenceNumber": 367, + "name": "Python License 2.0", + "licenseId": "Python-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.4.json", + "referenceNumber": 368, + "name": "Open LDAP Public License v2.4", + "licenseId": "OLDAP-2.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcd1284c4a91a8a380d904eee68d1583f989ed386" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PostgreSQL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PostgreSQL.json", + "referenceNumber": 369, + "name": "PostgreSQL License", + "licenseId": "PostgreSQL", + "seeAlso": [ + "http://www.postgresql.org/about/licence", + "https://opensource.org/licenses/PostgreSQL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Net-SNMP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Net-SNMP.json", + "referenceNumber": 370, + "name": "Net-SNMP License", + "licenseId": "Net-SNMP", + "seeAlso": [ + "http://net-snmp.sourceforge.net/about/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Ruby.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Ruby.json", + "referenceNumber": 371, + "name": "Ruby License", + "licenseId": "Ruby", + "seeAlso": [ + "http://www.ruby-lang.org/en/LICENSE.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OSET-PL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSET-PL-2.1.json", + "referenceNumber": 372, + "name": "OSET Public License version 2.1", + "licenseId": "OSET-PL-2.1", + "seeAlso": [ + "http://www.osetfoundation.org/public-license", + "https://opensource.org/licenses/OPL-2.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Dotseqn.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Dotseqn.json", + "referenceNumber": 373, + "name": "Dotseqn License", + "licenseId": "Dotseqn", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Dotseqn" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CUA-OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CUA-OPL-1.0.json", + "referenceNumber": 374, + "name": "CUA Office Public License v1.0", + "licenseId": "CUA-OPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CUA-OPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Bahyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bahyph.json", + "referenceNumber": 375, + "name": "Bahyph License", + "licenseId": "Bahyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Bahyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.json", + "referenceNumber": 376, + "name": "Licence Libre du Québec – Réciprocité forte version 1.1", + "licenseId": "LiLiQ-Rplus-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-forte-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-Rplus-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0+.json", + "referenceNumber": 377, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/wxWindows.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/wxWindows.json", + "referenceNumber": 378, + "name": "wxWindows Library License", + "licenseId": "wxWindows", + "seeAlso": [ + "https://opensource.org/licenses/WXwindows" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0.json", + "referenceNumber": 379, + "name": "GNU Affero General Public License v3.0", + "licenseId": "AGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Abstyles.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Abstyles.json", + "referenceNumber": 380, + "name": "Abstyles License", + "licenseId": "Abstyles", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Abstyles" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.3.json", + "referenceNumber": 381, + "name": "Open LDAP Public License v1.3", + "licenseId": "OLDAP-1.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003de5f8117f0ce088d0bd7a8e18ddf37eaa40eb09b1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NTP-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP-0.json", + "referenceNumber": 382, + "name": "NTP No Attribution", + "licenseId": "NTP-0", + "seeAlso": [ + "https://github.com/tytso/e2fsprogs/blob/master/lib/et/et_name.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.json", + "referenceNumber": 383, + "name": "Open LDAP Public License v2.2", + "licenseId": "OLDAP-2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d470b0c18ec67621c85881b2733057fecf4a1acc3" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0.json", + "referenceNumber": 384, + "name": "Creative Commons Attribution Share Alike 3.0 Unported", + "licenseId": "CC-BY-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SWL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SWL.json", + "referenceNumber": 385, + "name": "Scheme Widget Library (SWL) Software License Agreement", + "licenseId": "SWL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SWL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.json", + "referenceNumber": 386, + "name": "BSD 3-Clause Open MPI variant", + "licenseId": "BSD-3-Clause-Open-MPI", + "seeAlso": [ + "https://www.open-mpi.org/community/license.php", + "http://www.netlib.org/lapack/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1+.json", + "referenceNumber": 387, + "name": "GNU Library General Public License v2.1 or later", + "licenseId": "LGPL-2.1+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-only.json", + "referenceNumber": 388, + "name": "GNU Free Documentation License v1.2 only - invariants", + "licenseId": "GFDL-1.2-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zend-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zend-2.0.json", + "referenceNumber": 389, + "name": "Zend License v2.0", + "licenseId": "Zend-2.0", + "seeAlso": [ + "https://web.archive.org/web/20130517195954/http://www.zend.com/license/2_00.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.json", + "referenceNumber": 390, + "name": "GNU Free Documentation License v1.1 or later - no invariants", + "licenseId": "GFDL-1.1-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mpich2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mpich2.json", + "referenceNumber": 391, + "name": "mpich2 License", + "licenseId": "mpich2", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLOD-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLOD-1.0.json", + "referenceNumber": 392, + "name": "Norwegian Licence for Open Government Data", + "licenseId": "NLOD-1.0", + "seeAlso": [ + "http://data.norge.no/nlod/en/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/gnuplot.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gnuplot.json", + "referenceNumber": 393, + "name": "gnuplot License", + "licenseId": "gnuplot", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Gnuplot" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-S-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-S-2.0.json", + "referenceNumber": 394, + "name": "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", + "licenseId": "CERN-OHL-S-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-2.0.json", + "referenceNumber": 395, + "name": "Open Government Licence v2.0", + "licenseId": "OGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.1.json", + "referenceNumber": 396, + "name": "Netscape Public License v1.1", + "licenseId": "NPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.1/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Zed.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zed.json", + "referenceNumber": 397, + "name": "Zed License", + "licenseId": "Zed", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Zed" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/VOSTROM.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VOSTROM.json", + "referenceNumber": 398, + "name": "VOSTROM Public License for Open Source", + "licenseId": "VOSTROM", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/VOSTROM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.0.json", + "referenceNumber": 399, + "name": "Zope Public License 2.0", + "licenseId": "ZPL-2.0", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-2.0", + "https://opensource.org/licenses/ZPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-W-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-W-2.0.json", + "referenceNumber": 400, + "name": "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", + "licenseId": "CERN-OHL-W-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.json", + "referenceNumber": 401, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", + "licenseId": "CC-BY-NC-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-2.0.json", + "referenceNumber": 402, + "name": "Apple Public Source License 2.0", + "licenseId": "APSL-2.0", + "seeAlso": [ + "http://www.opensource.apple.com/license/apsl/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.0.json", + "referenceNumber": 403, + "name": "Lucent Public License Version 1.0", + "licenseId": "LPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/LPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD-fallback.json", + "referenceNumber": 404, + "name": "ANTLR Software Rights Notice with license fallback", + "licenseId": "ANTLR-PD-fallback", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libtiff.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libtiff.json", + "referenceNumber": 405, + "name": "libtiff License", + "licenseId": "libtiff", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/libtiff" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND.json", + "referenceNumber": 406, + "name": "Historical Permission Notice and Disclaimer", + "licenseId": "HPND", + "seeAlso": [ + "https://opensource.org/licenses/HPND" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-or-later.json", + "referenceNumber": 407, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-2.0.json", + "referenceNumber": 408, + "name": "Artistic License 2.0", + "licenseId": "Artistic-2.0", + "seeAlso": [ + "http://www.perlfoundation.org/artistic_license_2_0", + "https://www.perlfoundation.org/artistic-license-20.html", + "https://opensource.org/licenses/artistic-license-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2015.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2015.json", + "referenceNumber": 409, + "name": "Unicode License Agreement - Data Files and Software (2015)", + "licenseId": "Unicode-DFS-2015", + "seeAlso": [ + "https://web.archive.org/web/20151224134844/http://unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-4.0.json", + "referenceNumber": 410, + "name": "Creative Commons Attribution Non Commercial 4.0 International", + "licenseId": "CC-BY-NC-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.1.json", + "referenceNumber": 411, + "name": "Reciprocal Public License 1.1", + "licenseId": "RPL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-1.0.json", + "referenceNumber": 412, + "name": "Creative Commons Attribution Share Alike 1.0 Generic", + "licenseId": "CC-BY-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Cube.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cube.json", + "referenceNumber": 413, + "name": "Cube License", + "licenseId": "Cube", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Cube" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ODC-By-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODC-By-1.0.json", + "referenceNumber": 414, + "name": "Open Data Commons Attribution License v1.0", + "licenseId": "ODC-By-1.0", + "seeAlso": [ + "https://opendatacommons.org/licenses/by/1.0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.0.json", + "referenceNumber": 415, + "name": "copyleft-next 0.3.0", + "licenseId": "copyleft-next-0.3.0", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-4.0.json", + "referenceNumber": 416, + "name": "Creative Commons Attribution No Derivatives 4.0 International", + "licenseId": "CC-BY-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ZPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-1.1.json", + "referenceNumber": 417, + "name": "Zope Public License 1.1", + "licenseId": "ZPL-1.1", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-or-later.json", + "referenceNumber": 418, + "name": "GNU Free Documentation License v1.3 or later", + "licenseId": "GFDL-1.3-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CATOSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CATOSL-1.1.json", + "referenceNumber": 419, + "name": "Computer Associates Trusted Open Source License 1.1", + "licenseId": "CATOSL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/CATOSL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.json", + "referenceNumber": 420, + "name": "GNU General Public License v2.0 w/Classpath exception", + "licenseId": "GPL-2.0-with-classpath-exception", + "seeAlso": [ + "https://www.gnu.org/software/classpath/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0.json", + "referenceNumber": 421, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Views.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Views.json", + "referenceNumber": 422, + "name": "BSD 2-Clause with views sentence", + "licenseId": "BSD-2-Clause-Views", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html", + "https://people.freebsd.org/~ivoras/wine/patch-wine-nvidia.sh", + "https://github.com/protegeproject/protege/blob/master/license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSL-1.0.json", + "referenceNumber": 423, + "name": "Boost Software License 1.0", + "licenseId": "BSL-1.0", + "seeAlso": [ + "http://www.boost.org/LICENSE_1_0.txt", + "https://opensource.org/licenses/BSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CNRI-Jython.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Jython.json", + "referenceNumber": 424, + "name": "CNRI Jython License", + "licenseId": "CNRI-Jython", + "seeAlso": [ + "http://www.jython.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Eurosym.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Eurosym.json", + "referenceNumber": 425, + "name": "Eurosym License", + "licenseId": "Eurosym", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Eurosym" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.json", + "referenceNumber": 426, + "name": "Creative Commons Attribution Share Alike 3.0 Austria", + "licenseId": "CC-BY-SA-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-C.json", + "referenceNumber": 427, + "name": "CeCILL-C Free Software License Agreement", + "licenseId": "CECILL-C", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/EPICS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPICS.json", + "referenceNumber": 428, + "name": "EPICS Open License", + "licenseId": "EPICS", + "seeAlso": [ + "https://epics.anl.gov/license/open.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.json", + "referenceNumber": 429, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", + "licenseId": "CC-BY-NC-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GD.json", + "referenceNumber": 430, + "name": "GD License", + "licenseId": "GD", + "seeAlso": [ + "https://libgd.github.io/manuals/2.3.0/files/license-txt.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/X11.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/X11.json", + "referenceNumber": 431, + "name": "X11 License", + "licenseId": "X11", + "seeAlso": [ + "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.1.json", + "referenceNumber": 432, + "name": "Mozilla Public License 1.1", + "licenseId": "MPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.1.html", + "https://opensource.org/licenses/MPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-only.json", + "referenceNumber": 433, + "name": "GNU Free Documentation License v1.1 only - invariants", + "licenseId": "GFDL-1.1-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psfrag.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psfrag.json", + "referenceNumber": 434, + "name": "psfrag License", + "licenseId": "psfrag", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psfrag" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RSCPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSCPL.json", + "referenceNumber": 435, + "name": "Ricoh Source Code Public License", + "licenseId": "RSCPL", + "seeAlso": [ + "http://wayback.archive.org/web/20060715140826/http://www.risource.org/RPL/RPL-1.0A.shtml", + "https://opensource.org/licenses/RSCPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/YPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.0.json", + "referenceNumber": 436, + "name": "Yahoo! Public License v1.0", + "licenseId": "YPL-1.0", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.1.json", + "referenceNumber": 437, + "name": "SGI Free Software License B v1.1", + "licenseId": "SGI-B-1.1", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-1.0.json", + "referenceNumber": 438, + "name": "Creative Commons Attribution No Derivatives 1.0 Generic", + "licenseId": "CC-BY-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-2.0.json", + "referenceNumber": 439, + "name": "SGI Free Software License B v2.0", + "licenseId": "SGI-B-2.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.2.0.pdf" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/APAFML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APAFML.json", + "referenceNumber": 440, + "name": "Adobe Postscript AFM License", + "licenseId": "APAFML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobePostscriptAFM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-94.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-94.json", + "referenceNumber": 441, + "name": "Spencer License 94", + "licenseId": "Spencer-94", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ISC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ISC.json", + "referenceNumber": 442, + "name": "ISC License", + "licenseId": "ISC", + "seeAlso": [ + "https://www.isc.org/downloads/software-support-policy/isc-license/", + "https://opensource.org/licenses/ISC" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-advertising.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-advertising.json", + "referenceNumber": 443, + "name": "Enlightenment License (e16)", + "licenseId": "MIT-advertising", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT_With_Advertising" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.json", + "referenceNumber": 444, + "name": "GNU Free Documentation License v1.2 or later - invariants", + "licenseId": "GFDL-1.2-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.json", + "referenceNumber": 445, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", + "licenseId": "CC-BY-NC-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-1.0.json", + "referenceNumber": 446, + "name": "Creative Commons Attribution 1.0 Generic", + "licenseId": "CC-BY-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.1.json", + "referenceNumber": 447, + "name": "Open Software License 2.1", + "licenseId": "OSL-2.1", + "seeAlso": [ + "http://web.archive.org/web/20050212003940/http://www.rosenlaw.com/osl21.htm", + "https://opensource.org/licenses/OSL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CrystalStacker.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CrystalStacker.json", + "referenceNumber": 448, + "name": "CrystalStacker License", + "licenseId": "CrystalStacker", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:CrystalStacker?rd\u003dLicensing/CrystalStacker" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFULLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFULLR.json", + "referenceNumber": 449, + "name": "FSF Unlimited License (with License Retention)", + "licenseId": "FSFULLR", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License#License_Retention_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libselinux-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libselinux-1.0.json", + "referenceNumber": 450, + "name": "libselinux public domain notice", + "licenseId": "libselinux-1.0", + "seeAlso": [ + "https://github.com/SELinuxProject/selinux/blob/master/libselinux/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-2.0.json", + "referenceNumber": 451, + "name": "Mulan Permissive Software License, Version 2", + "licenseId": "MulanPSL-2.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL2/" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0.json", + "referenceNumber": 452, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.5.json", + "referenceNumber": 453, + "name": "Open LDAP Public License v2.5", + "licenseId": "OLDAP-2.5", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d6852b9d90022e8593c98205413380536b1b5a7cf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-Perl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-Perl.json", + "referenceNumber": 454, + "name": "Artistic License 1.0 (Perl)", + "licenseId": "Artistic-1.0-Perl", + "seeAlso": [ + "http://dev.perl.org/licenses/artistic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AFL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.2.json", + "referenceNumber": 455, + "name": "Academic Free License v1.2", + "licenseId": "AFL-1.2", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.2.txt", + "http://wayback.archive.org/web/20021204204652/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0.json", + "referenceNumber": 456, + "name": "Cryptographic Autonomy License 1.0", + "licenseId": "CAL-1.0", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause.json", + "referenceNumber": 457, + "name": "BSD 4-Clause \"Original\" or \"Old\" License", + "licenseId": "BSD-4-Clause", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BSD_4Clause" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Interbase-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Interbase-1.0.json", + "referenceNumber": 458, + "name": "Interbase Public License v1.0", + "licenseId": "Interbase-1.0", + "seeAlso": [ + "https://web.archive.org/web/20060319014854/http://info.borland.com/devsupport/interbase/opensource/IPL.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPOSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPOSL-3.0.json", + "referenceNumber": 459, + "name": "Non-Profit Open Software License 3.0", + "licenseId": "NPOSL-3.0", + "seeAlso": [ + "https://opensource.org/licenses/NOSL3.0" + ], + "isOsiApproved": true + } + ], + "releaseDate": "2021-05-20" +}
\ No newline at end of file diff --git a/test/schemas/f b/test/schemas/f new file mode 120000 index 0000000..ae8ff29 --- /dev/null +++ b/test/schemas/f @@ -0,0 +1 @@ +../../src/ansiblelint/schemas
\ No newline at end of file diff --git a/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml new file mode 100644 index 0000000..2639e9a --- /dev/null +++ b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml @@ -0,0 +1,4 @@ +--- +releases: + 1.0.0: + release_date: 01-01-2020 # invalid date format, must be ISO-8601 ! diff --git a/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md new file mode 100644 index 0000000..72b4f96 --- /dev/null +++ b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md @@ -0,0 +1,40 @@ +# ajv errors + +```json +[ + { + "instancePath": "/releases/1.0.0/release_date", + "keyword": "pattern", + "message": "must match pattern \"\\d\\d\\d\\d-\\d\\d-\\d\\d\"", + "params": { + "pattern": "\\d\\d\\d\\d-\\d\\d-\\d\\d" + }, + "schemaPath": "#/properties/release_date/pattern" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/changelogs/invalid-date/changelogs/changelog.yaml", + "path": "$.releases.1.0.0.release_date", + "message": "'01-01-2020' is not a 'date'", + "has_sub_errors": false + }, + { + "filename": "negative_test/changelogs/invalid-date/changelogs/changelog.yaml", + "path": "$.releases.1.0.0.release_date", + "message": "'01-01-2020' does not match '\\\\d\\\\d\\\\d\\\\d-\\\\d\\\\d-\\\\d\\\\d'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml new file mode 100644 index 0000000..99632a4 --- /dev/null +++ b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml @@ -0,0 +1,8 @@ +--- +releases: + 1.0.0: + plugins: + lookup: + - name: reverse + description: Reverse magic + namespace: "foo" # namespace must be null for plugins and objects diff --git a/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md new file mode 100644 index 0000000..ef847c3 --- /dev/null +++ b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/releases/1.0.0/plugins/lookup/0/namespace", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/$defs/plugin-descriptions/items/properties/namespace/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml", + "path": "$.releases.1.0.0.plugins.lookup[0].namespace", + "message": "'foo' is not of type 'null'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml new file mode 100644 index 0000000..72def5b --- /dev/null +++ b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml @@ -0,0 +1,4 @@ +--- +- this is invalid +- as changelog must be object (mapping) +- not an array (sequence) diff --git a/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md new file mode 100644 index 0000000..5938944 --- /dev/null +++ b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/changelogs/list/changelogs/changelog.yaml", + "path": "$", + "message": "['this is invalid', 'as changelog must be object (mapping)', 'not an array (sequence)'] is not of type 'object'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml new file mode 100644 index 0000000..d08ebd0 --- /dev/null +++ b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml @@ -0,0 +1,2 @@ +--- +releases: foo # <-- not a semver diff --git a/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md new file mode 100644 index 0000000..64c4665 --- /dev/null +++ b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/releases", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/properties/releases/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/changelogs/no-semver/changelogs/changelog.yaml", + "path": "$.releases", + "message": "'foo' is not of type 'object'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml new file mode 100644 index 0000000..a97e4e2 --- /dev/null +++ b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml @@ -0,0 +1,2 @@ +--- +release: {} # <- unknown key, correct would be releases diff --git a/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md new file mode 100644 index 0000000..490bdbe --- /dev/null +++ b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "release" + }, + "schemaPath": "#/additionalProperties" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/changelogs/unknown-keys/changelogs/changelog.yaml", + "path": "$", + "message": "Additional properties are not allowed ('release' was unexpected)", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/inventory/broken_dev_inventory.yml b/test/schemas/negative_test/inventory/broken_dev_inventory.yml new file mode 100644 index 0000000..ce84309 --- /dev/null +++ b/test/schemas/negative_test/inventory/broken_dev_inventory.yml @@ -0,0 +1,10 @@ +--- +# See https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html +ungrouped: {} +all: + hosts: + mail.example.com: + children: + foo: {} # <-- invalid based on inventory json schema + vars: {} +webservers: {} diff --git a/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md b/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md new file mode 100644 index 0000000..d4fefaf --- /dev/null +++ b/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/all", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "foo" + }, + "schemaPath": "#/$defs/special-group/additionalProperties" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/inventory/broken_dev_inventory.yml", + "path": "$.all", + "message": "Additional properties are not allowed ('foo' was unexpected)", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/meta/runtime.yml b/test/schemas/negative_test/meta/runtime.yml new file mode 100644 index 0000000..c143dc6 --- /dev/null +++ b/test/schemas/negative_test/meta/runtime.yml @@ -0,0 +1 @@ +requires_ansible: ">= 2.12" # invalid as space is not allowed! diff --git a/test/schemas/negative_test/meta/runtime.yml.md b/test/schemas/negative_test/meta/runtime.yml.md new file mode 100644 index 0000000..761fa6f --- /dev/null +++ b/test/schemas/negative_test/meta/runtime.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/requires_ansible", + "keyword": "pattern", + "message": "must match pattern \"^[^\\s]*$\"", + "params": { + "pattern": "^[^\\s]*$" + }, + "schemaPath": "#/properties/requires_ansible/pattern" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/meta/runtime.yml", + "path": "$.requires_ansible", + "message": "'>= 2.12' does not match '^[^\\\\s]*$'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/molecule/platforms_children/molecule.yml b/test/schemas/negative_test/molecule/platforms_children/molecule.yml new file mode 100644 index 0000000..6800584 --- /dev/null +++ b/test/schemas/negative_test/molecule/platforms_children/molecule.yml @@ -0,0 +1,5 @@ +driver: + name: delegated +platforms: + - name: foo + children: 2 # invalid, must be list of strings diff --git a/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md b/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md new file mode 100644 index 0000000..68e09eb --- /dev/null +++ b/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/platforms/0/children", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/children/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/molecule/platforms_children/molecule.yml", + "path": "$.platforms[0].children", + "message": "2 is not of type 'array'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/molecule/platforms_networks/molecule.yml b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml new file mode 100644 index 0000000..4ae9799 --- /dev/null +++ b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml @@ -0,0 +1,7 @@ +driver: + name: docker +platforms: + - name: docker + networks: # invalid, must be list of dictionaries + - foo + - bar diff --git a/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md new file mode 100644 index 0000000..74b8de7 --- /dev/null +++ b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md @@ -0,0 +1,49 @@ +# ajv errors + +```json +[ + { + "instancePath": "/platforms/0/networks/0", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/$defs/platform-network/type" + }, + { + "instancePath": "/platforms/0/networks/1", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/$defs/platform-network/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/molecule/platforms_networks/molecule.yml", + "path": "$.platforms[0].networks[0]", + "message": "'foo' is not of type 'object'", + "has_sub_errors": false + }, + { + "filename": "negative_test/molecule/platforms_networks/molecule.yml", + "path": "$.platforms[0].networks[1]", + "message": "'bar' is not of type 'object'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/environment.yml b/test/schemas/negative_test/playbooks/environment.yml new file mode 100644 index 0000000..2064aca --- /dev/null +++ b/test/schemas/negative_test/playbooks/environment.yml @@ -0,0 +1,3 @@ +--- +- hosts: localhost + environment: "{{ foo }}-123" # <- invalid only a full jinja string is allowed, or a list of strings diff --git a/test/schemas/negative_test/playbooks/environment.yml.md b/test/schemas/negative_test/playbooks/environment.yml.md new file mode 100644 index 0000000..8923cb3 --- /dev/null +++ b/test/schemas/negative_test/playbooks/environment.yml.md @@ -0,0 +1,138 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "environment" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/environment", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/anyOf/0/type" + }, + { + "instancePath": "/0/environment", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/environment", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/environment.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'environment': '{{ foo }}-123'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'environment', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'environment', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'environment': '{{ foo }}-123'} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].environment", + "message": "'{{ foo }}-123' is not valid under any of the given schemas" + }, + { + "path": "$[0].environment", + "message": "'{{ foo }}-123' is not of type 'object'" + }, + { + "path": "$[0].environment", + "message": "'{{ foo }}-123' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/failed_when.yml b/test/schemas/negative_test/playbooks/failed_when.yml new file mode 100644 index 0000000..59b7272 --- /dev/null +++ b/test/schemas/negative_test/playbooks/failed_when.yml @@ -0,0 +1,6 @@ +- hosts: localhost + tasks: + - name: foo + ansible.builtin.debug: + msg: foo! + failed_when: 123 # <- not ok diff --git a/test/schemas/negative_test/playbooks/failed_when.yml.md b/test/schemas/negative_test/playbooks/failed_when.yml.md new file mode 100644 index 0000000..e843e1f --- /dev/null +++ b/test/schemas/negative_test/playbooks/failed_when.yml.md @@ -0,0 +1,177 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/failed_when.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'foo', 'ansible.builtin.debug': {'msg': 'foo!'}, 'failed_when': 123}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'foo', 'ansible.builtin.debug': {'msg': 'foo!'}, 'failed_when': 123}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'name': 'foo', 'ansible.builtin.debug': {'msg': 'foo!'}, 'failed_when': 123} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/gather_facts.yml b/test/schemas/negative_test/playbooks/gather_facts.yml new file mode 100644 index 0000000..d1b1345 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_facts.yml @@ -0,0 +1,6 @@ +--- +- hosts: localhost + gather_facts: non + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/negative_test/playbooks/gather_facts.yml.md b/test/schemas/negative_test/playbooks/gather_facts.yml.md new file mode 100644 index 0000000..0eb3a4b --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_facts.yml.md @@ -0,0 +1,123 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "gather_facts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/gather_facts", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/properties/gather_facts/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/gather_facts.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_facts': 'non', 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'gather_facts', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'gather_facts', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_facts': 'non', 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].gather_facts", + "message": "'non' is not of type 'boolean'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/gather_subset.yml b/test/schemas/negative_test/playbooks/gather_subset.yml new file mode 100644 index 0000000..455d683 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset.yml @@ -0,0 +1,6 @@ +--- +- hosts: localhost + gather_subset: all + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/negative_test/playbooks/gather_subset.yml.md b/test/schemas/negative_test/playbooks/gather_subset.yml.md new file mode 100644 index 0000000..b426a23 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset.yml.md @@ -0,0 +1,123 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "gather_subset" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/gather_subset", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/gather_subset/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/gather_subset.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': 'all', 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': 'all', 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].gather_subset", + "message": "'all' is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/gather_subset2.yml b/test/schemas/negative_test/playbooks/gather_subset2.yml new file mode 100644 index 0000000..d5a39ae --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset2.yml @@ -0,0 +1,7 @@ +--- +- hosts: localhost + gather_subset: + - invalid + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/negative_test/playbooks/gather_subset2.yml.md b/test/schemas/negative_test/playbooks/gather_subset2.yml.md new file mode 100644 index 0000000..8d6be68 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset2.yml.md @@ -0,0 +1,277 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "gather_subset" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "all", + "min", + "all_ipv4_addresses", + "all_ipv6_addresses", + "apparmor", + "architecture", + "caps", + "chroot,cmdline", + "date_time", + "default_ipv4", + "default_ipv6", + "devices", + "distribution", + "distribution_major_version", + "distribution_release", + "distribution_version", + "dns", + "effective_group_ids", + "effective_user_id", + "env", + "facter", + "fips", + "hardware", + "interfaces", + "is_chroot", + "iscsi", + "kernel", + "local", + "lsb", + "machine", + "machine_id", + "mounts", + "network", + "ohai", + "os_family", + "pkg_mgr", + "platform", + "processor", + "processor_cores", + "processor_count", + "python", + "python_version", + "real_user_id", + "selinux", + "service_mgr", + "ssh_host_key_dsa_public", + "ssh_host_key_ecdsa_public", + "ssh_host_key_ed25519_public", + "ssh_host_key_rsa_public", + "ssh_host_pub_keys", + "ssh_pub_keys", + "system", + "system_capabilities", + "system_capabilities_enforced", + "user", + "user_dir", + "user_gecos", + "user_gid", + "user_id", + "user_shell", + "user_uid", + "virtual", + "virtualization_role", + "virtualization_type" + ] + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/0/enum" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "!all", + "!min", + "!all_ipv4_addresses", + "!all_ipv6_addresses", + "!apparmor", + "!architecture", + "!caps", + "!chroot,cmdline", + "!date_time", + "!default_ipv4", + "!default_ipv6", + "!devices", + "!distribution", + "!distribution_major_version", + "!distribution_release", + "!distribution_version", + "!dns", + "!effective_group_ids", + "!effective_user_id", + "!env", + "!facter", + "!fips", + "!hardware", + "!interfaces", + "!is_chroot", + "!iscsi", + "!kernel", + "!local", + "!lsb", + "!machine", + "!machine_id", + "!mounts", + "!network", + "!ohai", + "!os_family", + "!pkg_mgr", + "!platform", + "!processor", + "!processor_cores", + "!processor_count", + "!python", + "!python_version", + "!real_user_id", + "!selinux", + "!service_mgr", + "!ssh_host_key_dsa_public", + "!ssh_host_key_ecdsa_public", + "!ssh_host_key_ed25519_public", + "!ssh_host_key_rsa_public", + "!ssh_host_pub_keys", + "!ssh_pub_keys", + "!system", + "!system_capabilities", + "!system_capabilities_enforced", + "!user", + "!user_dir", + "!user_gecos", + "!user_gid", + "!user_id", + "!user_shell", + "!user_uid", + "!virtual", + "!virtualization_role", + "!virtualization_type" + ] + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/1/enum" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/gather_subset/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/gather_subset2.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': ['invalid'], 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': ['invalid'], 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].gather_subset[0]", + "message": "'invalid' is not valid under any of the given schemas" + }, + { + "path": "$[0].gather_subset[0]", + "message": "'invalid' is not one of ['all', 'min', 'all_ipv4_addresses', 'all_ipv6_addresses', 'apparmor', 'architecture', 'caps', 'chroot,cmdline', 'date_time', 'default_ipv4', 'default_ipv6', 'devices', 'distribution', 'distribution_major_version', 'distribution_release', 'distribution_version', 'dns', 'effective_group_ids', 'effective_user_id', 'env', 'facter', 'fips', 'hardware', 'interfaces', 'is_chroot', 'iscsi', 'kernel', 'local', 'lsb', 'machine', 'machine_id', 'mounts', 'network', 'ohai', 'os_family', 'pkg_mgr', 'platform', 'processor', 'processor_cores', 'processor_count', 'python', 'python_version', 'real_user_id', 'selinux', 'service_mgr', 'ssh_host_key_dsa_public', 'ssh_host_key_ecdsa_public', 'ssh_host_key_ed25519_public', 'ssh_host_key_rsa_public', 'ssh_host_pub_keys', 'ssh_pub_keys', 'system', 'system_capabilities', 'system_capabilities_enforced', 'user', 'user_dir', 'user_gecos', 'user_gid', 'user_id', 'user_shell', 'user_uid', 'virtual', 'virtualization_role', 'virtualization_type']" + }, + { + "path": "$[0].gather_subset[0]", + "message": "'invalid' is not one of ['!all', '!min', '!all_ipv4_addresses', '!all_ipv6_addresses', '!apparmor', '!architecture', '!caps', '!chroot,cmdline', '!date_time', '!default_ipv4', '!default_ipv6', '!devices', '!distribution', '!distribution_major_version', '!distribution_release', '!distribution_version', '!dns', '!effective_group_ids', '!effective_user_id', '!env', '!facter', '!fips', '!hardware', '!interfaces', '!is_chroot', '!iscsi', '!kernel', '!local', '!lsb', '!machine', '!machine_id', '!mounts', '!network', '!ohai', '!os_family', '!pkg_mgr', '!platform', '!processor', '!processor_cores', '!processor_count', '!python', '!python_version', '!real_user_id', '!selinux', '!service_mgr', '!ssh_host_key_dsa_public', '!ssh_host_key_ecdsa_public', '!ssh_host_key_ed25519_public', '!ssh_host_key_rsa_public', '!ssh_host_pub_keys', '!ssh_pub_keys', '!system', '!system_capabilities', '!system_capabilities_enforced', '!user', '!user_dir', '!user_gecos', '!user_gid', '!user_id', '!user_shell', '!user_uid', '!virtual', '!virtualization_role', '!virtualization_type']" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/gather_subset3.yml b/test/schemas/negative_test/playbooks/gather_subset3.yml new file mode 100644 index 0000000..05e4028 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset3.yml @@ -0,0 +1,7 @@ +--- +- hosts: localhost + gather_subset: + - 1 + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/negative_test/playbooks/gather_subset3.yml.md b/test/schemas/negative_test/playbooks/gather_subset3.yml.md new file mode 100644 index 0000000..7dc1b13 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset3.yml.md @@ -0,0 +1,303 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "gather_subset" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/0/type" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "all", + "min", + "all_ipv4_addresses", + "all_ipv6_addresses", + "apparmor", + "architecture", + "caps", + "chroot,cmdline", + "date_time", + "default_ipv4", + "default_ipv6", + "devices", + "distribution", + "distribution_major_version", + "distribution_release", + "distribution_version", + "dns", + "effective_group_ids", + "effective_user_id", + "env", + "facter", + "fips", + "hardware", + "interfaces", + "is_chroot", + "iscsi", + "kernel", + "local", + "lsb", + "machine", + "machine_id", + "mounts", + "network", + "ohai", + "os_family", + "pkg_mgr", + "platform", + "processor", + "processor_cores", + "processor_count", + "python", + "python_version", + "real_user_id", + "selinux", + "service_mgr", + "ssh_host_key_dsa_public", + "ssh_host_key_ecdsa_public", + "ssh_host_key_ed25519_public", + "ssh_host_key_rsa_public", + "ssh_host_pub_keys", + "ssh_pub_keys", + "system", + "system_capabilities", + "system_capabilities_enforced", + "user", + "user_dir", + "user_gecos", + "user_gid", + "user_id", + "user_shell", + "user_uid", + "virtual", + "virtualization_role", + "virtualization_type" + ] + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/0/enum" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/1/type" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "!all", + "!min", + "!all_ipv4_addresses", + "!all_ipv6_addresses", + "!apparmor", + "!architecture", + "!caps", + "!chroot,cmdline", + "!date_time", + "!default_ipv4", + "!default_ipv6", + "!devices", + "!distribution", + "!distribution_major_version", + "!distribution_release", + "!distribution_version", + "!dns", + "!effective_group_ids", + "!effective_user_id", + "!env", + "!facter", + "!fips", + "!hardware", + "!interfaces", + "!is_chroot", + "!iscsi", + "!kernel", + "!local", + "!lsb", + "!machine", + "!machine_id", + "!mounts", + "!network", + "!ohai", + "!os_family", + "!pkg_mgr", + "!platform", + "!processor", + "!processor_cores", + "!processor_count", + "!python", + "!python_version", + "!real_user_id", + "!selinux", + "!service_mgr", + "!ssh_host_key_dsa_public", + "!ssh_host_key_ecdsa_public", + "!ssh_host_key_ed25519_public", + "!ssh_host_key_rsa_public", + "!ssh_host_pub_keys", + "!ssh_pub_keys", + "!system", + "!system_capabilities", + "!system_capabilities_enforced", + "!user", + "!user_dir", + "!user_gecos", + "!user_gid", + "!user_id", + "!user_shell", + "!user_uid", + "!virtual", + "!virtualization_role", + "!virtualization_type" + ] + }, + "schemaPath": "#/properties/gather_subset/items/anyOf/1/enum" + }, + { + "instancePath": "/0/gather_subset/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/gather_subset/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/gather_subset3.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': [1], 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': [1], 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].gather_subset[0]", + "message": "1 is not valid under any of the given schemas" + }, + { + "path": "$[0].gather_subset[0]", + "message": "1 is not one of ['all', 'min', 'all_ipv4_addresses', 'all_ipv6_addresses', 'apparmor', 'architecture', 'caps', 'chroot,cmdline', 'date_time', 'default_ipv4', 'default_ipv6', 'devices', 'distribution', 'distribution_major_version', 'distribution_release', 'distribution_version', 'dns', 'effective_group_ids', 'effective_user_id', 'env', 'facter', 'fips', 'hardware', 'interfaces', 'is_chroot', 'iscsi', 'kernel', 'local', 'lsb', 'machine', 'machine_id', 'mounts', 'network', 'ohai', 'os_family', 'pkg_mgr', 'platform', 'processor', 'processor_cores', 'processor_count', 'python', 'python_version', 'real_user_id', 'selinux', 'service_mgr', 'ssh_host_key_dsa_public', 'ssh_host_key_ecdsa_public', 'ssh_host_key_ed25519_public', 'ssh_host_key_rsa_public', 'ssh_host_pub_keys', 'ssh_pub_keys', 'system', 'system_capabilities', 'system_capabilities_enforced', 'user', 'user_dir', 'user_gecos', 'user_gid', 'user_id', 'user_shell', 'user_uid', 'virtual', 'virtualization_role', 'virtualization_type']" + }, + { + "path": "$[0].gather_subset[0]", + "message": "1 is not of type 'string'" + }, + { + "path": "$[0].gather_subset[0]", + "message": "1 is not one of ['!all', '!min', '!all_ipv4_addresses', '!all_ipv6_addresses', '!apparmor', '!architecture', '!caps', '!chroot,cmdline', '!date_time', '!default_ipv4', '!default_ipv6', '!devices', '!distribution', '!distribution_major_version', '!distribution_release', '!distribution_version', '!dns', '!effective_group_ids', '!effective_user_id', '!env', '!facter', '!fips', '!hardware', '!interfaces', '!is_chroot', '!iscsi', '!kernel', '!local', '!lsb', '!machine', '!machine_id', '!mounts', '!network', '!ohai', '!os_family', '!pkg_mgr', '!platform', '!processor', '!processor_cores', '!processor_count', '!python', '!python_version', '!real_user_id', '!selinux', '!service_mgr', '!ssh_host_key_dsa_public', '!ssh_host_key_ecdsa_public', '!ssh_host_key_ed25519_public', '!ssh_host_key_rsa_public', '!ssh_host_pub_keys', '!ssh_pub_keys', '!system', '!system_capabilities', '!system_capabilities_enforced', '!user', '!user_dir', '!user_gecos', '!user_gid', '!user_id', '!user_shell', '!user_uid', '!virtual', '!virtualization_role', '!virtualization_type']" + }, + { + "path": "$[0].gather_subset[0]", + "message": "1 is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/gather_subset4.yml b/test/schemas/negative_test/playbooks/gather_subset4.yml new file mode 100644 index 0000000..816e666 --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset4.yml @@ -0,0 +1,6 @@ +--- +- hosts: localhost + gather_subset: 1 + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/negative_test/playbooks/gather_subset4.yml.md b/test/schemas/negative_test/playbooks/gather_subset4.yml.md new file mode 100644 index 0000000..ada01cb --- /dev/null +++ b/test/schemas/negative_test/playbooks/gather_subset4.yml.md @@ -0,0 +1,123 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "gather_subset" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/gather_subset", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/gather_subset/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/gather_subset4.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': 1, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'gather_subset': 1, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].gather_subset", + "message": "1 is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/ignore_errors.yml b/test/schemas/negative_test/playbooks/ignore_errors.yml new file mode 100644 index 0000000..9da277f --- /dev/null +++ b/test/schemas/negative_test/playbooks/ignore_errors.yml @@ -0,0 +1,6 @@ +- hosts: localhost + tasks: + - command: echo 123 + vars: + should_ignore_errors: true + ignore_errors: should_ignore_errors # invalid due to missing {{ }} diff --git a/test/schemas/negative_test/playbooks/ignore_errors.yml.md b/test/schemas/negative_test/playbooks/ignore_errors.yml.md new file mode 100644 index 0000000..61c3116 --- /dev/null +++ b/test/schemas/negative_test/playbooks/ignore_errors.yml.md @@ -0,0 +1,203 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/ignore_errors", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/ignore_errors.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'command': 'echo 123', 'vars': {'should_ignore_errors': True}, 'ignore_errors': 'should_ignore_errors'}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'command': 'echo 123', 'vars': {'should_ignore_errors': True}, 'ignore_errors': 'should_ignore_errors'}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'command': 'echo 123', 'vars': {'should_ignore_errors': True}, 'ignore_errors': 'should_ignore_errors'} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/import_playbook.yml b/test/schemas/negative_test/playbooks/import_playbook.yml new file mode 100644 index 0000000..b6d8ec2 --- /dev/null +++ b/test/schemas/negative_test/playbooks/import_playbook.yml @@ -0,0 +1 @@ +- ansible.builtin.import_playbook: {} # only freeform/string is allowed diff --git a/test/schemas/negative_test/playbooks/import_playbook.yml.md b/test/schemas/negative_test/playbooks/import_playbook.yml.md new file mode 100644 index 0000000..def3dce --- /dev/null +++ b/test/schemas/negative_test/playbooks/import_playbook.yml.md @@ -0,0 +1,90 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0/ansible.builtin.import_playbook", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/patternProperties/%5E(ansible%5C.builtin%5C.)%3Fimport_playbook%24/type" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/allOf/0/not" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'hosts'", + "params": { + "missingProperty": "hosts" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/import_playbook.yml", + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': {}} should not be valid under {'required': ['ansible.builtin.import_playbook']}" + }, + "sub_errors": [ + { + "path": "$[0].ansible.builtin.import_playbook", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0]", + "message": "Additional properties are not allowed ('ansible.builtin.import_playbook' was unexpected)" + }, + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': {}} should not be valid under {'required': ['ansible.builtin.import_playbook']}" + }, + { + "path": "$[0]", + "message": "'hosts' is a required property" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml new file mode 100644 index 0000000..ef2b5f6 --- /dev/null +++ b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml @@ -0,0 +1,4 @@ +--- +# invalid because you cannot have both entries in the same time: +- ansible.builtin.import_playbook: foo.yml + import_playbook: other.yml diff --git a/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md new file mode 100644 index 0000000..184a434 --- /dev/null +++ b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md @@ -0,0 +1,132 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/oneOf/0/not" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/oneOf/1/not" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/allOf/0/not" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/allOf/1/not" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'hosts'", + "params": { + "missingProperty": "hosts" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "import_playbook" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/import_playbook_exclusive.yml", + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['ansible.builtin.import_playbook']}" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['import_playbook']}" + }, + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['ansible.builtin.import_playbook']}" + }, + { + "path": "$[0]", + "message": "Additional properties are not allowed ('ansible.builtin.import_playbook', 'import_playbook' were unexpected)" + }, + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['ansible.builtin.import_playbook']}" + }, + { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['import_playbook']}" + }, + { + "path": "$[0]", + "message": "'hosts' is a required property" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/invalid-failed-when.yml b/test/schemas/negative_test/playbooks/invalid-failed-when.yml new file mode 100644 index 0000000..075f166 --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid-failed-when.yml @@ -0,0 +1,15 @@ +- hosts: localhost + tasks: + - debug: + msg: "failed_when should not accept numeric" + failed_when: 123 + + - debug: + msg: "failed_when should not accept sequence" + failed_when: + - foo + - bar + + - debug: + msg: "failed_when should not accept map" + failed_when: {} diff --git a/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md b/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md new file mode 100644 index 0000000..3a41059 --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md @@ -0,0 +1,253 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/tasks/0/failed_when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0/tasks/2", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/2/failed_when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/2/failed_when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/2/failed_when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/tasks/2/failed_when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0/tasks/2", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/invalid-failed-when.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'debug': {'msg': 'failed_when should not accept numeric'}, 'failed_when': 123}, {'debug': {'msg': 'failed_when should not accept sequence'}, 'failed_when': ['foo', 'bar']}, {'debug': {'msg': 'failed_when should not accept map'}, 'failed_when': {}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'debug': {'msg': 'failed_when should not accept numeric'}, 'failed_when': 123}, {'debug': {'msg': 'failed_when should not accept sequence'}, 'failed_when': ['foo', 'bar']}, {'debug': {'msg': 'failed_when should not accept map'}, 'failed_when': {}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'debug': {'msg': 'failed_when should not accept numeric'}, 'failed_when': 123} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'array'" + }, + { + "path": "$[0].tasks[2]", + "message": "{'debug': {'msg': 'failed_when should not accept map'}, 'failed_when': {}} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[2]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[2].failed_when", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[2].failed_when", + "message": "{} is not of type 'boolean'" + }, + { + "path": "$[0].tasks[2].failed_when", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].tasks[2].failed_when", + "message": "{} is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/invalid-serial.yml b/test/schemas/negative_test/playbooks/invalid-serial.yml new file mode 100644 index 0000000..f2ffd3c --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid-serial.yml @@ -0,0 +1,2 @@ +- hosts: localhost + serial: 10%BAD diff --git a/test/schemas/negative_test/playbooks/invalid-serial.yml.md b/test/schemas/negative_test/playbooks/invalid-serial.yml.md new file mode 100644 index 0000000..5c48b21 --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid-serial.yml.md @@ -0,0 +1,177 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "serial" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/serial", + "keyword": "type", + "message": "must be integer", + "params": { + "type": "integer" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/serial", + "keyword": "pattern", + "message": "must match pattern \"^\\d+\\.?\\d*%?$\"", + "params": { + "pattern": "^\\d+\\.?\\d*%?$" + }, + "schemaPath": "#/oneOf/1/pattern" + }, + { + "instancePath": "/0/serial", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/serial", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/serial", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/serial/anyOf/1/type" + }, + { + "instancePath": "/0/serial", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/serial/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/invalid-serial.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'serial': '10%BAD'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'serial' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'serial' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'serial': '10%BAD'} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' is not valid under any of the given schemas" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' is not valid under any of the given schemas" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' is not of type 'integer'" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' does not match '^\\\\d+\\\\.?\\\\d*%?$'" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].serial", + "message": "'10%BAD' is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/invalid.yml b/test/schemas/negative_test/playbooks/invalid.yml new file mode 100644 index 0000000..e34d3c9 --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid.yml @@ -0,0 +1,3 @@ +- name: foo + hosts: localhost # <-- not allowed with import_playbook + import_playbook: included.yml diff --git a/test/schemas/negative_test/playbooks/invalid.yml.md b/test/schemas/negative_test/playbooks/invalid.yml.md new file mode 100644 index 0000000..c3435dd --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/allOf/1/not" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "import_playbook" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/invalid.yml", + "path": "$[0]", + "message": "{'name': 'foo', 'hosts': 'localhost', 'import_playbook': 'included.yml'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "{'name': 'foo', 'hosts': 'localhost', 'import_playbook': 'included.yml'} should not be valid under {'required': ['import_playbook']}" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "Additional properties are not allowed ('import_playbook' was unexpected)" + }, + { + "path": "$[0]", + "message": "{'name': 'foo', 'hosts': 'localhost', 'import_playbook': 'included.yml'} should not be valid under {'required': ['import_playbook']}" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/invalid_become.yml b/test/schemas/negative_test/playbooks/invalid_become.yml new file mode 100644 index 0000000..0cc6721 --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid_become.yml @@ -0,0 +1,3 @@ +--- +- hosts: localhost + become: yes # <- invalid based on json schema diff --git a/test/schemas/negative_test/playbooks/invalid_become.yml.md b/test/schemas/negative_test/playbooks/invalid_become.yml.md new file mode 100644 index 0000000..37d730d --- /dev/null +++ b/test/schemas/negative_test/playbooks/invalid_become.yml.md @@ -0,0 +1,140 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "become" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/become", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/become", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/become", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/invalid_become.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'become': 'yes'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'become', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'become', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'become': 'yes'} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].become", + "message": "'yes' is not valid under any of the given schemas" + }, + { + "path": "$[0].become", + "message": "'yes' is not of type 'boolean'" + }, + { + "path": "$[0].become", + "message": "'yes' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/local_action.yml b/test/schemas/negative_test/playbooks/local_action.yml new file mode 100644 index 0000000..9e01b1d --- /dev/null +++ b/test/schemas/negative_test/playbooks/local_action.yml @@ -0,0 +1,3 @@ +- hosts: localhost + tasks: + - local_action: [] # <-- only string or dict is allowed diff --git a/test/schemas/negative_test/playbooks/local_action.yml.md b/test/schemas/negative_test/playbooks/local_action.yml.md new file mode 100644 index 0000000..17f6244 --- /dev/null +++ b/test/schemas/negative_test/playbooks/local_action.yml.md @@ -0,0 +1,141 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/local_action", + "keyword": "type", + "message": "must be string,object", + "params": { + "type": [ + "string", + "object" + ] + }, + "schemaPath": "#/properties/local_action/type" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/local_action.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'local_action': []}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'local_action': []}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'local_action': []} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].local_action", + "message": "[] is not of type 'string', 'object'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/loop.yml b/test/schemas/negative_test/playbooks/loop.yml new file mode 100644 index 0000000..fd02ec5 --- /dev/null +++ b/test/schemas/negative_test/playbooks/loop.yml @@ -0,0 +1,7 @@ +--- +- hosts: localhost + tasks: + - name: that should pass + ansible.builtin.debug: + var: item + loop: 123 # <-- number is not valid diff --git a/test/schemas/negative_test/playbooks/loop.yml.md b/test/schemas/negative_test/playbooks/loop.yml.md new file mode 100644 index 0000000..88df838 --- /dev/null +++ b/test/schemas/negative_test/playbooks/loop.yml.md @@ -0,0 +1,141 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/loop", + "keyword": "type", + "message": "must be string,array", + "params": { + "type": [ + "string", + "array" + ] + }, + "schemaPath": "#/properties/loop/type" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/loop.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': 123}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': 123}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': 123} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].loop", + "message": "123 is not of type 'string', 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/loop2.yml b/test/schemas/negative_test/playbooks/loop2.yml new file mode 100644 index 0000000..7c9f2db --- /dev/null +++ b/test/schemas/negative_test/playbooks/loop2.yml @@ -0,0 +1,7 @@ +--- +- hosts: localhost + tasks: + - name: that should pass + ansible.builtin.debug: + var: item + loop: {} # <-- map is not valid diff --git a/test/schemas/negative_test/playbooks/loop2.yml.md b/test/schemas/negative_test/playbooks/loop2.yml.md new file mode 100644 index 0000000..df60a41 --- /dev/null +++ b/test/schemas/negative_test/playbooks/loop2.yml.md @@ -0,0 +1,141 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/loop", + "keyword": "type", + "message": "must be string,array", + "params": { + "type": [ + "string", + "array" + ] + }, + "schemaPath": "#/properties/loop/type" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/loop2.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': {}}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': {}}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'name': 'that should pass', 'ansible.builtin.debug': {'var': 'item'}, 'loop': {}} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].loop", + "message": "{} is not of type 'string', 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/no_log_partial_template.yml b/test/schemas/negative_test/playbooks/no_log_partial_template.yml new file mode 100644 index 0000000..224aba8 --- /dev/null +++ b/test/schemas/negative_test/playbooks/no_log_partial_template.yml @@ -0,0 +1,7 @@ +- hosts: localhost + vars: + some_var: true + tasks: + - ansible.builtin.debug: + msg: foo + no_log: "foo-{{ some_var }}" # <-- partial templating not allowed here diff --git a/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md b/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md new file mode 100644 index 0000000..ee73686 --- /dev/null +++ b/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md @@ -0,0 +1,203 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/no_log_partial_template.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars': {'some_var': True}, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'foo-{{ some_var }}'}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars': {'some_var': True}, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'foo-{{ some_var }}'}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'foo-{{ some_var }}'} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/no_log_string.yml b/test/schemas/negative_test/playbooks/no_log_string.yml new file mode 100644 index 0000000..caf88e2 --- /dev/null +++ b/test/schemas/negative_test/playbooks/no_log_string.yml @@ -0,0 +1,7 @@ +- hosts: localhost + vars: + some_var: true + tasks: + - ansible.builtin.debug: + msg: foo + no_log: some_var # <-- bad, jinja use must be explicit diff --git a/test/schemas/negative_test/playbooks/no_log_string.yml.md b/test/schemas/negative_test/playbooks/no_log_string.yml.md new file mode 100644 index 0000000..c8213c0 --- /dev/null +++ b/test/schemas/negative_test/playbooks/no_log_string.yml.md @@ -0,0 +1,203 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/no_log_string.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars': {'some_var': True}, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'some_var'}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars': {'some_var': True}, 'tasks': [{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'some_var'}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 'some_var'} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/roles.yml b/test/schemas/negative_test/playbooks/roles.yml new file mode 100644 index 0000000..e24445a --- /dev/null +++ b/test/schemas/negative_test/playbooks/roles.yml @@ -0,0 +1,2 @@ +- hosts: localhost + roles: xxx # must be array diff --git a/test/schemas/negative_test/playbooks/roles.yml.md b/test/schemas/negative_test/playbooks/roles.yml.md new file mode 100644 index 0000000..9b4e25a --- /dev/null +++ b/test/schemas/negative_test/playbooks/roles.yml.md @@ -0,0 +1,114 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "roles" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/roles", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/roles/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/roles.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'roles': 'xxx'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'roles' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'roles' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'roles': 'xxx'} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].roles", + "message": "'xxx' is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/run_once_list.yml b/test/schemas/negative_test/playbooks/run_once_list.yml new file mode 100644 index 0000000..0dd2cd5 --- /dev/null +++ b/test/schemas/negative_test/playbooks/run_once_list.yml @@ -0,0 +1,8 @@ +- hosts: localhost + tasks: + - name: foo2 + ansible.builtin.debug: + msg: foo! + run_once: # invalid due to schema, also ansible does not allow lists + - "{{ true }}" + - xxx diff --git a/test/schemas/negative_test/playbooks/run_once_list.yml.md b/test/schemas/negative_test/playbooks/run_once_list.yml.md new file mode 100644 index 0000000..84b7dc1 --- /dev/null +++ b/test/schemas/negative_test/playbooks/run_once_list.yml.md @@ -0,0 +1,221 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/tasks/0/run_once", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/run_once_list.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'foo2', 'ansible.builtin.debug': {'msg': 'foo!'}, 'run_once': ['{{ true }}', 'xxx']}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'name': 'foo2', 'ansible.builtin.debug': {'msg': 'foo!'}, 'run_once': ['{{ true }}', 'xxx']}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'name': 'foo2', 'ansible.builtin.debug': {'msg': 'foo!'}, 'run_once': ['{{ true }}', 'xxx']} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not of type 'string'" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tags-mapping.yml b/test/schemas/negative_test/playbooks/tags-mapping.yml new file mode 100644 index 0000000..8c6da3d --- /dev/null +++ b/test/schemas/negative_test/playbooks/tags-mapping.yml @@ -0,0 +1,2 @@ +- hosts: localhost + tags: {} # <-- not allowed diff --git a/test/schemas/negative_test/playbooks/tags-mapping.yml.md b/test/schemas/negative_test/playbooks/tags-mapping.yml.md new file mode 100644 index 0000000..aada0c6 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tags-mapping.yml.md @@ -0,0 +1,166 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tags-mapping.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tags': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tags': {}} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tags", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'array'" + }, + { + "path": "$[0].tags", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tags-number.yml b/test/schemas/negative_test/playbooks/tags-number.yml new file mode 100644 index 0000000..1872ced --- /dev/null +++ b/test/schemas/negative_test/playbooks/tags-number.yml @@ -0,0 +1,2 @@ +- hosts: localhost + tags: 123 # <-- not allowed diff --git a/test/schemas/negative_test/playbooks/tags-number.yml.md b/test/schemas/negative_test/playbooks/tags-number.yml.md new file mode 100644 index 0000000..3d32737 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tags-number.yml.md @@ -0,0 +1,166 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tags-number.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tags': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tags': 123} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tags", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'array'" + }, + { + "path": "$[0].tags", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks.yml b/test/schemas/negative_test/playbooks/tasks.yml new file mode 100644 index 0000000..2464a73 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks.yml @@ -0,0 +1,5 @@ +- hosts: localhost + pre_tasks: foo # <-- must be array + post_tasks: {} # <-- must be array + tasks: 1 # <-- must be array + handlers: 1.0 # <-- must be array diff --git a/test/schemas/negative_test/playbooks/tasks.yml.md b/test/schemas/negative_test/playbooks/tasks.yml.md new file mode 100644 index 0000000..309912b --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks.yml.md @@ -0,0 +1,192 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "pre_tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "post_tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "handlers" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/handlers", + "keyword": "type", + "message": "must be array,null", + "params": { + "type": [ + "array", + "null" + ] + }, + "schemaPath": "#/type" + }, + { + "instancePath": "/0/post_tasks", + "keyword": "type", + "message": "must be array,null", + "params": { + "type": [ + "array", + "null" + ] + }, + "schemaPath": "#/type" + }, + { + "instancePath": "/0/pre_tasks", + "keyword": "type", + "message": "must be array,null", + "params": { + "type": [ + "array", + "null" + ] + }, + "schemaPath": "#/type" + }, + { + "instancePath": "/0/tasks", + "keyword": "type", + "message": "must be array,null", + "params": { + "type": [ + "array", + "null" + ] + }, + "schemaPath": "#/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'pre_tasks': 'foo', 'post_tasks': {}, 'tasks': 1, 'handlers': 1.0} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'handlers', 'hosts', 'post_tasks', 'pre_tasks', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'handlers', 'hosts', 'post_tasks', 'pre_tasks', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'pre_tasks': 'foo', 'post_tasks': {}, 'tasks': 1, 'handlers': 1.0} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].handlers", + "message": "1.0 is not of type 'array', 'null'" + }, + { + "path": "$[0].post_tasks", + "message": "{} is not of type 'array', 'null'" + }, + { + "path": "$[0].pre_tasks", + "message": "'foo' is not of type 'array', 'null'" + }, + { + "path": "$[0].tasks", + "message": "1 is not of type 'array', 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/args_integer.yml b/test/schemas/negative_test/playbooks/tasks/args_integer.yml new file mode 100644 index 0000000..b831039 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/args_integer.yml @@ -0,0 +1,2 @@ +- action: foo + args: 123 # invalid diff --git a/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md b/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md new file mode 100644 index 0000000..8820251 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md @@ -0,0 +1,99 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/args", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/args", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/args", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/args", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/args_integer.yml", + "path": "$[0]", + "message": "{'action': 'foo', 'args': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].args", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].args", + "message": "123 is not of type 'object'" + }, + { + "path": "$[0].args", + "message": "123 is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/args_string.yml b/test/schemas/negative_test/playbooks/tasks/args_string.yml new file mode 100644 index 0000000..121da6d --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/args_string.yml @@ -0,0 +1,2 @@ +- action: foo + args: "{{ }}123" # invalid as only full jinja2 expressions are allowed diff --git a/test/schemas/negative_test/playbooks/tasks/args_string.yml.md b/test/schemas/negative_test/playbooks/tasks/args_string.yml.md new file mode 100644 index 0000000..6359a14 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/args_string.yml.md @@ -0,0 +1,90 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/args", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/args", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/args", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/args_string.yml", + "path": "$[0]", + "message": "{'action': 'foo', 'args': '{{ }}123'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].args", + "message": "'{{ }}123' is not valid under any of the given schemas" + }, + { + "path": "$[0].args", + "message": "'{{ }}123' is not of type 'object'" + }, + { + "path": "$[0].args", + "message": "'{{ }}123' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml b/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml new file mode 100644 index 0000000..bc7217f --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml @@ -0,0 +1,4 @@ +- command: echo 123 + vars: + sudo_var: doo + become_method: sudo_var # templating requires {{ }} diff --git a/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml.md b/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml.md new file mode 100644 index 0000000..25d3704 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/become_method_untemplated.yml.md @@ -0,0 +1,149 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/become_method", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "sudo", + "su", + "pbrun", + "pfexec", + "runas", + "dzdo", + "ksu", + "doas", + "machinectl" + ] + }, + "schemaPath": "#/oneOf/0/enum" + }, + { + "instancePath": "/0/become_method", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/become_method", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/become_method", + "keyword": "enum", + "message": "must be equal to one of the allowed values", + "params": { + "allowedValues": [ + "sudo", + "su", + "pbrun", + "pfexec", + "runas", + "dzdo", + "ksu", + "doas", + "machinectl" + ] + }, + "schemaPath": "#/oneOf/0/enum" + }, + { + "instancePath": "/0/become_method", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/become_method", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/become_method_untemplated.yml", + "path": "$[0]", + "message": "{'command': 'echo 123', 'vars': {'sudo_var': 'doo'}, 'become_method': 'sudo_var'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].become_method", + "message": "'sudo_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].become_method", + "message": "'sudo_var' is not one of ['sudo', 'su', 'pbrun', 'pfexec', 'runas', 'dzdo', 'ksu', 'doas', 'machinectl']" + }, + { + "path": "$[0].become_method", + "message": "'sudo_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].become_method", + "message": "'sudo_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].become_method", + "message": "'sudo_var' is not one of ['sudo', 'su', 'pbrun', 'pfexec', 'runas', 'dzdo', 'ksu', 'doas', 'machinectl']" + }, + { + "path": "$[0].become_method", + "message": "'sudo_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml new file mode 100644 index 0000000..4f8cbb3 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml @@ -0,0 +1,4 @@ +- command: echo 123 + vars: + should_ignore_errors: true + ignore_errors: should_ignore_errors # invalid due to missing {{ }} diff --git a/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md new file mode 100644 index 0000000..559a200 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md @@ -0,0 +1,129 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/ignore_errors", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/ignore_errors.yml", + "path": "$[0]", + "message": "{'command': 'echo 123', 'vars': {'should_ignore_errors': True}, 'ignore_errors': 'should_ignore_errors'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' is not valid under any of the given schemas" + }, + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' is not valid under any of the given schemas" + }, + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/invalid_block.yml b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml new file mode 100644 index 0000000..6fef6d1 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml @@ -0,0 +1,2 @@ +--- +- block: {} # <-- invalid, should be array diff --git a/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md new file mode 100644 index 0000000..bf4b30e --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md @@ -0,0 +1,62 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0/block", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/block/type" + }, + { + "instancePath": "/0", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/allOf/3/not" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/invalid_block.yml", + "path": "$[0]", + "message": "{'block': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "{'block': {}} should not be valid under {'required': ['block']}" + }, + "sub_errors": [ + { + "path": "$[0].block", + "message": "{} is not of type 'array'" + }, + { + "path": "$[0]", + "message": "{'block': {}} should not be valid under {'required': ['block']}" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/local_action.yml b/test/schemas/negative_test/playbooks/tasks/local_action.yml new file mode 100644 index 0000000..d601ff5 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/local_action.yml @@ -0,0 +1 @@ +- local_action: [] # <-- only string or dict is allowed diff --git a/test/schemas/negative_test/playbooks/tasks/local_action.yml.md b/test/schemas/negative_test/playbooks/tasks/local_action.yml.md new file mode 100644 index 0000000..cf67e7b --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/local_action.yml.md @@ -0,0 +1,67 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/local_action", + "keyword": "type", + "message": "must be string,object", + "params": { + "type": [ + "string", + "object" + ] + }, + "schemaPath": "#/properties/local_action/type" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/local_action.yml", + "path": "$[0]", + "message": "{'local_action': []} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].local_action", + "message": "[] is not of type 'string', 'object'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/loop.yml b/test/schemas/negative_test/playbooks/tasks/loop.yml new file mode 100644 index 0000000..651d262 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/loop.yml @@ -0,0 +1,3 @@ +- ansible.builtin.debug: + var: item + loop: {} # <-- map is not valid diff --git a/test/schemas/negative_test/playbooks/tasks/loop.yml.md b/test/schemas/negative_test/playbooks/tasks/loop.yml.md new file mode 100644 index 0000000..de8277f --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/loop.yml.md @@ -0,0 +1,67 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/loop", + "keyword": "type", + "message": "must be string,array", + "params": { + "type": [ + "string", + "array" + ] + }, + "schemaPath": "#/properties/loop/type" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/loop.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'var': 'item'}, 'loop': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].loop", + "message": "{} is not of type 'string', 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/loop2.yml b/test/schemas/negative_test/playbooks/tasks/loop2.yml new file mode 100644 index 0000000..ec2642f --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/loop2.yml @@ -0,0 +1,3 @@ +- ansible.builtin.debug: + var: item + loop: 123 # <-- number is not valid diff --git a/test/schemas/negative_test/playbooks/tasks/loop2.yml.md b/test/schemas/negative_test/playbooks/tasks/loop2.yml.md new file mode 100644 index 0000000..c36d7c9 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/loop2.yml.md @@ -0,0 +1,67 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/loop", + "keyword": "type", + "message": "must be string,array", + "params": { + "type": [ + "string", + "array" + ] + }, + "schemaPath": "#/properties/loop/type" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/loop2.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'var': 'item'}, 'loop': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].loop", + "message": "123 is not of type 'string', 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_number.yml b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml new file mode 100644 index 0000000..4fa8da2 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml @@ -0,0 +1,3 @@ +- ansible.builtin.debug: + msg: foo + no_log: 123 # <-- bad diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md new file mode 100644 index 0000000..4b9516c --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md @@ -0,0 +1,147 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/no_log_number.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'no_log': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].no_log", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].no_log", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].no_log", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].no_log", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].no_log", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].no_log", + "message": "123 is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_string.yml b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml new file mode 100644 index 0000000..0e0b71a --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml @@ -0,0 +1,5 @@ +- ansible.builtin.debug: + msg: foo + vars: + some_var: true + no_log: some_var # <-- bad, jinja use must be explicit diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md new file mode 100644 index 0000000..6742175 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md @@ -0,0 +1,129 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/no_log", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/no_log", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/no_log", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/no_log_string.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'vars': {'some_var': True}, 'no_log': 'some_var'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].no_log", + "message": "'some_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + { + "path": "$[0].no_log", + "message": "'some_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].no_log", + "message": "'some_var' is not valid under any of the given schemas" + }, + { + "path": "$[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + { + "path": "$[0].no_log", + "message": "'some_var' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml new file mode 100644 index 0000000..39fe8c7 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml @@ -0,0 +1,3 @@ +- ansible.builtin.debug: + msg: foo + tags: {} # <-- not allowed diff --git a/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md new file mode 100644 index 0000000..d860605 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md @@ -0,0 +1,125 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/tags-mapping.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'tags': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].tags", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'array'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tags", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "{} is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/tags-string.yml b/test/schemas/negative_test/playbooks/tasks/tags-string.yml new file mode 100644 index 0000000..6512fb5 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/tags-string.yml @@ -0,0 +1,3 @@ +- ansible.builtin.debug: + msg: foo + tags: 123 # <-- not allowed diff --git a/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md b/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md new file mode 100644 index 0000000..0bb7ed0 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md @@ -0,0 +1,125 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/tags/anyOf/0/type" + }, + { + "instancePath": "/0/tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/tags/anyOf/1/type" + }, + { + "instancePath": "/0/tags", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/$defs/tags/anyOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/tags-string.yml", + "path": "$[0]", + "message": "{'ansible.builtin.debug': {'msg': 'foo'}, 'tags': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].tags", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'array'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tags", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tags", + "message": "123 is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/when_integer.yml b/test/schemas/negative_test/playbooks/tasks/when_integer.yml new file mode 100644 index 0000000..7758503 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/when_integer.yml @@ -0,0 +1,2 @@ +- action: foo + when: 123 # invalid, number is not accepted diff --git a/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md b/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md new file mode 100644 index 0000000..bc59cc4 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md @@ -0,0 +1,155 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/when_integer.yml", + "path": "$[0]", + "message": "{'action': 'foo', 'when': 123} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].when", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'array'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].when", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].when", + "message": "123 is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/when_object.yml b/test/schemas/negative_test/playbooks/tasks/when_object.yml new file mode 100644 index 0000000..430605d --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/when_object.yml @@ -0,0 +1,2 @@ +- action: foo + when: {} # invalid, object is not accepted diff --git a/test/schemas/negative_test/playbooks/tasks/when_object.yml.md b/test/schemas/negative_test/playbooks/tasks/when_object.yml.md new file mode 100644 index 0000000..6c28d0c --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/when_object.yml.md @@ -0,0 +1,155 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/0/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/1/type" + }, + { + "instancePath": "/0/when", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/$defs/complex_conditional/oneOf/2/type" + }, + { + "instancePath": "/0/when", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/$defs/complex_conditional/oneOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/when_object.yml", + "path": "$[0]", + "message": "{'action': 'foo', 'when': {}} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0].when", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'boolean'" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'array'" + }, + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].when", + "message": "{} is not valid under any of the given schemas" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'boolean'" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'string'" + }, + { + "path": "$[0].when", + "message": "{} is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml new file mode 100644 index 0000000..eff6ea0 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml @@ -0,0 +1,2 @@ +- command: echo 123 + with_items: true # invalid, must be a list or templated string diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md new file mode 100644 index 0000000..ffc8ef8 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md @@ -0,0 +1,88 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/with_items", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/with_items", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/with_items/anyOf/1/type" + }, + { + "instancePath": "/0/with_items", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/with_items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/with_items_boolean.yml", + "path": "$[0]", + "message": "{'command': 'echo 123', 'with_items': True} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].with_items", + "message": "True is not valid under any of the given schemas" + }, + { + "path": "$[0].with_items", + "message": "True is not of type 'string'" + }, + { + "path": "$[0].with_items", + "message": "True is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml new file mode 100644 index 0000000..257ffe2 --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml @@ -0,0 +1,2 @@ +- command: echo 123 + with_items: foobar # invalid, probably user wanted "{{ foobar }}"? diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md new file mode 100644 index 0000000..158b0ee --- /dev/null +++ b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md @@ -0,0 +1,88 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/with_items", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/with_items", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/with_items/anyOf/1/type" + }, + { + "instancePath": "/0/with_items", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/with_items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/tasks/with_items_untemplated_string.yml", + "path": "$[0]", + "message": "{'command': 'echo 123', 'with_items': 'foobar'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'block' is a required property" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].with_items", + "message": "'foobar' is not valid under any of the given schemas" + }, + { + "path": "$[0].with_items", + "message": "'foobar' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].with_items", + "message": "'foobar' is not of type 'array'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/var_files_list_number.yml b/test/schemas/negative_test/playbooks/var_files_list_number.yml new file mode 100644 index 0000000..9f3d8dd --- /dev/null +++ b/test/schemas/negative_test/playbooks/var_files_list_number.yml @@ -0,0 +1,5 @@ +--- +- name: var_files should not accept array[number] + hosts: localhost + vars_files: + - 0 diff --git a/test/schemas/negative_test/playbooks/var_files_list_number.yml.md b/test/schemas/negative_test/playbooks/var_files_list_number.yml.md new file mode 100644 index 0000000..6ecc15e --- /dev/null +++ b/test/schemas/negative_test/playbooks/var_files_list_number.yml.md @@ -0,0 +1,118 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/vars_files", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/patternProperties/vars/type" + }, + { + "instancePath": "/0/vars_files/0", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/properties/vars_files/items/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/var_files_list_number.yml", + "path": "$[0]", + "message": "{'name': 'var_files should not accept array[number]', 'hosts': 'localhost', 'vars_files': [0]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'name': 'var_files should not accept array[number]', 'hosts': 'localhost', 'vars_files': [0]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].vars_files", + "message": "[0] is not of type 'object'" + }, + { + "path": "$[0].vars_files[0]", + "message": "0 is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/var_files_number.yml b/test/schemas/negative_test/playbooks/var_files_number.yml new file mode 100644 index 0000000..fe26650 --- /dev/null +++ b/test/schemas/negative_test/playbooks/var_files_number.yml @@ -0,0 +1,4 @@ +--- +- name: var_files should not accept number + hosts: localhost + vars_files: 0 diff --git a/test/schemas/negative_test/playbooks/var_files_number.yml.md b/test/schemas/negative_test/playbooks/var_files_number.yml.md new file mode 100644 index 0000000..fa97e7e --- /dev/null +++ b/test/schemas/negative_test/playbooks/var_files_number.yml.md @@ -0,0 +1,122 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/vars_files", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/patternProperties/vars/type" + }, + { + "instancePath": "/0/vars_files", + "keyword": "type", + "message": "must be array,string,null", + "params": { + "type": [ + "array", + "string", + "null" + ] + }, + "schemaPath": "#/properties/vars_files/type" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/var_files_number.yml", + "path": "$[0]", + "message": "{'name': 'var_files should not accept number', 'hosts': 'localhost', 'vars_files': 0} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'name': 'var_files should not accept number', 'hosts': 'localhost', 'vars_files': 0} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].vars_files", + "message": "0 is not of type 'object'" + }, + { + "path": "$[0].vars_files", + "message": "0 is not of type 'array', 'string', 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/asterisk.yml b/test/schemas/negative_test/playbooks/vars/asterisk.yml new file mode 100644 index 0000000..9dd2200 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/asterisk.yml @@ -0,0 +1,2 @@ +--- +"*foo": ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/asterisk.yml.md b/test/schemas/negative_test/playbooks/vars/asterisk.yml.md new file mode 100644 index 0000000..1ea9a98 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/asterisk.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "*foo" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/asterisk.yml", + "path": "$", + "message": "{'*foo': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'*foo': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'*foo' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'*foo': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'*foo': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml new file mode 100644 index 0000000..216de64 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml @@ -0,0 +1,2 @@ +--- +foo-bar: ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md new file mode 100644 index 0000000..b862e69 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "foo-bar" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/dash-in-var-name.yml", + "path": "$", + "message": "{'foo-bar': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'foo-bar': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'foo-bar' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'foo-bar': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'foo-bar': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/list.yml b/test/schemas/negative_test/playbooks/vars/list.yml new file mode 100644 index 0000000..909a4d7 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/list.yml @@ -0,0 +1,3 @@ +# invalid vars file, as sequence is not allowed +- foo +- bar diff --git a/test/schemas/negative_test/playbooks/vars/list.yml.md b/test/schemas/negative_test/playbooks/vars/list.yml.md new file mode 100644 index 0000000..e2c9bf5 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/list.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/anyOf/0/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/list.yml", + "path": "$", + "message": "['foo', 'bar'] is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "['foo', 'bar'] is not of type 'object'" + }, + "sub_errors": [ + { + "path": "$", + "message": "['foo', 'bar'] is not of type 'object'" + }, + { + "path": "$", + "message": "['foo', 'bar'] is not of type 'string'" + }, + { + "path": "$", + "message": "['foo', 'bar'] is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml new file mode 100644 index 0000000..826150d --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml @@ -0,0 +1,2 @@ +--- +12: ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md new file mode 100644 index 0000000..7ddcff6 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "12" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/numeric-var-name.yml", + "path": "$", + "message": "{'12': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'12': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'12' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'12': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'12': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/play-keyword.yml b/test/schemas/negative_test/playbooks/vars/play-keyword.yml new file mode 100644 index 0000000..7d277ed --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/play-keyword.yml @@ -0,0 +1,2 @@ +--- +environment: ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md b/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md new file mode 100644 index 0000000..6b88b2a --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "environment" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/play-keyword.yml", + "path": "$", + "message": "{'environment': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'environment': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'environment' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'environment': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'environment': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/python-keyword.yml b/test/schemas/negative_test/playbooks/vars/python-keyword.yml new file mode 100644 index 0000000..7b9d01d --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/python-keyword.yml @@ -0,0 +1,3 @@ +--- +async: ... # invalid var name +lambda: ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md b/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md new file mode 100644 index 0000000..ca42f74 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md @@ -0,0 +1,86 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "async" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "lambda" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/python-keyword.yml", + "path": "$", + "message": "{'async': '...', 'lambda': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'async': '...', 'lambda': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'async', 'lambda' do not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'async': '...', 'lambda': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'async': '...', 'lambda': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml new file mode 100644 index 0000000..5f97995 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml @@ -0,0 +1,2 @@ +--- +5foo: ... # invalid var name diff --git a/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md new file mode 100644 index 0000000..8b73b0a --- /dev/null +++ b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md @@ -0,0 +1,77 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "5foo" + }, + "schemaPath": "#/anyOf/0/additionalProperties" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/anyOf/1/type" + }, + { + "instancePath": "", + "keyword": "type", + "message": "must be null", + "params": { + "type": "null" + }, + "schemaPath": "#/anyOf/2/type" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vars/varname-numeric-prefix.yml", + "path": "$", + "message": "{'5foo': '...'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'5foo': '...'} is not of type 'string'" + }, + "sub_errors": [ + { + "path": "$", + "message": "'5foo' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + { + "path": "$", + "message": "{'5foo': '...'} is not of type 'string'" + }, + { + "path": "$", + "message": "{'5foo': '...'} is not of type 'null'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/vas_prompt.yml b/test/schemas/negative_test/playbooks/vas_prompt.yml new file mode 100644 index 0000000..a90d131 --- /dev/null +++ b/test/schemas/negative_test/playbooks/vas_prompt.yml @@ -0,0 +1,7 @@ +- hosts: localhost + vars_prompt: + - name: username + prompt: What is your username? + private: false + tags: # tags were never supported, https://github.com/ansible/ansible/issues/1780 + - foo diff --git a/test/schemas/negative_test/playbooks/vas_prompt.yml.md b/test/schemas/negative_test/playbooks/vas_prompt.yml.md new file mode 100644 index 0000000..d2d809d --- /dev/null +++ b/test/schemas/negative_test/playbooks/vas_prompt.yml.md @@ -0,0 +1,118 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/vars_prompt", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/patternProperties/vars/type" + }, + { + "instancePath": "/0/vars_prompt/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tags" + }, + "schemaPath": "#/$defs/vars_prompt/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/playbooks/vas_prompt.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars_prompt': [{'name': 'username', 'prompt': 'What is your username?', 'private': False, 'tags': ['foo']}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'vars_prompt': [{'name': 'username', 'prompt': 'What is your username?', 'private': False, 'tags': ['foo']}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].vars_prompt", + "message": "[{'name': 'username', 'prompt': 'What is your username?', 'private': False, 'tags': ['foo']}] is not of type 'object'" + }, + { + "path": "$[0].vars_prompt[0]", + "message": "Additional properties are not allowed ('tags' was unexpected)" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/reqs3/meta/requirements.yml b/test/schemas/negative_test/reqs3/meta/requirements.yml new file mode 100644 index 0000000..f28aebb --- /dev/null +++ b/test/schemas/negative_test/reqs3/meta/requirements.yml @@ -0,0 +1,2 @@ +# this should fail validation +foo: bar diff --git a/test/schemas/negative_test/reqs3/meta/requirements.yml.md b/test/schemas/negative_test/reqs3/meta/requirements.yml.md new file mode 100644 index 0000000..5de6643 --- /dev/null +++ b/test/schemas/negative_test/reqs3/meta/requirements.yml.md @@ -0,0 +1,101 @@ +# ajv errors + +```json +[ + { + "instancePath": "", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/anyOf/0/type" + }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'collections'", + "params": { + "missingProperty": "collections" + }, + "schemaPath": "#/anyOf/0/required" + }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'roles'", + "params": { + "missingProperty": "roles" + }, + "schemaPath": "#/anyOf/1/required" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + }, + { + "instancePath": "", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "foo" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/reqs3/meta/requirements.yml", + "path": "$", + "message": "{'foo': 'bar'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$", + "message": "{'foo': 'bar'} is not of type 'array'" + }, + "sub_errors": [ + { + "path": "$", + "message": "{'foo': 'bar'} is not of type 'array'" + }, + { + "path": "$", + "message": "Additional properties are not allowed ('foo' was unexpected)" + }, + { + "path": "$", + "message": "{'foo': 'bar'} is not valid under any of the given schemas" + }, + { + "path": "$", + "message": "'collections' is a required property" + }, + { + "path": "$", + "message": "'roles' is a required property" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/meta/argument_specs.yml b/test/schemas/negative_test/roles/meta/argument_specs.yml new file mode 100644 index 0000000..ddc9862 --- /dev/null +++ b/test/schemas/negative_test/roles/meta/argument_specs.yml @@ -0,0 +1,5 @@ +--- +argument_specs: + main: + foo: bar # <-- invalid based on json schema + options: {} diff --git a/test/schemas/negative_test/roles/meta/argument_specs.yml.md b/test/schemas/negative_test/roles/meta/argument_specs.yml.md new file mode 100644 index 0000000..34da932 --- /dev/null +++ b/test/schemas/negative_test/roles/meta/argument_specs.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/argument_specs/main", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "foo" + }, + "schemaPath": "#/additionalProperties" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/meta/argument_specs.yml", + "path": "$.argument_specs.main", + "message": "Additional properties are not allowed ('foo' was unexpected)", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/meta/main.yml b/test/schemas/negative_test/roles/meta/main.yml new file mode 100644 index 0000000..3ed9a8c --- /dev/null +++ b/test/schemas/negative_test/roles/meta/main.yml @@ -0,0 +1,10 @@ +galaxy_info: + description: bar + min_ansible_version: "2.9" + company: foo + license: MIT + galaxy_tags: database # <-- invalid, must be a list of strings + platforms: + - name: Alpine + versions: + - all diff --git a/test/schemas/negative_test/roles/meta/main.yml.md b/test/schemas/negative_test/roles/meta/main.yml.md new file mode 100644 index 0000000..2c9e99b --- /dev/null +++ b/test/schemas/negative_test/roles/meta/main.yml.md @@ -0,0 +1,58 @@ +# ajv errors + +```json +[ + { + "instancePath": "/galaxy_info", + "keyword": "required", + "message": "must have required property 'author'", + "params": { + "missingProperty": "author" + }, + "schemaPath": "#/allOf/0/then/required" + }, + { + "instancePath": "/galaxy_info", + "keyword": "if", + "message": "must match \"then\" schema", + "params": { + "failingKeyword": "then" + }, + "schemaPath": "#/allOf/0/if" + }, + { + "instancePath": "/galaxy_info/galaxy_tags", + "keyword": "type", + "message": "must be array", + "params": { + "type": "array" + }, + "schemaPath": "#/properties/galaxy_tags/type" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/meta/main.yml", + "path": "$.galaxy_info", + "message": "'author' is a required property", + "has_sub_errors": false + }, + { + "filename": "negative_test/roles/meta/main.yml", + "path": "$.galaxy_info.galaxy_tags", + "message": "'database' is not of type 'array'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml new file mode 100644 index 0000000..1fa41eb --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml @@ -0,0 +1,10 @@ +collections: + - foo # invalid pattern +galaxy_info: + standalone: false # role inside a collection + description: foo + license: bar + platforms: + - name: Fedora + versions: + - all diff --git a/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md new file mode 100644 index 0000000..1b8dcd0 --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/collections/0", + "keyword": "pattern", + "message": "must match pattern \"^[a-z_]+\\.[a-z_]+$\"", + "params": { + "pattern": "^[a-z_]+\\.[a-z_]+$" + }, + "schemaPath": "#/$defs/collections/items/pattern" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/meta_invalid_collection/meta/main.yml", + "path": "$.collections[0]", + "message": "'foo' does not match '^[a-z_]+\\\\.[a-z_]+$'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml new file mode 100644 index 0000000..488928c --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml @@ -0,0 +1,11 @@ +# role inside a collection +collections: + - FOO.BAR # invalid pattern, need to use lowercase +galaxy_info: + standalone: false + description: foo + license: bar + platforms: + - name: Fedora + versions: + - all diff --git a/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md new file mode 100644 index 0000000..5d775f0 --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md @@ -0,0 +1,34 @@ +# ajv errors + +```json +[ + { + "instancePath": "/collections/0", + "keyword": "pattern", + "message": "must match pattern \"^[a-z_]+\\.[a-z_]+$\"", + "params": { + "pattern": "^[a-z_]+\\.[a-z_]+$" + }, + "schemaPath": "#/$defs/collections/items/pattern" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/meta_invalid_collections/meta/main.yml", + "path": "$.collections[0]", + "message": "'FOO.BAR' does not match '^[a-z_]+\\\\.[a-z_]+$'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml new file mode 100644 index 0000000..e50e5b7 --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml @@ -0,0 +1,12 @@ +--- +# old standalone role +galaxy_info: + description: foo + min_ansible_version: "2.9" + namespace: foo-bar + company: foo + license: MIT + platforms: + - name: Alpine + versions: + - all diff --git a/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md new file mode 100644 index 0000000..ad7e9d3 --- /dev/null +++ b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md @@ -0,0 +1,58 @@ +# ajv errors + +```json +[ + { + "instancePath": "/galaxy_info", + "keyword": "required", + "message": "must have required property 'author'", + "params": { + "missingProperty": "author" + }, + "schemaPath": "#/allOf/0/then/required" + }, + { + "instancePath": "/galaxy_info", + "keyword": "if", + "message": "must match \"then\" schema", + "params": { + "failingKeyword": "then" + }, + "schemaPath": "#/allOf/0/if" + }, + { + "instancePath": "/galaxy_info/namespace", + "keyword": "pattern", + "message": "must match pattern \"^[a-z][a-z0-9_]+$\"", + "params": { + "pattern": "^[a-z][a-z0-9_]+$" + }, + "schemaPath": "#/properties/namespace/pattern" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/meta_invalid_role_namespace/meta/main.yml", + "path": "$.galaxy_info", + "message": "'author' is a required property", + "has_sub_errors": false + }, + { + "filename": "negative_test/roles/meta_invalid_role_namespace/meta/main.yml", + "path": "$.galaxy_info.namespace", + "message": "'foo-bar' does not match '^[a-z][a-z0-9_]+$'", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml new file mode 100644 index 0000000..81d4d3d --- /dev/null +++ b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml @@ -0,0 +1,13 @@ +# old standalone role +galaxy_info: + description: bar + min_ansible_version: "2.9" + company: foo + license: MIT + platforms: + - name: Alpine + versions: + - all + +dependencies: + - version: foo # invalid, should have at least name, role or src properties diff --git a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md new file mode 100644 index 0000000..f09b1ac --- /dev/null +++ b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md @@ -0,0 +1,101 @@ +# ajv errors + +```json +[ + { + "instancePath": "/dependencies/0", + "keyword": "required", + "message": "must have required property 'role'", + "params": { + "missingProperty": "role" + }, + "schemaPath": "#/anyOf/0/required" + }, + { + "instancePath": "/dependencies/0", + "keyword": "required", + "message": "must have required property 'src'", + "params": { + "missingProperty": "src" + }, + "schemaPath": "#/anyOf/1/required" + }, + { + "instancePath": "/dependencies/0", + "keyword": "required", + "message": "must have required property 'name'", + "params": { + "missingProperty": "name" + }, + "schemaPath": "#/anyOf/2/required" + }, + { + "instancePath": "/dependencies/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/anyOf" + }, + { + "instancePath": "/galaxy_info", + "keyword": "required", + "message": "must have required property 'author'", + "params": { + "missingProperty": "author" + }, + "schemaPath": "#/allOf/0/then/required" + }, + { + "instancePath": "/galaxy_info", + "keyword": "if", + "message": "must match \"then\" schema", + "params": { + "failingKeyword": "then" + }, + "schemaPath": "#/allOf/0/if" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "errors": [ + { + "filename": "negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml", + "path": "$.dependencies[0]", + "message": "{'version': 'foo'} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$.dependencies[0]", + "message": "'role' is a required property" + }, + "sub_errors": [ + { + "path": "$.dependencies[0]", + "message": "'role' is a required property" + }, + { + "path": "$.dependencies[0]", + "message": "'src' is a required property" + }, + { + "path": "$.dependencies[0]", + "message": "'name' is a required property" + } + ] + }, + { + "filename": "negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml", + "path": "$.galaxy_info", + "message": "'author' is a required property", + "has_sub_errors": false + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/package-lock.json b/test/schemas/package-lock.json new file mode 100644 index 0000000..5e110a3 --- /dev/null +++ b/test/schemas/package-lock.json @@ -0,0 +1,2447 @@ +{ + "name": "schemas", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", + "js-yaml": "^4.1.0", + "safe-stable-stringify": "^2.4.2", + "ts-node": "^10.9.1", + "vscode-json-languageservice": "^5.2.0" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/js-yaml": "^4.0.5", + "@types/minimatch": "^5.1.2", + "@types/mocha": "^10.0.1", + "@types/node": "^18.13.0", + "chai": "^4.3.7", + "minimatch": "^6.2.0", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + }, + "node_modules/@vscode/l10n": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.11.tgz", + "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==" + }, + "node_modules/acorn": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dependencies": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "bin": { + "ajv": "dist/index.js" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/ajv-cli/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/ajv-cli/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dependencies": { + "fast-deep-equal": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fast-json-patch/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", + "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json5": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/minimatch": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/vscode-json-languageservice": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.2.0.tgz", + "integrity": "sha512-q8Rdhu2HEddRxvlhVqwh0cWmKK+OtyMB2xRhtqXEQ7cjb0iZ14madb90iJe9fCHPjoj9CGBrq6QzuOp8OE6XWg==", + "dependencies": { + "@vscode/l10n": "^0.0.11", + "jsonc-parser": "^3.2.0", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", + "vscode-uri": "^3.0.7" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", + "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" + }, + "node_modules/vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" + }, + "@vscode/l10n": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.11.tgz", + "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==" + }, + "acorn": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==" + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + }, + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "requires": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "requires": { + "fast-deep-equal": "^2.0.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", + "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "requires": { + "ajv": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "json5": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==" + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "minimatch": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-stable-stringify": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==" + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + } + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "vscode-json-languageservice": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.2.0.tgz", + "integrity": "sha512-q8Rdhu2HEddRxvlhVqwh0cWmKK+OtyMB2xRhtqXEQ7cjb0iZ14madb90iJe9fCHPjoj9CGBrq6QzuOp8OE6XWg==", + "requires": { + "@vscode/l10n": "^0.0.11", + "jsonc-parser": "^3.2.0", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", + "vscode-uri": "^3.0.7" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==" + }, + "vscode-languageserver-types": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", + "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" + }, + "vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/test/schemas/package.json b/test/schemas/package.json new file mode 100644 index 0000000..97a3850 --- /dev/null +++ b/test/schemas/package.json @@ -0,0 +1,29 @@ +{ + "dependencies": { + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", + "js-yaml": "^4.1.0", + "safe-stable-stringify": "^2.4.2", + "ts-node": "^10.9.1", + "vscode-json-languageservice": "^5.2.0" + }, + "scripts": { + "compile": "tsc -p ./src", + "deps": "npx --yes npm-check-updates -u && npm install --ignore-scripts", + "test": "python3 src/rebuild.py && mocha" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/js-yaml": "^4.0.5", + "@types/minimatch": "^5.1.2", + "@types/mocha": "^10.0.1", + "@types/node": "^18.13.0", + "chai": "^4.3.7", + "minimatch": "^6.2.0", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + }, + "directories": { + "test": "./src" + } +} diff --git a/test/schemas/src/rebuild.py b/test/schemas/src/rebuild.py new file mode 100644 index 0000000..7966ea5 --- /dev/null +++ b/test/schemas/src/rebuild.py @@ -0,0 +1,141 @@ +"""Utility to generate some complex patterns.""" +import copy +import json +import keyword +import sys +from typing import Any + +play_keywords = list( + filter( + None, + """\ +any_errors_fatal +become +become_exe +become_flags +become_method +become_user +check_mode +collections +connection +debugger +diff +environment +fact_path +force_handlers +gather_facts +gather_subset +gather_timeout +handlers +hosts +ignore_errors +ignore_unreachable +max_fail_percentage +module_defaults +name +no_log +order +port +post_tasks +pre_tasks +remote_user +roles +run_once +serial +strategy +tags +tasks +throttle +timeout +vars +vars_files +vars_prompt +""".split(), + ) +) + + +def is_ref_used(obj: Any, ref: str) -> bool: + """Return a reference use from a schema.""" + ref_use = f"#/$defs/{ref}" + if isinstance(obj, dict): + if obj.get("$ref", None) == ref_use: + return True + for _ in obj.values(): + if isinstance(_, (dict, list)): + if is_ref_used(_, ref): + return True + elif isinstance(obj, list): + for _ in obj: + if isinstance(_, (dict, list)): + if is_ref_used(_, ref): + return True + return False + + +if __name__ == "__main__": + invalid_var_names = sorted(list(keyword.kwlist) + play_keywords) + if "__peg_parser__" in invalid_var_names: + invalid_var_names.remove("__peg_parser__") + # flake8: noqa: T201 + print("Updating invalid var names") + + with open("f/vars.json", "r+", encoding="utf-8") as f: + vars_schema = json.load(f) + vars_schema["anyOf"][0]["patternProperties"] = { + f"^(?!({'|'.join(invalid_var_names)})$)[a-zA-Z_][\\w]*$": {} + } + f.seek(0) + json.dump(vars_schema, f, indent=2) + f.write("\n") + f.truncate() + + # flake8: noqa: T201 + print("Compiling subschemas...") + with open("f/ansible.json", encoding="utf-8") as f: + combined_json = json.load(f) + + for subschema in ["tasks", "playbook"]: + sub_json = copy.deepcopy(combined_json) + # remove unsafe keys from root + for key in [ + "$id", + "id", + "title", + "description", + "type", + "default", + "items", + "properties", + "additionalProperties", + "examples", + ]: + if key in sub_json: + del sub_json[key] + for key in sub_json: + if key not in ["$schema", "$defs"]: + print(f"Unexpected key found at combined schema root: ${key}") + sys.exit(2) + # Copy keys from subschema to root + for key, value in combined_json["$defs"][subschema].items(): + sub_json[key] = value + sub_json["$comment"] = "Generated from ansible.json, do not edit." + sub_json[ + "$id" + ] = f"https://raw.githubusercontent.com/ansible-lint/main/src/ansiblelint/schemas/{subschema}.json" + + # Remove all unreferenced ($ref) definitions ($defs) recursively + while True: + spare = [] + for k in sub_json["$defs"].keys(): + if not is_ref_used(sub_json, k): + spare.append(k) + for k in spare: + print(f"{subschema}: deleting unused '{k}' definition") + del sub_json["$defs"][k] + if not spare: + break + + with open(f"f/{subschema}.json", "w", encoding="utf-8") as f: + json.dump(sub_json, f, indent=2, sort_keys=True) + f.write("\n") diff --git a/test/schemas/src/schema.spec.ts b/test/schemas/src/schema.spec.ts new file mode 100644 index 0000000..774bae6 --- /dev/null +++ b/test/schemas/src/schema.spec.ts @@ -0,0 +1,184 @@ +import * as path from "path"; +import Ajv from "ajv"; +import fs from "fs"; +import minimatch from "minimatch"; +import yaml from "js-yaml"; +import { assert } from "chai"; +import stringify from "safe-stable-stringify"; +import { integer } from "vscode-languageserver-types"; +import { exec } from "child_process"; +const spawnSync = require("child_process").spawnSync; + +function ansiRegex({ onlyFirst = false } = {}) { + const pattern = [ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", + ].join("|"); + + return new RegExp(pattern, onlyFirst ? undefined : "g"); +} + +function stripAnsi(data: string) { + if (typeof data !== "string") { + throw new TypeError( + `Expected a \`string\`, got \`${typeof data}\ = ${data}` + ); + } + return data.replace(ansiRegex(), ""); +} + +const ajv = new Ajv({ + strictTypes: false, + strict: false, + inlineRefs: true, // https://github.com/ajv-validator/ajv/issues/1581#issuecomment-832211568 + allErrors: true, // https://github.com/ajv-validator/ajv/issues/1581#issuecomment-832211568 +}); + +// load whitelist of all test file subjects schemas can reference +const test_files = getAllFiles("./test"); +const negative_test_files = getAllFiles("./negative_test"); + +// load all schemas +const schema_files = fs + .readdirSync("f/") + .filter((el) => path.extname(el) === ".json"); +console.log(`Schemas: ${schema_files}`); + +describe("schemas under f/", function () { + schema_files.forEach((schema_file) => { + if ( + schema_file.startsWith("_") || + schema_file == "ansible-navigator-config.json" + ) { + return; + } + const schema_json = JSON.parse(fs.readFileSync(`f/${schema_file}`, "utf8")); + ajv.addSchema(schema_json); + const validator = ajv.compile(schema_json); + if (schema_json.examples == undefined) { + console.error( + `Schema file ${schema_file} is missing an examples key that we need for documenting file matching patterns.` + ); + return process.exit(1); + } + describe(schema_file, function () { + getTestFiles(schema_json.examples).forEach( + ({ file: test_file, expect_fail }) => { + it(`linting ${test_file} using ${schema_file}`, function () { + var errors_md = ""; + const result = validator( + yaml.load(fs.readFileSync(test_file, "utf8")) + ); + if (validator.errors) { + errors_md += "# ajv errors\n\n```json\n"; + errors_md += stringify(validator.errors, null, 2); + errors_md += "\n```\n\n"; + } + // validate using check-jsonschema (python-jsonschema): + // const py = exec(); + // Do not use python -m ... calling notation because for some + // reason, nodejs environment lacks some env variables needed + // and breaks usage from inside virtualenvs. + const proc = spawnSync( + `${process.env.VIRTUAL_ENV}/bin/check-jsonschema -v -o json --schemafile f/${schema_file} ${test_file}`, + { shell: true, encoding: "utf-8", stdio: "pipe" } + ); + if (proc.status != 0) { + // real errors are sent to stderr due to https://github.com/python-jsonschema/check-jsonschema/issues/88 + errors_md += "# check-jsonschema\n\nstdout:\n\n```json\n"; + errors_md += stripAnsi(proc.output[1]); + errors_md += "```\n"; + if (proc.output[2]) { + errors_md += "\nstderr:\n\n```\n"; + errors_md += stripAnsi(proc.output[2]); + errors_md += "```\n"; + } + } + + // dump errors to markdown file for manual inspection + const md_filename = `${test_file}.md`; + if (errors_md) { + fs.writeFileSync(md_filename, errors_md); + } else { + // if no error occurs, we should ensure there is no md file present + fs.unlink(md_filename, function (err) { + if (err && err.code != "ENOENT") { + console.error(`Failed to remove ${md_filename}.`); + } + }); + } + assert.equal( + result, + !expect_fail, + `${JSON.stringify(validator.errors)}` + ); + }); + } + ); + // All /$defs/ that have examples property are assumed to be + // subschemas, "tasks" being the primary such case, which is also used + // for validating separated files. + for (var definition in schema_json["$defs"]) { + if (schema_json["$defs"][definition].examples) { + const subschema_uri = `${schema_json["$id"]}#/$defs/${definition}`; + const subschema_validator = ajv.getSchema(subschema_uri); + if (!subschema_validator) { + console.error(`Failed to load subschema ${subschema_uri}`); + return process.exit(1); + } + getTestFiles(schema_json["$defs"][definition].examples).forEach( + ({ file: test_file, expect_fail }) => { + it(`linting ${test_file} using ${subschema_uri}`, function () { + const result = subschema_validator( + yaml.load(fs.readFileSync(test_file, "utf8")) + ); + assert.equal( + result, + !expect_fail, + `${JSON.stringify(validator.errors)}` + ); + }); + } + ); + } + } + }); + }); +}); + +// find all tests for each schema file +function getTestFiles( + globs: string[] +): { file: string; expect_fail: boolean }[] { + const files = Array.from( + new Set( + globs + .map((glob: any) => minimatch.match(test_files, path.join("**", glob))) + .flat() + ) + ); + const negative_files = Array.from( + new Set( + globs + .map((glob: any) => + minimatch.match(negative_test_files, path.join("**", glob)) + ) + .flat() + ) + ); + + // All fails ending with fail, like `foo.fail.yml` are expected to fail validation + let result = files.map((f) => ({ file: f, expect_fail: false })); + result = result.concat( + negative_files.map((f) => ({ file: f, expect_fail: true })) + ); + return result; +} + +function getAllFiles(dir: string): string[] { + return fs.readdirSync(dir).reduce((files: string[], file: string) => { + const name = path.join(dir, file); + const isDirectory = fs.statSync(name).isDirectory(); + return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name]; + }, []); +} diff --git a/test/schemas/test/ansible-navigator.yml b/test/schemas/test/ansible-navigator.yml new file mode 100644 index 0000000..e627b78 --- /dev/null +++ b/test/schemas/test/ansible-navigator.yml @@ -0,0 +1,85 @@ +--- +ansible-navigator: + ansible: + config: /tmp/ansible.cfg + cmdline: "--forks 15" + inventories: + - /tmp/test_inventory.yml + playbook: /tmp/test_playbook.yml + + ansible-builder: + workdir: /tmp/ + + ansible-runner: + artifact-dir: /tmp/test1 + rotate-artifacts-count: 10 + timeout: 300 + + app: run + + collection-doc-cache-path: /tmp/cache.db + + color: + enable: False + osc4: False + + documentation: + plugin: + name: shell + type: become + + editor: + command: vim_from_setting + console: False + + exec: + shell: False + command: /bin/foo + + execution-environment: + container-engine: podman + enabled: False + environment-variables: + pass: + - ONE + - TWO + - THREE + set: + KEY1: VALUE1 + KEY2: VALUE2 + KEY3: VALUE3 + image: test_image:latest + pull-policy: never + volume-mounts: + - src: "/test1" + dest: "/test1" + label: "Z" + container-options: + - "--net=host" + + help-builder: False + + help-config: True + + help-doc: True + + help-inventory: True + + help-playbook: False + + inventory-columns: + - ansible_network_os + - ansible_network_cli_ssh_type + - ansible_connection + + logging: + level: critical + append: False + file: /tmp/log.txt + + mode: stdout + + playbook-artifact: + enable: True + replay: /tmp/test_artifact.json + save-as: /tmp/test_artifact.json diff --git a/test/schemas/test/changelog.yml b/test/schemas/test/changelog.yml new file mode 100644 index 0000000..99bcb2f --- /dev/null +++ b/test/schemas/test/changelog.yml @@ -0,0 +1,47 @@ +ancestor: 0.5.4 +releases: + 1.0.0-alpha: + release_date: "2020-01-01" + codename: "The first public one" + changes: + release_summary: A bit o markdown text + major_changes: + - Free form text mentioning a major change + minor_changes: + - Free form text mentioning a minor change + breaking_changes: + - Free form text mentioning a breaking change + deprecated_features: + - A list of strings describing features deprecated in this release + removed_features: + - A list of strings describing features removed in this release + security_fixes: + - A list of strings describing security-relevant bugfixes + bugfixes: + - Fixed bug `#1 <https://example.com>` + known_issues: + - A list of strings describing known issues that are currently not fixed or will not be fixed + trivial: + - A list of strings describing changes that are too trivial to show in the changelog + modules: + - name: short_module_name + description: foo + namespace: foo + plugins: + lookup: + - name: reverse + description: Reverse magic + namespace: null + inventory: + - name: docker + description: Inventory plugin for docker containers + namespace: null + objects: + role: + - name: install_reqs + description: Install all requirements of this collection + namespace: null + playbook: + - name: wipe_personal_data + description: Wipes all personal data from the database + namespace: null diff --git a/test/schemas/test/changelogs/maximal/changelog.yaml b/test/schemas/test/changelogs/maximal/changelog.yaml new file mode 100644 index 0000000..e21b349 --- /dev/null +++ b/test/schemas/test/changelogs/maximal/changelog.yaml @@ -0,0 +1,59 @@ +--- +# Example of minimal changelogs/changelog.yaml that is considered valid +ancestor: null + +releases: + 1.0.0-alpha: + release_date: "1980-01-01" + codename: foo + fragments: [] + changes: + release_summary: This is the initial White Rabbit release. Enjoy! + major_changes: + - The authentication method handling has been rewritten. + minor_changes: + - foo - Module can now reformat hard disks without asking. + - bob lookup - Makes sure Bob isn't there multiple times. + breaking_changes: + - Due to the security bug in the post module, the module no longer accepts the password + option. Please stop using the option and change any password you ever supplied to the + module. + deprecated_features: + - foo - The bar option has been deprecated. Use the username option instead. + - send_request - The quick option has been deprecated. Use the protocol option instead. + removed_features: + - foo - The baz option has been removed. It has never been used anyway. + security_fixes: + - post - The module accidentally sent your password in plaintext to all servers it could find. + bugfixes: + - post - The module made PUT requests instead of POST requests. + - get - The module will no longer crash if it received invalid JSON data + trivial: + - something that is not included in release notes + known_issues: + - som other + modules: + - name: head + description: Make a HEAD request + namespace: "net_tools.rest" + - name: echo + description: Echo params + namespace: "" + plugins: + lookup: + - name: reverse + description: Reverse magic + namespace: null + inventory: + - name: docker + description: Inventory plugin for docker containers + namespace: null + objects: + role: + - name: install_reqs + description: Install all requirements of this collection + namespace: null + playbook: + - name: wipe_personal_data + description: Wipes all personal data from the database + namespace: null diff --git a/test/schemas/test/changelogs/minimal/changelog.yaml b/test/schemas/test/changelogs/minimal/changelog.yaml new file mode 100644 index 0000000..d1618f0 --- /dev/null +++ b/test/schemas/test/changelogs/minimal/changelog.yaml @@ -0,0 +1,3 @@ +--- +# Example of minimal changelogs/changelog.yaml that is considered valid +releases: {} diff --git a/test/schemas/test/execution-environment.yml b/test/schemas/test/execution-environment.yml new file mode 100644 index 0000000..e447a9a --- /dev/null +++ b/test/schemas/test/execution-environment.yml @@ -0,0 +1,21 @@ +--- +# Example from https://docs.ansible.com/automation-controller/latest/html/userguide/ee_reference.html +version: 1 + +build_arg_defaults: + EE_BASE_IMAGE: "quay.io/ansible/ansible-runner:stable-2.10-devel" + +ansible_config: "ansible.cfg" + +dependencies: + galaxy: requirements.yml + python: requirements.txt + system: bindep.txt + +additional_build_steps: + prepend: | + RUN whoami + RUN cat /etc/os-release + append: + - RUN echo This is a post-install command! + - RUN ls -la /etc diff --git a/test/schemas/test/galaxy.yml b/test/schemas/test/galaxy.yml new file mode 100644 index 0000000..1c77216 --- /dev/null +++ b/test/schemas/test/galaxy.yml @@ -0,0 +1,13 @@ +name: foo +namespace: bar +version: 1.2.3 +authors: + - John +readme: ../README.md +description: ... +dependencies: + "other_namespace.collection1": ">=1.0.0" + "other_namespace.collection2": ">=2.0.0,<3.0.0" + "anderson55.my_collection": "*" # note: "*" selects the highest version available +# upload to galaxy will fail if a repository key is not present +repository: https://www.github.com/my_org/my_collection diff --git a/test/schemas/test/inventory.yml b/test/schemas/test/inventory.yml new file mode 100644 index 0000000..48a0e6a --- /dev/null +++ b/test/schemas/test/inventory.yml @@ -0,0 +1,13 @@ +all: + hosts: + mail.example.com: + children: + webservers: + hosts: + foo.example.com: + bar[01:50:2].example.com: + dbservers: + hosts: + one.example.com: + two.example.com: + three.example.com: diff --git a/test/schemas/test/inventory/inventory.yml b/test/schemas/test/inventory/inventory.yml new file mode 100644 index 0000000..8752d9b --- /dev/null +++ b/test/schemas/test/inventory/inventory.yml @@ -0,0 +1,31 @@ +--- +# https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html +ungrouped: {} +all: + hosts: + mail.example.com: + children: + webservers: + hosts: + foo.example.com: + bar.example.com: + dbservers: + hosts: + one.example.com: + two.example.com: + three.example.com: + east: + hosts: + foo.example.com: + one.example.com: + two.example.com: + west: + hosts: + bar.example.com: + three.example.com: + prod: + children: + east: {} + test: + children: + west: {} diff --git a/test/schemas/test/inventory/production.yml b/test/schemas/test/inventory/production.yml new file mode 100644 index 0000000..6350bda --- /dev/null +++ b/test/schemas/test/inventory/production.yml @@ -0,0 +1,37 @@ +all: + hosts: + mail.example.com: + children: + webservers: + hosts: + foo.example.com: + bar.example.com: + # ranges are supported: + www[01:50].example.com: + www[01:50:2].example.com: + # these are variables: + var_1: value_1 + another_var: 200 + dbservers: + hosts: + one.example.com: + two.example.com: + three.example.com: + east: + hosts: + foo.example.com: + one.example.com: + two.example.com: + west: + hosts: + bar.example.com: + three.example.com: + prod: + children: + east: + test: + children: + west: + # add variables for all hosts + vars: + my_var: 123 diff --git a/test/schemas/test/meta/requirements.yml b/test/schemas/test/meta/requirements.yml new file mode 100644 index 0000000..6b07e4f --- /dev/null +++ b/test/schemas/test/meta/requirements.yml @@ -0,0 +1,3 @@ +# requirements v2 +collections: [] +roles: [] diff --git a/test/schemas/test/molecule/cluster/base.yml b/test/schemas/test/molecule/cluster/base.yml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/schemas/test/molecule/cluster/base.yml diff --git a/test/schemas/test/molecule/cluster/converge.yml b/test/schemas/test/molecule/cluster/converge.yml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/schemas/test/molecule/cluster/converge.yml diff --git a/test/schemas/test/molecule/cluster/foobar.yml b/test/schemas/test/molecule/cluster/foobar.yml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/schemas/test/molecule/cluster/foobar.yml diff --git a/test/schemas/test/molecule/cluster/molecule.yml b/test/schemas/test/molecule/cluster/molecule.yml new file mode 100644 index 0000000..bf40d14 --- /dev/null +++ b/test/schemas/test/molecule/cluster/molecule.yml @@ -0,0 +1,77 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +lint: | + set -e + yamllint -c molecule/yaml-lint.yml . + ansible-lint + flake8 + +platforms: + - name: instance-1 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + pre_build_image: true + groups: + - zookeeper + env: + - Hello: world! + + - name: instance-2 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + pre_build_image: true + groups: + - zookeeper + env: + - Hello: world! + + - name: instance-3 + image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + pre_build_image: true + groups: + - zookeeper + env: + - Hello: world! + +provisioner: + name: ansible + log: false + playbooks: + converge: ${MOLECULE_PLAYBOOK:-converge.yml} + inventory: + host_vars: + instance-1: + zookeeper_id: 0 + instance-2: + zookeeper_id: 1 + instance-3: + zookeeper_id: 2 + +scenario: + name: cluster + test_sequence: + - destroy + - create + - prepare + - converge + - check + - verify + - destroy + +verifier: + name: ansible diff --git a/test/schemas/test/molecule/default/molecule.yml b/test/schemas/test/molecule/default/molecule.yml new file mode 100644 index 0000000..b3e290d --- /dev/null +++ b/test/schemas/test/molecule/default/molecule.yml @@ -0,0 +1,118 @@ +--- +dependency: + name: shell + enabled: true + command: path/to/command --flag1 subcommand --flag2 + options: + ignore-certs: true + ignore-errors: true + env: + FOO: bar + +lint: | + set -e + yamllint . + ansible-lint + flake8 + +driver: + name: podman + options: + managed: false + login_cmd_template: ... + ansible_connection_options: + ansible_connection: ssh + # vagrant options: + provider: + name: virtualbox + +log: true + +platforms: + - name: ubi8 + hostname: ubi8 + children: [] # list of strings + unknown_property_foo: bar # unknown properties should be allowed for drivers + groups: + - ubi8 + image: ubi8/ubi-init + pre_build_image: true + registry: + url: registry.access.redhat.com + dockerfile: Dockerfile + pkg_extras: python*setuptools + volumes: + - /etc/ci/mirror_info.sh:/etc/ci/mirror_info.sh:ro + - /etc/pki/rpm-gpg:/etc/pki/rpm-gpg + privileged: true + environment: &env + http_proxy: "{{ lookup('env', 'http_proxy') }}" + https_proxy: "{{ lookup('env', 'https_proxy') }}" + ulimits: &ulimit + - host + # vagrant ones + box: foo/bar + memory: 1024 + cpus: 2 + provider_raw_config_args: [] + networks: # used by docker/podman + - name: foo + + - name: ubi7 + hostname: ubi7 + children: ["ubi8"] + groups: + - ubi7 + image: ubi7/ubi-init + registry: + url: registry.access.redhat.com + command: /sbin/init + tmpfs: + - /run + - /tmp + volumes: + - /etc/ci/mirror_info.sh:/etc/ci/mirror_info.sh:ro + - /etc/pki/rpm-gpg:/etc/pki/rpm-gpg + - /sys/fs/cgroup:/sys/fs/cgroup:ro + network_mode: service:vpn + privileged: true + environment: &env + http_proxy: "{{ lookup('env', 'http_proxy') }}" + https_proxy: "{{ lookup('env', 'https_proxy') }}" + ulimits: &ulimit + - host + +provisioner: + playbooks: + prepare: prepare.yml + inventory: + hosts: + all: + hosts: + ubi8: + ansible_python_interpreter: /usr/bin/python3 + ubi7: + selinux: permissive + ubi8: + selinux: enforced + name: ansible + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml + config_options: + defaults: + fact_caching: jsonfile + fact_caching_connection: /tmp/molecule/facts + +scenario: + test_sequence: + - destroy + - create + - prepare + - converge + - check + - verify + - destroy + +verifier: + name: testinfra diff --git a/test/schemas/test/molecule/vagrant/molecule.yml b/test/schemas/test/molecule/vagrant/molecule.yml new file mode 100644 index 0000000..5630df6 --- /dev/null +++ b/test/schemas/test/molecule/vagrant/molecule.yml @@ -0,0 +1,47 @@ +--- +dependency: + name: shell + enabled: false + +lint: | + set -e + yamllint . + ansible-lint + flake8 + +driver: + name: vagrant + provider: + name: libvirt + provision: false + cachier: machine + parallel: true + default_box: "generic/alpine310" +platforms: + - name: instance + hostname: foo.bar.com + interfaces: + - auto_config: true + network_name: private_network + type: dhcp + instance_raw_config_args: + - 'vm.synced_folder ".", "/vagrant", type: "rsync"' + - 'vm.provision :shell, inline: "uname"' + config_options: + ssh.keep_alive: true + ssh.remote_user: "vagrant" + synced_folder: true + box: fedora/32-cloud-base + box_version: 32.20200422.0 + box_url: "http://127.0.0.1/box.img" + memory: 512 + cpus: 1 + provider_options: + video_type: "vga" + provider_raw_config_args: + - cpuset = '1-4,^3,6' + - name: instance2 + hostname: false + +provisioner: + name: ansible diff --git a/test/schemas/test/playbooks/block.yml b/test/schemas/test/playbooks/block.yml new file mode 100644 index 0000000..631242b --- /dev/null +++ b/test/schemas/test/playbooks/block.yml @@ -0,0 +1,10 @@ +- hosts: localhost + tasks: + - debug: + msg: task under no block + - block: + - debug: + msg: task under one level of block + - block: + - debug: + msg: task under two levels of block diff --git a/test/schemas/test/playbooks/defaults/foo.yml b/test/schemas/test/playbooks/defaults/foo.yml new file mode 100644 index 0000000..47d9438 --- /dev/null +++ b/test/schemas/test/playbooks/defaults/foo.yml @@ -0,0 +1,3 @@ +# defaults have same format as vars +in_is_reserved: ... +ss: ss diff --git a/test/schemas/test/playbooks/environment.yml b/test/schemas/test/playbooks/environment.yml new file mode 100644 index 0000000..d25fd1b --- /dev/null +++ b/test/schemas/test/playbooks/environment.yml @@ -0,0 +1,7 @@ +--- +- hosts: localhost + environment: # <- valid + FOO: BAR + +- hosts: localhost + environment: "{{ foo }}" # <- valid diff --git a/test/schemas/test/playbooks/failed_when.yml b/test/schemas/test/playbooks/failed_when.yml new file mode 100644 index 0000000..14c942a --- /dev/null +++ b/test/schemas/test/playbooks/failed_when.yml @@ -0,0 +1,18 @@ +- hosts: localhost + tasks: + - name: foo + ansible.builtin.debug: + msg: foo! + failed_when: false # <- valid + + - name: foo + ansible.builtin.debug: + msg: foo! + failed_when: "string is valid too" # <- valid + + - name: foo + ansible.builtin.debug: + msg: foo! + failed_when: # <- lists are valid too + - foo + - bar diff --git a/test/schemas/test/playbooks/full-jinja.yml b/test/schemas/test/playbooks/full-jinja.yml new file mode 100644 index 0000000..22eaafe --- /dev/null +++ b/test/schemas/test/playbooks/full-jinja.yml @@ -0,0 +1,16 @@ +--- +- name: Test that schema allows multiline-jinja + hosts: localhost + # https://github.com/ansible/ansible-lint/issues/2772 + become: >- + {{ + true + }} + tasks: + - name: Test more complex jinja is also allowed + ansible.builtin.debug: + msg: "{{ item }}" + # that below is valid and show be allowed: + with_items: >- + {%- set ns = [1, 1, 2] -%} + {{- ns | unique -}} diff --git a/test/schemas/test/playbooks/gather_facts.yml b/test/schemas/test/playbooks/gather_facts.yml new file mode 100644 index 0000000..598188d --- /dev/null +++ b/test/schemas/test/playbooks/gather_facts.yml @@ -0,0 +1,6 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - ansible.builtin.debug: + msg: foo diff --git a/test/schemas/test/playbooks/gather_subset.yml b/test/schemas/test/playbooks/gather_subset.yml new file mode 100644 index 0000000..de0e689 --- /dev/null +++ b/test/schemas/test/playbooks/gather_subset.yml @@ -0,0 +1,15 @@ +--- +- hosts: localhost + gather_subset: + - all + - "!network" + tasks: + - ansible.builtin.debug: + msg: foo + +- hosts: localhost + gather_subset: + - all + tasks: + - ansible.builtin.debug: + msg: bar diff --git a/test/schemas/test/playbooks/ignore_errors..yml b/test/schemas/test/playbooks/ignore_errors..yml new file mode 100644 index 0000000..6c92046 --- /dev/null +++ b/test/schemas/test/playbooks/ignore_errors..yml @@ -0,0 +1,9 @@ +- hosts: localhost + tasks: + - command: echo 123 + ignore_errors: true + + - command: echo 123 + vars: + should_ignore_errors: true + ignore_errors: "{{ should_ignore_errors }}" diff --git a/test/schemas/test/playbooks/import_playbook.yml b/test/schemas/test/playbooks/import_playbook.yml new file mode 100644 index 0000000..efd8787 --- /dev/null +++ b/test/schemas/test/playbooks/import_playbook.yml @@ -0,0 +1,9 @@ +- ansible.builtin.import_playbook: other.yml + +- import_playbook: other.yml + tags: + - foo + +- import_playbook: other.yml + when: + - foo is true diff --git a/test/schemas/test/playbooks/included.yml b/test/schemas/test/playbooks/included.yml new file mode 100644 index 0000000..468a17c --- /dev/null +++ b/test/schemas/test/playbooks/included.yml @@ -0,0 +1 @@ +- hosts: localhost diff --git a/test/schemas/test/playbooks/integers.yml b/test/schemas/test/playbooks/integers.yml new file mode 100644 index 0000000..861acee --- /dev/null +++ b/test/schemas/test/playbooks/integers.yml @@ -0,0 +1,23 @@ +--- +- hosts: localhost + vars: + some: 0 + gather_timeout: "{{ some }}" + tasks: + - ansible.builtin.debug: + msg: "{{ item }}" + async: 0 + poll: 0 + delay: 0 + timeout: 0 + port: 0 + - ansible.builtin.debug: + msg: "{{ item }}" + async: "{{ some }}" + poll: "{{ some }}" + delay: "{{ some }}" + timeout: "{{ some }}" + port: "{{ some }}" + +- hosts: localhost + gather_timeout: 0 diff --git a/test/schemas/test/playbooks/local_action_dict.yml b/test/schemas/test/playbooks/local_action_dict.yml new file mode 100644 index 0000000..05b3129 --- /dev/null +++ b/test/schemas/test/playbooks/local_action_dict.yml @@ -0,0 +1,5 @@ +- hosts: localhost + tasks: + - local_action: + module: ansible.builtin.debug + msg: hello diff --git a/test/schemas/test/playbooks/local_action_string.yml b/test/schemas/test/playbooks/local_action_string.yml new file mode 100644 index 0000000..e7dacc4 --- /dev/null +++ b/test/schemas/test/playbooks/local_action_string.yml @@ -0,0 +1,3 @@ +- hosts: localhost + tasks: + - local_action: "ansible.builtin.debug msg=hello" diff --git a/test/schemas/test/playbooks/loop.yml b/test/schemas/test/playbooks/loop.yml new file mode 100644 index 0000000..c0e1734 --- /dev/null +++ b/test/schemas/test/playbooks/loop.yml @@ -0,0 +1,9 @@ +--- +- hosts: localhost + tasks: + - name: that should pass + ansible.builtin.debug: + var: item + loop: + - foo + - bar diff --git a/test/schemas/test/playbooks/no_log.yml b/test/schemas/test/playbooks/no_log.yml new file mode 100644 index 0000000..e1944dd --- /dev/null +++ b/test/schemas/test/playbooks/no_log.yml @@ -0,0 +1,11 @@ +- hosts: localhost + vars: + some_var: true + tasks: + - ansible.builtin.debug: + msg: foo + no_log: true + + - ansible.builtin.debug: + msg: foo + no_log: "{{ some_var }}" diff --git a/test/schemas/test/playbooks/roles.yml b/test/schemas/test/playbooks/roles.yml new file mode 100644 index 0000000..a996ce0 --- /dev/null +++ b/test/schemas/test/playbooks/roles.yml @@ -0,0 +1,13 @@ +- hosts: localhost + roles: [] + +- hosts: localhost + roles: + - foo + - role: "path/to/role" + vars: + FOO: bar + tags: + - foo + - role: bar + tags: string_tag diff --git a/test/schemas/test/playbooks/run.yml b/test/schemas/test/playbooks/run.yml new file mode 100644 index 0000000..52e7001 --- /dev/null +++ b/test/schemas/test/playbooks/run.yml @@ -0,0 +1,42 @@ +- name: foo + ansible.builtin.import_playbook: included.yml + +- hosts: # to check if lists are allowed: + - localhost + - webservers + # validate serial allows strings like percentage value + serial: 10% + handlers: + - name: handler 1 + ansible.builtin.debug: + msg: "I am handler 1" + listen: "always handler" + + - name: handler 2 + ansible.builtin.debug: + msg: "I am handler 2" + listen: # to check if lists are allowed: + - "list listening handler" + - "other listening topic" + +- hosts: localhost + serial: 1 # validate serial allows integer + +- hosts: localhost + serial: "{{ 1 }}" # jinja also ok + +- hosts: localhost + serial: # validate serial allows these too: + - 123 + - 10% + - "{{ some }}" # jinja also ok + +- hosts: localhost + tasks: + - debug: + msg: "failed_when should accept booleans" + failed_when: false + + - debug: + msg: "failed_when should allow strings" + failed_when: "'foo' in 'foobar'" diff --git a/test/schemas/test/playbooks/run_once.yml b/test/schemas/test/playbooks/run_once.yml new file mode 100644 index 0000000..be36c8e --- /dev/null +++ b/test/schemas/test/playbooks/run_once.yml @@ -0,0 +1,6 @@ +- hosts: localhost + tasks: + - name: foo2 + ansible.builtin.debug: + msg: foo! + run_once: "{{ true }}" # valid diff --git a/test/schemas/test/playbooks/tags.yml b/test/schemas/test/playbooks/tags.yml new file mode 100644 index 0000000..b758257 --- /dev/null +++ b/test/schemas/test/playbooks/tags.yml @@ -0,0 +1,23 @@ +- hosts: localhost + roles: + - role: foo + tags: foo # <-- allowed + - role: foo + tags: # <-- allowed + - foo + - bar + tags: # <-- allowed + - foo + - bar + tasks: + - ansible.builtin.debug: + msg: "..." + tags: # <-- allowed + - foo + - bar + - ansible.builtin.debug: + msg: "..." + tags: # <-- allowed + - foo +- hosts: localhost + tags: foo # <-- allowed diff --git a/test/schemas/test/playbooks/tasks.yml b/test/schemas/test/playbooks/tasks.yml new file mode 100644 index 0000000..b01cf8c --- /dev/null +++ b/test/schemas/test/playbooks/tasks.yml @@ -0,0 +1,5 @@ +- hosts: localhost + pre_tasks: [] + post_tasks: [] + tasks: [] + handlers: [] diff --git a/test/schemas/test/playbooks/tasks/args.yml b/test/schemas/test/playbooks/tasks/args.yml new file mode 100644 index 0000000..1e25e1d --- /dev/null +++ b/test/schemas/test/playbooks/tasks/args.yml @@ -0,0 +1,4 @@ +- action: foo + args: {} +- action: foo + args: "{{ {} }}" diff --git a/test/schemas/test/playbooks/tasks/become_method.yml b/test/schemas/test/playbooks/tasks/become_method.yml new file mode 100644 index 0000000..9d63a76 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/become_method.yml @@ -0,0 +1,7 @@ +- command: echo 123 + become_method: sudo + +- command: echo 123 + vars: + sudo_var: doo + become_method: "{{ sudo_var }}" # templating is ok diff --git a/test/schemas/test/playbooks/tasks/changed_when.yml b/test/schemas/test/playbooks/tasks/changed_when.yml new file mode 100644 index 0000000..7887ac7 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/changed_when.yml @@ -0,0 +1,10 @@ +- command: echo 123 + changed_when: false + +- command: echo 123 + changed_when: '"1" in ["1", "2", "3"]' + +- command: echo 123 + changed_when: # valid, all items must evaluate as true (AND) + - "foo is defined" + - '"1" in ["1", "2", "3"]' diff --git a/test/schemas/test/playbooks/tasks/diff.yml b/test/schemas/test/playbooks/tasks/diff.yml new file mode 100644 index 0000000..cc0bebc --- /dev/null +++ b/test/schemas/test/playbooks/tasks/diff.yml @@ -0,0 +1,4 @@ +- action: foo + diff: true +- action: foo + diff: "{{ true }}" diff --git a/test/schemas/test/playbooks/tasks/empty_tasks.yml b/test/schemas/test/playbooks/tasks/empty_tasks.yml new file mode 100644 index 0000000..7ee1211 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/empty_tasks.yml @@ -0,0 +1,2 @@ +--- +# this is a valid tasks file, loaded as 'null' document. diff --git a/test/schemas/test/playbooks/tasks/ignore_errors.yml b/test/schemas/test/playbooks/tasks/ignore_errors.yml new file mode 100644 index 0000000..2f253f2 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/ignore_errors.yml @@ -0,0 +1,7 @@ +- command: echo 123 + ignore_errors: true + +- command: echo 123 + vars: + should_ignore_errors: true + ignore_errors: "{{ should_ignore_errors }}" diff --git a/test/schemas/test/playbooks/tasks/local_action_dict.yml b/test/schemas/test/playbooks/tasks/local_action_dict.yml new file mode 100644 index 0000000..5351ab9 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/local_action_dict.yml @@ -0,0 +1,3 @@ +- local_action: + module: ansible.builtin.debug + msg: hello diff --git a/test/schemas/test/playbooks/tasks/local_action_string.yml b/test/schemas/test/playbooks/tasks/local_action_string.yml new file mode 100644 index 0000000..93d98e0 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/local_action_string.yml @@ -0,0 +1 @@ +- local_action: "ansible.builtin.debug msg=hello" diff --git a/test/schemas/test/playbooks/tasks/loop.yml b/test/schemas/test/playbooks/tasks/loop.yml new file mode 100644 index 0000000..33c6130 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/loop.yml @@ -0,0 +1,6 @@ +- name: that should pass + ansible.builtin.debug: + var: item + loop: + - foo + - bar diff --git a/test/schemas/test/playbooks/tasks/no_log.yml b/test/schemas/test/playbooks/tasks/no_log.yml new file mode 100644 index 0000000..83a12d0 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/no_log.yml @@ -0,0 +1,11 @@ +- ansible.builtin.debug: + msg: foo + no_log: true # valid + vars: + some_var: true + +- ansible.builtin.debug: + msg: foo + no_log: "{{ some_var }}" # valid too + vars: + some_var: true diff --git a/test/schemas/test/playbooks/tasks/notify.yml b/test/schemas/test/playbooks/tasks/notify.yml new file mode 100644 index 0000000..88432d9 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/notify.yml @@ -0,0 +1,11 @@ +- name: notify single handler + ansible.builtin.debug: + msg: task with single handler + notify: handler1 + +- name: notify multiple handlers + ansible.builtin.debug: + msg: task with multiple handlers + notify: + - handler1 + - handler2 diff --git a/test/schemas/test/playbooks/tasks/run_once.yml b/test/schemas/test/playbooks/tasks/run_once.yml new file mode 100644 index 0000000..0f3f6f7 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/run_once.yml @@ -0,0 +1,9 @@ +- name: foo + ansible.builtin.debug: + msg: foo! + run_once: true # valid + +- name: foo2 + ansible.builtin.debug: + msg: foo! + run_once: "{{ true }}" # valid diff --git a/test/schemas/test/playbooks/tasks/some_tasks.yml b/test/schemas/test/playbooks/tasks/some_tasks.yml new file mode 100644 index 0000000..2430d52 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/some_tasks.yml @@ -0,0 +1,8 @@ +- name: foo + debug: + msg: bar + delegate_facts: true + +- block: + - debug: + msg: "block under one level of block" diff --git a/test/schemas/test/playbooks/tasks/tags.yml b/test/schemas/test/playbooks/tasks/tags.yml new file mode 100644 index 0000000..a0b7454 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/tags.yml @@ -0,0 +1,29 @@ +- command: echo 123 + tags: + - foo + - bar + +- command: echo 123 + tags: foo + +- block: + - command: echo 123 + tags: + - foo + - bar + + - command: echo 123 + tags: foo + tags: + - foo + - bar + +- block: + - command: echo 123 + tags: + - foo + - bar + + - command: echo 123 + tags: foo + tags: foo diff --git a/test/schemas/test/playbooks/tasks/templated_become.yml b/test/schemas/test/playbooks/tasks/templated_become.yml new file mode 100644 index 0000000..a8cfad3 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/templated_become.yml @@ -0,0 +1,12 @@ +- name: foo + ansible.builtin.debug: + msg: foo! + become: "{{ firewalld_become }}" # <- valid + +- name: foo block + become: "{{ firewalld_become }}" # <- valid + block: + - name: foo + ansible.builtin.debug: + msg: foo! + become: "{{ firewalld_become }}" # <- valid diff --git a/test/schemas/test/playbooks/tasks/templated_integers.yml b/test/schemas/test/playbooks/tasks/templated_integers.yml new file mode 100644 index 0000000..59c4530 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/templated_integers.yml @@ -0,0 +1,5 @@ +- debug: + msg: foo + retries: "{{ 2 }}" # <-- valid + port: "{{ 80 }}" # <-- valid + poll: "{{ 2 }}" # <-- valid diff --git a/test/schemas/test/playbooks/tasks/throttled.yml b/test/schemas/test/playbooks/tasks/throttled.yml new file mode 100644 index 0000000..e1be471 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/throttled.yml @@ -0,0 +1,5 @@ +- action: foo + throttle: 1 # valid + +- action: foo + throttle: "{{ 1 }}" # valid diff --git a/test/schemas/test/playbooks/tasks/until.yml b/test/schemas/test/playbooks/tasks/until.yml new file mode 100644 index 0000000..2146a9d --- /dev/null +++ b/test/schemas/test/playbooks/tasks/until.yml @@ -0,0 +1,14 @@ +- ansible.builtin.debug: + msg: "valid" + until: true + +- ansible.builtin.debug: + msg: "valid" + until: + - "foo not in bar" + +- ansible.builtin.debug: + msg: "valid" + until: + - "'1' in ['1', '2', '3']" + - "foo is not defined" diff --git a/test/schemas/test/playbooks/tasks/when.yml b/test/schemas/test/playbooks/tasks/when.yml new file mode 100644 index 0000000..7874329 --- /dev/null +++ b/test/schemas/test/playbooks/tasks/when.yml @@ -0,0 +1,10 @@ +- action: foo + when: true # valid + +- action: foo 2 + when: foo in bar # valid + +- action: foo 3 + when: # valid + - foo in bar + - apple is orange diff --git a/test/schemas/test/playbooks/tasks/with_items.yml b/test/schemas/test/playbooks/tasks/with_items.yml new file mode 100644 index 0000000..07c72aa --- /dev/null +++ b/test/schemas/test/playbooks/tasks/with_items.yml @@ -0,0 +1,16 @@ +- command: echo 123 + with_items: [] + +- command: echo 123 + with_items: + - 1 + - foo + - {} + - [] + +- command: echo 123 + vars: + my_list: + - 1 + - 2 + with_items: "{{ my_list }}" diff --git a/test/schemas/test/playbooks/templated_become.yml b/test/schemas/test/playbooks/templated_become.yml new file mode 100644 index 0000000..518e46b --- /dev/null +++ b/test/schemas/test/playbooks/templated_become.yml @@ -0,0 +1,16 @@ +--- +- hosts: localhost + become: "{{ firewalld_become }}" # <- valid + tasks: + - name: foo + ansible.builtin.debug: + msg: foo! + become: "{{ firewalld_become }}" # <- valid + + - name: foo block + become: "{{ firewalld_become }}" # <- valid + block: + - name: foo + ansible.builtin.debug: + msg: foo! + become: "{{ firewalld_become }}" # <- valid diff --git a/test/schemas/test/playbooks/user_valid.yml b/test/schemas/test/playbooks/user_valid.yml new file mode 100644 index 0000000..bc6a5e6 --- /dev/null +++ b/test/schemas/test/playbooks/user_valid.yml @@ -0,0 +1,3 @@ +- hosts: localhost + user: foo # <-- allowed, alias to remote_user + tasks: [] diff --git a/test/schemas/test/playbooks/var_files.yml b/test/schemas/test/playbooks/var_files.yml new file mode 100644 index 0000000..eef44fd --- /dev/null +++ b/test/schemas/test/playbooks/var_files.yml @@ -0,0 +1,13 @@ +--- +- name: var_files should accept null + hosts: localhost + vars_files: null + +- name: var_files should accept string + hosts: localhost + vars_files: /dev/null + +- name: var_files should accept array[string] + hosts: localhost + vars_files: + - /dev/null diff --git a/test/schemas/test/playbooks/vars/empty_vars.yml b/test/schemas/test/playbooks/vars/empty_vars.yml new file mode 100644 index 0000000..a6e3ce7 --- /dev/null +++ b/test/schemas/test/playbooks/vars/empty_vars.yml @@ -0,0 +1,2 @@ +--- +# Ensure we allow empty var files, matching Ansible behavior diff --git a/test/schemas/test/playbooks/vars/encrypted.yml b/test/schemas/test/playbooks/vars/encrypted.yml new file mode 100644 index 0000000..7808fec --- /dev/null +++ b/test/schemas/test/playbooks/vars/encrypted.yml @@ -0,0 +1,6 @@ +$ANSIBLE_VAULT;1.2;AES256;dev +66373266323161346330626137613862653935343634366636353266323966363665636266363739 +6436363237626633653139636232663131613832336266310a323766643264306436306266663930 +66666238346132373766623932356530333165613835623863653837306130383065323138333034 +6265313861613761620a393663616265633637343534346533366437653839623239396366366330 +3165 diff --git a/test/schemas/test/playbooks/vars/myvars.yml b/test/schemas/test/playbooks/vars/myvars.yml new file mode 100644 index 0000000..8698380 --- /dev/null +++ b/test/schemas/test/playbooks/vars/myvars.yml @@ -0,0 +1,9 @@ +foo: bar +_foo: bar +foo_var_xxx: "{{ sss }}" +in_job: ... +nested: + pear: fruit + apple: fruit +sso_force_handlers: ... +force_handlers_foo: ... diff --git a/test/schemas/test/playbooks/vars_prompt.yml b/test/schemas/test/playbooks/vars_prompt.yml new file mode 100644 index 0000000..1bf65c3 --- /dev/null +++ b/test/schemas/test/playbooks/vars_prompt.yml @@ -0,0 +1,11 @@ +- name: Fixture + hosts: localhost + vars_prompt: + - name: username + prompt: What is your username? + private: false + unsafe: false + + - name: password + prompt: What is your password? + default: "secret" diff --git a/test/schemas/test/playbooks/with_.yml b/test/schemas/test/playbooks/with_.yml new file mode 100644 index 0000000..b3a3748 --- /dev/null +++ b/test/schemas/test/playbooks/with_.yml @@ -0,0 +1,34 @@ +--- +# https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-flattened +- hosts: localhost + tasks: + - ansible.builtin.debug: + msg: "{{ item }}" + with_list: [] # <-- valid + - ansible.builtin.debug: + msg: "{{ item }}" + with_items: [] # <-- valid + - ansible.builtin.debug: + msg: "{{ item }}" + with_indexed_items: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_together: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_dict: {} + - ansible.builtin.debug: + msg: "{{ item }}" + with_sequence: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_subelements: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_nested: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_random_choice: [] + - ansible.builtin.debug: + msg: "{{ item }}" + with_fileglob: [] diff --git a/test/schemas/test/reqs2/meta/requirements.yml b/test/schemas/test/reqs2/meta/requirements.yml new file mode 100644 index 0000000..8d55085 --- /dev/null +++ b/test/schemas/test/reqs2/meta/requirements.yml @@ -0,0 +1,7 @@ +# https://docs.ansible.com/ansible/latest/galaxy/user_guide.html +collections: + - doo.bar + - name: geerlingguy.php_roles + version: 0.9.3 + source: https://galaxy.ansible.com +roles: [] diff --git a/test/schemas/test/reqs4/meta/requirements.yml b/test/schemas/test/reqs4/meta/requirements.yml new file mode 100644 index 0000000..8269128 --- /dev/null +++ b/test/schemas/test/reqs4/meta/requirements.yml @@ -0,0 +1,6 @@ +# requirements v1 format +- src: https://github.com/bennojoy/nginx +- src: git+http://bitbucket.org/willthames/git-ansible-galaxy + version: v1.4 + scm: git +- include: foo.yml diff --git a/test/schemas/test/reqs5/meta/requirements.yml b/test/schemas/test/reqs5/meta/requirements.yml new file mode 100644 index 0000000..cd99e3c --- /dev/null +++ b/test/schemas/test/reqs5/meta/requirements.yml @@ -0,0 +1,3 @@ +# Collection without roles +collections: + - name: kubernetes.core diff --git a/test/schemas/test/roles/empty-meta/meta/main.yml b/test/schemas/test/roles/empty-meta/meta/main.yml new file mode 100644 index 0000000..9b6fe15 --- /dev/null +++ b/test/schemas/test/roles/empty-meta/meta/main.yml @@ -0,0 +1 @@ +# this is meta file without any data, ansible-core accepts it diff --git a/test/schemas/test/roles/foo/meta/argument_specs.yml b/test/schemas/test/roles/foo/meta/argument_specs.yml new file mode 100644 index 0000000..c8d8c68 --- /dev/null +++ b/test/schemas/test/roles/foo/meta/argument_specs.yml @@ -0,0 +1,74 @@ +--- +# https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-argument-validation +argument_specs: + main: + short_description: The main entry point for the role. + description: "a longer description" + version_added: 1.2.3 + author: Foobar Baz + options: + my_app_int: + type: "int" + required: false + default: 42 + description: "The integer value, defaulting to 42." + no_log: false + version_added: 1.0.0 + + my_app_str: + type: "str" + required: true + description: + - The string value. + - Has some more text. + choices: + - foo + - bar + - baz + + top_level: + type: dict + description: Contains more content. + options: + sub_option: + type: list + elements: int + description: A list of special integers. + choices: + - 1 + - 2 + - 3 + - 123 + + seealso: + - module: community.foo.bar + - module: community.foo.baz + description: Baz bam! + - plugin: community.foo.bam + plugin_type: lookup + - plugin: community.foo.bar + plugin_type: lookup + description: A lookup plugin. + - ref: developer_guide + description: A link into the Ansible documentation. + - link: https://docs.ansible.com/ + name: The Ansible documentation. + description: A link to the Ansible documentation. + + alternate: + short_description: The alternate entry point for the my_app role. + author: + - Foobar Baz + - Bert Foo + options: + my_app_int: + type: "int" + required: false + default: 1024 + description: "The integer value, defaulting to 1024." + + third: + description: + - First paragraph. + - Second paragraph. + options: {} diff --git a/test/schemas/test/roles/foo/meta/main.yml b/test/schemas/test/roles/foo/meta/main.yml new file mode 100644 index 0000000..b84b10c --- /dev/null +++ b/test/schemas/test/roles/foo/meta/main.yml @@ -0,0 +1,46 @@ +collections: + - foo.bar +dependencies: + - name: ansible-role-foo + version: "1.0" + - name: ansible-role-bar + version: "1.0" + # from Bitbucket + - src: git+http://bitbucket.org/willthames/git-ansible-galaxy + version: v1.4 + + # from Bitbucket, alternative syntax and caveats + - src: http://bitbucket.org/willthames/hg-ansible-galaxy + scm: hg + + # from galaxy + - src: community.molecule + + # from GitHub + - src: https://github.com/bennojoy/nginx + + # from GitHub, overriding the name and specifying a specific tag + - src: https://github.com/bennojoy/nginx + version: master + name: nginx_role + + # from GitLab or other git-based scm + - src: git@gitlab.company.com:my-group/my-repo.git + scm: git + version: "0.1" # quoted, so YAML doesn't parse this as a floating-point value + + # from a web server, where the role is packaged in a tar.gz + - src: https://some.webserver.example.com/files/master.tar.gz + name: http-role + +galaxy_info: + author: John Doe + company: foo + description: foo + license: MIT + min_ansible_version: "2.9" + # standalone: true + platforms: + - name: Alpine + versions: + - all diff --git a/test/schemas/test/roles/foo/meta/runtime.yml b/test/schemas/test/roles/foo/meta/runtime.yml new file mode 100644 index 0000000..561e446 --- /dev/null +++ b/test/schemas/test/roles/foo/meta/runtime.yml @@ -0,0 +1,39 @@ +# Based on https://docs.ansible.com/ansible/devel/dev_guide/developing_collections_structure.html#meta-directory +requires_ansible: ">=2.10,<2.11" +plugin_routing: + inventory: + kubevirt: + redirect: community.general.kubevirt + my_inventory: + tombstone: + removal_version: "2.0.0" + warning_text: my_inventory has been removed. Please use other_inventory instead. + modules: + my_module: + deprecation: + removal_date: "2021-11-30" + warning_text: + my_module will be removed in a future release of this collection. Use + another.collection.new_module instead. + redirect: another.collection.new_module + podman_image: + redirect: containers.podman.podman_image + module_utils: + ec2: + redirect: amazon.aws.ec2 + util_dir.subdir.my_util: + redirect: namespace.name.my_util +import_redirection: + ansible.module_utils.old_utility: + redirect: ansible_collections.namespace_name.collection_name.plugins.module_utils.new_location +action_groups: + groupname: + # The special metadata dictionary. All action/module names should be strings. + - metadata: + extend_group: + - another.collection.groupname + - another_group + - my_action + another_group: + - my_module + - another.collection.another_module diff --git a/test/schemas/test/roles/maximum/meta/main.yml b/test/schemas/test/roles/maximum/meta/main.yml new file mode 100644 index 0000000..10c57b1 --- /dev/null +++ b/test/schemas/test/roles/maximum/meta/main.yml @@ -0,0 +1,20 @@ +allow_duplicates: true +galaxy_info: + author: John Doe + standalone: true # v1 role meta (standalone) + description: maximum + min_ansible_version: "2.9" + company: foo + license: MIT + galaxy_tags: # ensure galaxy_tags is allowed + - database + platforms: + - name: Alpine + versions: + - all +dependencies: + - role: foo + vars: {} + when: + - foo + - bar diff --git a/test/schemas/test/roles/meta-tags/meta/main.yml b/test/schemas/test/roles/meta-tags/meta/main.yml new file mode 100644 index 0000000..4abba23 --- /dev/null +++ b/test/schemas/test/roles/meta-tags/meta/main.yml @@ -0,0 +1,25 @@ +--- +# https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#role-dependencies +dependencies: + - role: foo + tags: fruit # simple string allowed + - role: bar + tags: # array of strings allowed + - apple + - orange + - role: requires_sudo + become: true + - role: role_with_condition + when: inventory_hostname == "foo" + - role: another_role + # https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#passing-different-parameters + something_that_counts_as_role_parameter: ... + vars: + "foo": bar +galaxy_info: + author: John Doe + standalone: true + description: foo + license: MIT + min_ansible_version: "2.10" + platforms: [] diff --git a/test/schemas/test/roles/ns/meta/main.yml b/test/schemas/test/roles/ns/meta/main.yml new file mode 100644 index 0000000..0ea558c --- /dev/null +++ b/test/schemas/test/roles/ns/meta/main.yml @@ -0,0 +1,13 @@ +--- +galaxy_info: + author: John Doe + standalone: true + description: foo + min_ansible_version: "2.9" + namespace: foo_bar + company: foo + license: MIT + platforms: + - name: Alpine + versions: + - all diff --git a/test/schemas/test/roles/v1_role/meta/main.yml b/test/schemas/test/roles/v1_role/meta/main.yml new file mode 100644 index 0000000..a74eb47 --- /dev/null +++ b/test/schemas/test/roles/v1_role/meta/main.yml @@ -0,0 +1,12 @@ +--- +galaxy_info: + standalone: true + author: foo-bar # <-- that is a valid author name because is a valid github username + description: foo + min_ansible_version: "2.9" + company: foo + license: MIT + platforms: + - name: Alpine + versions: + - all diff --git a/test/schemas/test/tests/integration/rom_role/meta/main.yml b/test/schemas/test/tests/integration/rom_role/meta/main.yml new file mode 100644 index 0000000..c1409c4 --- /dev/null +++ b/test/schemas/test/tests/integration/rom_role/meta/main.yml @@ -0,0 +1,5 @@ +--- +dependencies: [] +galaxy_info: + standalone: false + description: foo diff --git a/test/schemas/tsconfig.json b/test/schemas/tsconfig.json new file mode 100644 index 0000000..fe51c68 --- /dev/null +++ b/test/schemas/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "declaration": true, + "esModuleInterop": true, + "lib": ["es5", "es2015.promise"], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../lib/umd", + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "target": "es5" + }, + "exclude": ["node_modules"], + "include": ["src/**/*"] +} diff --git a/test/test_ansiblelintrule.py b/test/test_ansiblelintrule.py new file mode 100644 index 0000000..4afcd59 --- /dev/null +++ b/test/test_ansiblelintrule.py @@ -0,0 +1,29 @@ +"""Generic tests for AnsibleLintRule class.""" +from __future__ import annotations + +from typing import Any + +import pytest +from _pytest.monkeypatch import MonkeyPatch + +from ansiblelint.config import options +from ansiblelint.rules import AnsibleLintRule + + +def test_unjinja() -> None: + """Verify that unjinja understands nested mustache.""" + text = "{{ a }} {% b %} {# try to confuse parsing inside a comment { {{}} } #}" + output = "JINJA_EXPRESSION JINJA_STATEMENT JINJA_COMMENT" + assert AnsibleLintRule.unjinja(text) == output + + +@pytest.mark.parametrize("rule_config", ({}, {"foo": True, "bar": 1})) +def test_rule_config(rule_config: dict[str, Any], monkeypatch: MonkeyPatch) -> None: + """Check that a rule config is inherited from options.""" + rule_id = "rule-0" + monkeypatch.setattr(AnsibleLintRule, "id", rule_id) + monkeypatch.setitem(options.rules, rule_id, rule_config) + + rule = AnsibleLintRule() + assert set(rule.rule_config.items()) == set(rule_config.items()) + assert all(rule.get_config(k) == v for k, v in rule_config.items()) diff --git a/test/test_ansiblesyntax.py b/test/test_ansiblesyntax.py new file mode 100644 index 0000000..f71a525 --- /dev/null +++ b/test/test_ansiblesyntax.py @@ -0,0 +1,19 @@ +"""Test Ansible Syntax. + +This module contains tests that validate that linter does not produce errors +when encountering what counts as valid Ansible syntax. +""" +from ansiblelint.testing import RunFromText + +PB_WITH_NULL_TASKS = """\ +--- +- name: Fixture for test_null_tasks + hosts: all + tasks: +""" + + +def test_null_tasks(default_text_runner: RunFromText) -> None: + """Assure we do not fail when encountering null tasks.""" + results = default_text_runner.run_playbook(PB_WITH_NULL_TASKS) + assert not results diff --git a/test/test_app.py b/test/test_app.py new file mode 100644 index 0000000..cbbc6d5 --- /dev/null +++ b/test/test_app.py @@ -0,0 +1,21 @@ +"""Test for app module.""" +from pathlib import Path + +from ansiblelint.file_utils import Lintable +from ansiblelint.testing import run_ansible_lint + + +def test_generate_ignore(tmp_path: Path) -> None: + """Validate that --generate-ignore dumps expected ignore to the file.""" + lintable = Lintable(tmp_path / "vars.yaml") + lintable.content = "foo: bar\nfoo: baz\n" + lintable.write(force=True) + assert not (tmp_path / ".ansible-lint-ignore").exists() + result = run_ansible_lint(lintable.filename, "--generate-ignore", cwd=str(tmp_path)) + assert result.returncode == 2 + assert (tmp_path / ".ansible-lint-ignore").exists() + with open(tmp_path / ".ansible-lint-ignore", encoding="utf-8") as f: + assert "vars.yaml yaml[key-duplicates]\n" in f.readlines() + # Run again and now we expect to succeed as we have an ignore file. + result = run_ansible_lint(lintable.filename, cwd=str(tmp_path)) + assert result.returncode == 0 diff --git a/test/test_cli_role_paths.py b/test/test_cli_role_paths.py new file mode 100644 index 0000000..b70191d --- /dev/null +++ b/test/test_cli_role_paths.py @@ -0,0 +1,186 @@ +"""Tests related to role paths.""" +from __future__ import annotations + +import os +from pathlib import Path + +import pytest + +from ansiblelint.testing import run_ansible_lint +from ansiblelint.text import strip_ansi_escape + + +@pytest.fixture(name="local_test_dir") +def fixture_local_test_dir() -> str: + """Fixture to return local test directory.""" + return os.path.realpath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "examples") + ) + + +def test_run_single_role_path_no_trailing_slash_module(local_test_dir: str) -> None: + """Test that a role path without a trailing slash is accepted.""" + cwd = local_test_dir + role_path = "roles/test-role" + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_single_role_path_no_trailing_slash_script(local_test_dir: str) -> None: + """Test that a role path without a trailing slash is accepted.""" + cwd = local_test_dir + role_path = "roles/test-role" + + result = run_ansible_lint(role_path, cwd=cwd, executable="ansible-lint") + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_single_role_path_with_trailing_slash(local_test_dir: str) -> None: + """Test that a role path with a trailing slash is accepted.""" + cwd = local_test_dir + role_path = "roles/test-role/" + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_multiple_role_path_no_trailing_slash(local_test_dir: str) -> None: + """Test that multiple roles paths without a trailing slash are accepted.""" + cwd = local_test_dir + role_path = "roles/test-role" + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_multiple_role_path_with_trailing_slash(local_test_dir: str) -> None: + """Test that multiple roles paths without a trailing slash are accepted.""" + cwd = local_test_dir + role_path = "roles/test-role/" + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_inside_role_dir(local_test_dir: str) -> None: + """Tests execution from inside a role.""" + cwd = os.path.join(local_test_dir, "roles/test-role/") + role_path = "." + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_role_three_dir_deep(local_test_dir: str) -> None: + """Tests execution from deep inside a role.""" + cwd = local_test_dir + role_path = "testproject/roles/test-role" + + result = run_ansible_lint(role_path, cwd=cwd) + assert "Use shell only when shell functionality is required" in result.stdout + + +def test_run_playbook(local_test_dir: str) -> None: + """Call ansible-lint the way molecule does.""" + cwd = os.path.abspath(os.path.join(local_test_dir, "roles/test-role")) + lintable = "molecule/default/include-import-role.yml" + role_path = str(Path(cwd).parent.resolve()) + + env = os.environ.copy() + env["ANSIBLE_ROLES_PATH"] = role_path + + result = run_ansible_lint(lintable, cwd=cwd, env=env) + assert "Use shell only when shell functionality is required" in result.stdout + + +@pytest.mark.parametrize( + ("args", "expected_msg"), + ( + pytest.param( + [], "role-name: Role name invalid-name does not match", id="normal" + ), + pytest.param(["--skip-list", "role-name"], "", id="skipped"), + ), +) +def test_run_role_name_invalid( + local_test_dir: str, args: list[str], expected_msg: str +) -> None: + """Test run with a role with invalid name.""" + cwd = local_test_dir + role_path = "roles/invalid-name" + + result = run_ansible_lint(*args, role_path, cwd=cwd) + assert result.returncode == (2 if expected_msg else 0), result + if expected_msg: + assert expected_msg in strip_ansi_escape(result.stdout) + + +def test_run_role_name_with_prefix(local_test_dir: str) -> None: + """Test run where role path has a prefix.""" + cwd = local_test_dir + role_path = "roles/ansible-role-foo" + + result = run_ansible_lint("-v", role_path, cwd=cwd) + assert len(result.stdout) == 0 + assert result.returncode == 0 + + +def test_run_role_name_from_meta(local_test_dir: str) -> None: + """Test running from inside meta folder.""" + cwd = local_test_dir + role_path = "roles/valid-due-to-meta" + + result = run_ansible_lint("-v", role_path, cwd=cwd) + assert len(result.stdout) == 0 + assert result.returncode == 0 + + +def test_run_invalid_role_name_from_meta(local_test_dir: str) -> None: + """Test invalid role from inside meta folder.""" + cwd = local_test_dir + role_path = "roles/invalid_due_to_meta" + + result = run_ansible_lint(role_path, cwd=cwd) + assert ( + "role-name: Role name invalid-due-to-meta does not match" + in strip_ansi_escape(result.stdout) + ) + + +def test_run_single_role_path_with_roles_path_env(local_test_dir: str) -> None: + """Test for role name collision with ANSIBLE_ROLES_PATH. + + Test if ansible-lint chooses the role in the current directory when the role + specified as parameter exists in the current directory and the ANSIBLE_ROLES_PATH. + """ + cwd = local_test_dir + role_path = "roles/test-role" + + env = os.environ.copy() + env["ANSIBLE_ROLES_PATH"] = os.path.realpath(os.path.join(cwd, "../examples/roles")) + + result = run_ansible_lint(role_path, cwd=cwd, env=env) + assert "Use shell only when shell functionality is required" in result.stdout + + +@pytest.mark.parametrize( + ("result", "env"), + ((True, {"GITHUB_ACTIONS": "true", "GITHUB_WORKFLOW": "foo"}), (False, None)), + ids=("on", "off"), +) +def test_run_playbook_github(result: bool, env: dict[str, str]) -> None: + """Call ansible-lint simulating GitHub Actions environment.""" + cwd = str(Path(__file__).parent.parent.resolve()) + role_path = "examples/playbooks/example.yml" + + if env is None: + env = {} + env["PATH"] = os.environ["PATH"] + result_gh = run_ansible_lint(role_path, cwd=cwd, env=env) + + expected = ( + "::error file=examples/playbooks/example.yml,line=44,severity=VERY_LOW,title=package-latest::" + "Package installs should not use latest" + ) + assert (expected in result_gh.stdout) is result diff --git a/test/test_commandline_invocations_same_as_config.py b/test/test_commandline_invocations_same_as_config.py new file mode 100644 index 0000000..eecc0d9 --- /dev/null +++ b/test/test_commandline_invocations_same_as_config.py @@ -0,0 +1,195 @@ +"""Test cli arguments and config.""" +from __future__ import annotations + +import os +from pathlib import Path + +import pytest +from _pytest.monkeypatch import MonkeyPatch + +from ansiblelint import cli + + +@pytest.fixture(name="base_arguments") +def fixture_base_arguments() -> list[str]: + """Define reusable base arguments for tests in current module.""" + return ["../test/skiptasks.yml"] + + +@pytest.mark.parametrize( + ("args", "config"), + ( + (["-p"], "test/fixtures/parseable.yml"), + (["-q"], "test/fixtures/quiet.yml"), + (["-r", "test/fixtures/rules/"], "test/fixtures/rulesdir.yml"), + (["-R", "-r", "test/fixtures/rules/"], "test/fixtures/rulesdir-defaults.yml"), + (["-s"], "test/fixtures/strict.yml"), + (["-t", "skip_ansible_lint"], "test/fixtures/tags.yml"), + (["-v"], "test/fixtures/verbosity.yml"), + (["-x", "bad_tag"], "test/fixtures/skip-tags.yml"), + (["--exclude", "test/"], "test/fixtures/exclude-paths.yml"), + (["--show-relpath"], "test/fixtures/show-abspath.yml"), + ([], "test/fixtures/show-relpath.yml"), + ), +) +def test_ensure_config_are_equal( + base_arguments: list[str], args: list[str], config: str +) -> None: + """Check equality of the CLI options to config files.""" + command = base_arguments + args + cli_parser = cli.get_cli_parser() + + options = cli_parser.parse_args(command) + file_config = cli.load_config(config) + + for key, val in file_config.items(): + # config_file does not make sense in file_config + if key == "config_file": + continue + + if key in {"exclude_paths", "rulesdir"}: + val = [Path(p) for p in val] + assert val == getattr(options, key) + + +@pytest.mark.parametrize( + ("with_base", "args", "config"), + ( + (True, ["--write"], "test/fixtures/config-with-write-all.yml"), + (True, ["--write=all"], "test/fixtures/config-with-write-all.yml"), + (True, ["--write", "all"], "test/fixtures/config-with-write-all.yml"), + (True, ["--write=none"], "test/fixtures/config-with-write-none.yml"), + (True, ["--write", "none"], "test/fixtures/config-with-write-none.yml"), + ( + True, + ["--write=rule-tag,rule-id"], + "test/fixtures/config-with-write-subset.yml", + ), + ( + True, + ["--write", "rule-tag,rule-id"], + "test/fixtures/config-with-write-subset.yml", + ), + ( + True, + ["--write", "rule-tag", "--write", "rule-id"], + "test/fixtures/config-with-write-subset.yml", + ), + ( + False, + ["--write", "examples/playbooks/example.yml"], + "test/fixtures/config-with-write-all.yml", + ), + ( + False, + ["--write", "examples/playbooks/example.yml", "non-existent.yml"], + "test/fixtures/config-with-write-all.yml", + ), + ), +) +def test_ensure_write_cli_does_not_consume_lintables( + base_arguments: list[str], with_base: bool, args: list[str], config: str +) -> None: + """Check equality of the CLI --write options to config files.""" + cli_parser = cli.get_cli_parser() + + command = base_arguments + args if with_base else args + options = cli_parser.parse_args(command) + file_config = cli.load_config(config) + + file_value = file_config.get("write_list") + orig_cli_value = getattr(options, "write_list") + cli_value = cli.WriteArgAction.merge_write_list_config( + from_file=[], from_cli=orig_cli_value + ) + assert file_value == cli_value + + +def test_config_can_be_overridden(base_arguments: list[str]) -> None: + """Check that config can be overridden from CLI.""" + no_override = cli.get_config(base_arguments + ["-t", "bad_tag"]) + + overridden = cli.get_config( + base_arguments + ["-t", "bad_tag", "-c", "test/fixtures/tags.yml"] + ) + + assert no_override.tags + ["skip_ansible_lint"] == overridden.tags + + +def test_different_config_file(base_arguments: list[str]) -> None: + """Ensures an alternate config_file can be used.""" + diff_config = cli.get_config( + base_arguments + ["-c", "test/fixtures/ansible-config.yml"] + ) + no_config = cli.get_config(base_arguments + ["-v"]) + + assert diff_config.verbosity == no_config.verbosity + + +def test_expand_path_user_and_vars_config_file(base_arguments: list[str]) -> None: + """Ensure user and vars are expanded when specified as exclude_paths.""" + config1 = cli.get_config( + base_arguments + ["-c", "test/fixtures/exclude-paths-with-expands.yml"] + ) + config2 = cli.get_config( + base_arguments + + ["--exclude", "~/.ansible/roles", "--exclude", "$HOME/.ansible/roles"] + ) + + assert str(config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") + assert str(config2.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") + assert str(config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") + assert str(config2.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") + + +def test_path_from_config_do_not_depend_on_cwd( + monkeypatch: MonkeyPatch, +) -> None: # Issue 572 + """Check that config-provided paths are decoupled from CWD.""" + config1 = cli.load_config("test/fixtures/config-with-relative-path.yml") + monkeypatch.chdir("test") + config2 = cli.load_config("fixtures/config-with-relative-path.yml") + + assert config1["exclude_paths"].sort() == config2["exclude_paths"].sort() + + +def test_path_from_cli_depend_on_cwd( + base_arguments: list[str], monkeypatch: MonkeyPatch +) -> None: + """Check that CLI-provided paths are relative to CWD.""" + # Issue 572 + arguments = base_arguments + [ + "--exclude", + "test/fixtures/config-with-relative-path.yml", + ] + + options1 = cli.get_cli_parser().parse_args(arguments) + assert "test/test" not in str(options1.exclude_paths[0]) + + test_dir = "test" + monkeypatch.chdir(test_dir) + options2 = cli.get_cli_parser().parse_args(arguments) + + assert "test/test" in str(options2.exclude_paths[0]) + + +@pytest.mark.parametrize( + "config_file", + ( + pytest.param("test/fixtures/ansible-config-invalid.yml", id="invalid"), + pytest.param("/dev/null/ansible-config-missing.yml", id="missing"), + ), +) +def test_config_failure(base_arguments: list[str], config_file: str) -> None: + """Ensures specific config files produce error code 3.""" + with pytest.raises(SystemExit, match="^3$"): + cli.get_config(base_arguments + ["-c", config_file]) + + +def test_extra_vars_loaded(base_arguments: list[str]) -> None: + """Ensure ``extra_vars`` option is loaded from file config.""" + config = cli.get_config( + base_arguments + ["-c", "test/fixtures/config-with-extra-vars.yml"] + ) + + assert config.extra_vars == {"foo": "bar", "knights_favorite_word": "NI"} diff --git a/test/test_constants.py b/test/test_constants.py new file mode 100644 index 0000000..52b297a --- /dev/null +++ b/test/test_constants.py @@ -0,0 +1,9 @@ +"""Tests for constants module.""" +from ansiblelint.constants import States + + +def test_states() -> None: + """Test that states are evaluated as boolean false.""" + assert bool(States.NOT_LOADED) is False + assert bool(States.LOAD_FAILED) is False + assert bool(States.UNKNOWN_DATA) is False diff --git a/test/test_dependencies_in_meta.py b/test/test_dependencies_in_meta.py new file mode 100644 index 0000000..44007b7 --- /dev/null +++ b/test/test_dependencies_in_meta.py @@ -0,0 +1,10 @@ +"""Tests about dependencies in meta.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_external_dependency_is_ok(default_rules_collection: RulesCollection) -> None: + """Check that external dep in role meta is not a violation.""" + playbook_path = "examples/roles/dependency_in_meta/meta/main.yml" + good_runner = Runner(playbook_path, rules=default_rules_collection) + assert [] == good_runner.run() diff --git a/test/test_eco.py b/test/test_eco.py new file mode 100644 index 0000000..6463b71 --- /dev/null +++ b/test/test_eco.py @@ -0,0 +1,88 @@ +"""Test a set of 3rd party Ansible repositories for possible regressions.""" +import os +import pathlib +import re +import shlex +import subprocess + +import pytest + +from ansiblelint.testing import run_ansible_lint + +eco_repos = { + "bootstrap": "https://github.com/robertdebock/ansible-role-bootstrap", + "cisco.nxos": "https://github.com/ansible-collections/cisco.nxos", + "colsystem": "https://github.com/devroles/ansible_collection_system", + "debops": "https://github.com/debops/debops", + "docker-rootless": "https://github.com/konstruktoid/ansible-docker-rootless", + "hardening": "https://github.com/konstruktoid/ansible-role-hardening", + "mysql": "https://github.com/geerlingguy/ansible-role-mysql.git", +} + + +def sanitize_output(text: str) -> str: + """Make the output less likely to vary between runs or minor changes.""" + # replace full path to home directory with ~. + result = text.replace(os.path.expanduser("~"), "~") + # removes warning related to PATH alteration + result = re.sub( + r"^WARNING: PATH altered to include.+\n", "", result, flags=re.MULTILINE + ) + # replace venv path + result = result.replace(".tox/venv", ".tox/eco") + + return result + + +@pytest.mark.eco() +@pytest.mark.parametrize(("repo"), (eco_repos.keys())) +def test_eco(repo: str) -> None: + """Test a set of 3rd party Ansible repositories for possible regressions.""" + url = eco_repos[repo] + cache_dir = os.path.expanduser("~/.cache/ansible-lint-eco") + my_dir = (pathlib.Path(__file__).parent / "eco").resolve() + os.makedirs(cache_dir, exist_ok=True) + # clone repo + if os.path.exists(f"{cache_dir}/{repo}/.git"): + subprocess.run("git pull", cwd=f"{cache_dir}/{repo}", shell=True, check=True) + else: + subprocess.run( + f"git clone --recurse-submodules {url} {cache_dir}/{repo}", + shell=True, + check=True, + ) + # run ansible lint and paths from user home in order to produce + # consistent results regardless on its location. + for step in ["before", "after"]: + args = ["-f", "pep8"] + executable = ( + "ansible-lint" + if step == "after" + else f"{pathlib.Path(__file__).parent.parent}/.tox/venv/bin/ansible-lint" + ) + result = run_ansible_lint( + *args, + executable=executable, + cwd=f"{cache_dir}/{repo}", + ) + + # Ensure that cmd looks the same for later diff, even if the path was different + result.args[0] = "ansible-lint" + # sort stderr because parallel runs can + result.stderr = "\n".join(sorted(result.stderr.split("\n"))) + + result_txt = f"CMD: {shlex.join(result.args)}\n\nRC: {result.returncode}\n\nSTDERR:\n{result.stderr}\n\nSTDOUT:\n{result.stdout}" + + os.makedirs(f"{my_dir}/{step}", exist_ok=True) + with open(f"{my_dir}/{step}/{repo}.result", "w", encoding="utf-8") as f: + f.write(sanitize_output(result_txt)) + + # fail if result is different than our expected one + result = subprocess.run( + f"git --no-pager diff --exit-code --no-index test/eco/before/{repo}.result test/eco/after/{repo}.result", + shell=True, + check=False, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result_txt + f"\nDIFF:\n{result.stdout}" diff --git a/test/test_examples.py b/test/test_examples.py new file mode 100644 index 0000000..19cb25b --- /dev/null +++ b/test/test_examples.py @@ -0,0 +1,64 @@ +"""Assure samples produced desire outcomes.""" +import pytest + +from ansiblelint.app import get_app +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner +from ansiblelint.testing import run_ansible_lint + + +def test_example(default_rules_collection: RulesCollection) -> None: + """example.yml is expected to have exact number of errors inside.""" + result = Runner( + "examples/playbooks/example.yml", rules=default_rules_collection + ).run() + assert len(result) == 22 + + +@pytest.mark.parametrize( + ("filename", "line", "column"), + ( + pytest.param( + "examples/playbooks/syntax-error-string.yml", 6, 7, id="syntax-error" + ), + pytest.param("examples/playbooks/syntax-error.yml", 2, 3, id="syntax-error"), + ), +) +def test_example_syntax_error( + default_rules_collection: RulesCollection, filename: str, line: int, column: int +) -> None: + """Validates that loading valid YAML string produce error.""" + result = Runner(filename, rules=default_rules_collection).run() + assert len(result) == 1 + assert result[0].rule.id == "syntax-check" + # This also ensures that line and column numbers start at 1, so they + # match what editors will show (or output from other linters) + assert result[0].linenumber == line + assert result[0].column == column + + +def test_example_custom_module(default_rules_collection: RulesCollection) -> None: + """custom_module.yml is expected to pass.""" + app = get_app() + result = Runner( + "examples/playbooks/custom_module.yml", rules=default_rules_collection + ).run() + assert len(result) == 0, f"{app.runtime.cache_dir}" + + +def test_full_vault(default_rules_collection: RulesCollection) -> None: + """custom_module.yml is expected to pass.""" + result = Runner( + "examples/playbooks/vars/not_decryptable.yml", rules=default_rules_collection + ).run() + assert len(result) == 0 + + +def test_custom_kinds() -> None: + """Check if user defined kinds are used.""" + result = run_ansible_lint("-vv", "--offline", "examples/other/") + assert result.returncode == 0 + # .yaml-too is not a recognized extension and unless is manually defined + # in our ansible-lint config, the test would not identify it as yaml file. + assert "Examining examples/other/some.yaml-too of type yaml" in result.stderr + assert "Examining examples/other/some.j2.yaml of type jinja2" in result.stderr diff --git a/test/test_file_path_evaluation.py b/test/test_file_path_evaluation.py new file mode 100644 index 0000000..15692e4 --- /dev/null +++ b/test/test_file_path_evaluation.py @@ -0,0 +1,126 @@ +"""Testing file path evaluation when using import_tasks / include_tasks.""" +from __future__ import annotations + +import textwrap +from pathlib import Path + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +LAYOUT_IMPORTS: dict[str, str] = { + "main.yml": textwrap.dedent( + """\ + --- + - name: Fixture + hosts: target + gather_facts: false + tasks: + - name: From main import task 1 + ansible.builtin.import_tasks: tasks/task_1.yml + """ + ), + "tasks/task_1.yml": textwrap.dedent( + """\ + --- + - name: task_1 | From task 1 import task 2 + ansible.builtin.import_tasks: tasks/task_2.yml + """ + ), + "tasks/task_2.yml": textwrap.dedent( + """\ + --- + - name: task_2 | From task 2 import subtask 1 + ansible.builtin.import_tasks: tasks/subtasks/subtask_1.yml + """ + ), + "tasks/subtasks/subtask_1.yml": textwrap.dedent( + """\ + --- + - name: subtask_1 | From subtask 1 import subtask 2 + ansible.builtin.import_tasks: tasks/subtasks/subtask_2.yml + """ + ), + "tasks/subtasks/subtask_2.yml": textwrap.dedent( + """\ + --- + - name: subtask_2 | From subtask 2 do something + debug: # <-- expected to raise fqcn[action-core] + msg: | + Something... + """ + ), +} + +LAYOUT_INCLUDES: dict[str, str] = { + "main.yml": textwrap.dedent( + """\ + --- + - name: Fixture + hosts: target + gather_facts: false + tasks: + - name: From main import task 1 + ansible.builtin.include_tasks: tasks/task_1.yml + """ + ), + "tasks/task_1.yml": textwrap.dedent( + """\ + --- + - name: task_1 | From task 1 import task 2 + ansible.builtin.include_tasks: tasks/task_2.yml + """ + ), + "tasks/task_2.yml": textwrap.dedent( + """\ + --- + - name: task_2 | From task 2 import subtask 1 + ansible.builtin.include_tasks: tasks/subtasks/subtask_1.yml + """ + ), + "tasks/subtasks/subtask_1.yml": textwrap.dedent( + """\ + --- + - name: subtask_1 | From subtask 1 import subtask 2 + ansible.builtin.include_tasks: tasks/subtasks/subtask_2.yml + """ + ), + "tasks/subtasks/subtask_2.yml": textwrap.dedent( + """\ + --- + - name: subtask_2 | From subtask 2 do something + debug: # <-- expected to raise fqcn[action-core] + msg: | + Something... + """ + ), +} + + +@pytest.mark.parametrize( + "ansible_project_layout", + ( + pytest.param(LAYOUT_IMPORTS, id="using only import_tasks"), + pytest.param(LAYOUT_INCLUDES, id="using only include_tasks"), + ), +) +def test_file_path_evaluation( + tmp_path: Path, + default_rules_collection: RulesCollection, + ansible_project_layout: dict[str, str], +) -> None: + """Test file path evaluation when using import_tasks / include_tasks in the project. + + The goal of this test is to verify our ability to find errors from within + nested includes. + """ + for file_path, file_content in ansible_project_layout.items(): + full_path = tmp_path / file_path + full_path.parent.mkdir(parents=True, exist_ok=True) + full_path.write_text(file_content) + + result = Runner(str(tmp_path), rules=default_rules_collection).run() + + assert len(result) == 1 + assert result[0].rule.id == "fqcn" diff --git a/test/test_file_utils.py b/test/test_file_utils.py new file mode 100644 index 0000000..b427fa8 --- /dev/null +++ b/test/test_file_utils.py @@ -0,0 +1,491 @@ +"""Tests for file utility functions.""" +from __future__ import annotations + +import os +import time +from argparse import Namespace +from pathlib import Path +from typing import Any + +import pytest +from _pytest.capture import CaptureFixture +from _pytest.logging import LogCaptureFixture +from _pytest.monkeypatch import MonkeyPatch + +from ansiblelint import cli, file_utils +from ansiblelint.__main__ import initialize_logger +from ansiblelint.constants import FileType +from ansiblelint.file_utils import ( + Lintable, + expand_path_vars, + expand_paths_vars, + guess_project_dir, + normpath, + normpath_path, +) +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +from .conftest import cwd + + +@pytest.mark.parametrize( + ("path", "expected"), + ( + pytest.param(Path("a/b/../"), "a", id="pathlib.Path"), + pytest.param("a/b/../", "a", id="str"), + pytest.param("", ".", id="empty"), + pytest.param(".", ".", id="empty"), + ), +) +def test_normpath(path: str, expected: str) -> None: + """Ensure that relative parent dirs are normalized in paths.""" + assert normpath(path) == expected + + +def test_expand_path_vars(monkeypatch: MonkeyPatch) -> None: + """Ensure that tilde and env vars are expanded in paths.""" + test_path = "/test/path" + monkeypatch.setenv("TEST_PATH", test_path) + assert expand_path_vars("~") == os.path.expanduser("~") + assert expand_path_vars("$TEST_PATH") == test_path + + +@pytest.mark.parametrize( + ("test_path", "expected"), + ( + pytest.param(Path("$TEST_PATH"), "/test/path", id="pathlib.Path"), + pytest.param("$TEST_PATH", "/test/path", id="str"), + pytest.param(" $TEST_PATH ", "/test/path", id="stripped-str"), + pytest.param("~", os.path.expanduser("~"), id="home"), + ), +) +def test_expand_paths_vars( + test_path: str | Path, expected: str, monkeypatch: MonkeyPatch +) -> None: + """Ensure that tilde and env vars are expanded in paths lists.""" + monkeypatch.setenv("TEST_PATH", "/test/path") + assert expand_paths_vars([test_path]) == [expected] # type: ignore + + +@pytest.mark.parametrize( + ("reset_env_var", "message_prefix"), + ( + # simulate absence of git command + ("PATH", "Failed to locate command: "), + # simulate a missing git repo + ("GIT_DIR", "Looking up for files"), + ), + ids=("no-git-cli", "outside-git-repo"), +) +def test_discover_lintables_git_verbose( + reset_env_var: str, + message_prefix: str, + monkeypatch: MonkeyPatch, + caplog: LogCaptureFixture, +) -> None: + """Ensure that autodiscovery lookup failures are logged.""" + options = cli.get_config(["-v"]) + initialize_logger(options.verbosity) + monkeypatch.setenv(reset_env_var, "") + file_utils.discover_lintables(options) + + assert any(m[2].startswith("Looking up for files") for m in caplog.record_tuples) + assert any(m.startswith(message_prefix) for m in caplog.messages) + + +@pytest.mark.parametrize( + "is_in_git", + (True, False), + ids=("in Git", "outside Git"), +) +def test_discover_lintables_silent( + is_in_git: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture[str] +) -> None: + """Verify that no stderr output is displayed while discovering yaml files. + + (when the verbosity is off, regardless of the Git or Git-repo presence) + + Also checks expected number of files are detected. + """ + options = cli.get_config([]) + test_dir = Path(__file__).resolve().parent + lint_path = test_dir / ".." / "examples" / "roles" / "test-role" + if not is_in_git: + monkeypatch.setenv("GIT_DIR", "") + + yaml_count = len(list(lint_path.glob("**/*.yml"))) + len( + list(lint_path.glob("**/*.yaml")) + ) + + monkeypatch.chdir(str(lint_path)) + files = file_utils.discover_lintables(options) + stderr = capsys.readouterr().err + assert not stderr, "No stderr output is expected when the verbosity is off" + assert ( + len(files) == yaml_count + ), "Expected to find {yaml_count} yaml files in {lint_path}".format_map( + locals(), + ) + + +def test_discover_lintables_umlaut(monkeypatch: MonkeyPatch) -> None: + """Verify that filenames containing German umlauts are not garbled by the discover_lintables.""" + options = cli.get_config([]) + test_dir = Path(__file__).resolve().parent + lint_path = test_dir / ".." / "examples" / "playbooks" + + monkeypatch.chdir(str(lint_path)) + files = file_utils.discover_lintables(options) + assert '"with-umlaut-\\303\\244.yml"' not in files + assert "with-umlaut-ä.yml" in files + + +@pytest.mark.parametrize( + ("path", "kind"), + ( + pytest.param("tasks/run_test_playbook.yml", "tasks", id="0"), + pytest.param("foo/playbook.yml", "playbook", id="1"), + pytest.param("playbooks/foo.yml", "playbook", id="2"), + pytest.param("examples/roles/foo.yml", "yaml", id="3"), + # the only yml file that is not a playbook inside molecule/ folders + pytest.param( + "examples/.config/molecule/config.yml", "yaml", id="4" + ), # molecule shared config + pytest.param( + "test/schemas/test/molecule/cluster/base.yml", "yaml", id="5" + ), # molecule scenario base config + pytest.param( + "test/schemas/test/molecule/cluster/molecule.yml", "yaml", id="6" + ), # molecule scenario config + pytest.param( + "test/schemas/test/molecule/cluster/foobar.yml", "playbook", id="7" + ), # custom playbook name + pytest.param( + "test/schemas/test/molecule/cluster/converge.yml", "playbook", id="8" + ), # common playbook name + pytest.param( + "roles/foo/molecule/scenario3/requirements.yml", "requirements", id="9" + ), # requirements + pytest.param( + "roles/foo/molecule/scenario3/collections.yml", "requirements", id="10" + ), # requirements + pytest.param( + "roles/foo/meta/argument_specs.yml", "arg_specs", id="11" + ), # role argument specs + # tasks files: + pytest.param("tasks/directory with spaces/main.yml", "tasks", id="12"), # tasks + pytest.param("tasks/requirements.yml", "tasks", id="13"), # tasks + # requirements (we do not support includes yet) + pytest.param( + "requirements.yml", "requirements", id="14" + ), # collection requirements + pytest.param( + "roles/foo/meta/requirements.yml", "requirements", id="15" + ), # inside role requirements + # Undeterminable files: + pytest.param("test/fixtures/unknown-type.yml", "yaml", id="16"), + pytest.param( + "releasenotes/notes/run-playbooks-refactor.yaml", "reno", id="17" + ), # reno + pytest.param("examples/host_vars/localhost.yml", "vars", id="18"), + pytest.param("examples/group_vars/all.yml", "vars", id="19"), + pytest.param("examples/playbooks/vars/other.yml", "vars", id="20"), + pytest.param( + "examples/playbooks/vars/subfolder/settings.yml", "vars", id="21" + ), # deep vars + pytest.param( + "molecule/scenario/collections.yml", "requirements", id="22" + ), # deprecated 2.8 format + pytest.param( + "../roles/geerlingguy.mysql/tasks/configure.yml", "tasks", id="23" + ), # relative path involved + pytest.param("galaxy.yml", "galaxy", id="24"), + pytest.param("foo.j2.yml", "jinja2", id="25"), + pytest.param("foo.yml.j2", "jinja2", id="26"), + pytest.param("foo.j2.yaml", "jinja2", id="27"), + pytest.param("foo.yaml.j2", "jinja2", id="28"), + pytest.param( + "examples/playbooks/rulebook.yml", "playbook", id="29" + ), # playbooks folder should determine kind + pytest.param( + "examples/rulebooks/rulebook.yml", "rulebook", id="30" + ), # content should determine it as a rulebook + pytest.param( + "examples/yamllint/valid.yml", "yaml", id="31" + ), # empty yaml is valid yaml, not assuming anything else + pytest.param( + "examples/other/guess-1.yml", "playbook", id="32" + ), # content should determine is as a play + pytest.param( + "examples/playbooks/tasks/passing_task.yml", "tasks", id="33" + ), # content should determine is tasks + pytest.param("examples/collection/galaxy.yml", "galaxy", id="34"), + pytest.param("examples/meta/runtime.yml", "meta-runtime", id="35"), + pytest.param("examples/meta/changelogs/changelog.yaml", "changelog", id="36"), + pytest.param("examples/inventory/inventory.yml", "inventory", id="37"), + pytest.param("examples/inventory/production.yml", "inventory", id="38"), + pytest.param("examples/playbooks/vars/empty_vars.yml", "vars", id="39"), + pytest.param( + "examples/playbooks/vars/subfolder/settings.yaml", "vars", id="40" + ), + ), +) +def test_kinds(monkeypatch: MonkeyPatch, path: str, kind: FileType) -> None: + """Verify auto-detection logic based on DEFAULT_KINDS.""" + options = cli.get_config([]) + + # pylint: disable=unused-argument + def mockreturn(options: Namespace) -> dict[str, Any]: + return {normpath(path): kind} + + # assert Lintable is able to determine file type + lintable_detected = Lintable(path) + lintable_expected = Lintable(path, kind=kind) + assert lintable_detected == lintable_expected + + monkeypatch.setattr(file_utils, "discover_lintables", mockreturn) + result = file_utils.discover_lintables(options) + assert lintable_detected.kind == result[lintable_expected.name] + + +def test_guess_project_dir_tmp_path(tmp_path: Path) -> None: + """Verify guess_project_dir().""" + with cwd(str(tmp_path)): + result = guess_project_dir(None) + assert result == str(tmp_path) + + +def test_guess_project_dir_dotconfig() -> None: + """Verify guess_project_dir().""" + with cwd("examples"): + assert os.path.exists( + ".config/ansible-lint.yml" + ), "Test requires config file inside .config folder." + result = guess_project_dir(".config/ansible-lint.yml") + assert result == str(os.getcwd()) + + +BASIC_PLAYBOOK = """ +- name: "playbook" + tasks: + - name: Hello + debug: + msg: 'world' +""" + + +@pytest.fixture(name="tmp_updated_lintable") +def fixture_tmp_updated_lintable( + tmp_path: Path, path: str, content: str, updated_content: str +) -> Lintable: + """Create a temp file Lintable with a content update that is not on disk.""" + lintable = Lintable(tmp_path / path, content) + with lintable.path.open("w", encoding="utf-8") as f: + f.write(content) + # move mtime to a time in the past to avoid race conditions in the test + mtime = time.time() - 60 * 60 # 1hr ago + os.utime(str(lintable.path), (mtime, mtime)) + lintable.content = updated_content + return lintable + + +@pytest.mark.parametrize( + ("path", "content", "updated_content", "updated"), + ( + pytest.param( + "no_change.yaml", BASIC_PLAYBOOK, BASIC_PLAYBOOK, False, id="no_change" + ), + pytest.param( + "quotes.yaml", + BASIC_PLAYBOOK, + BASIC_PLAYBOOK.replace('"', "'"), + True, + id="updated_quotes", + ), + pytest.param( + "shorten.yaml", BASIC_PLAYBOOK, "# short file\n", True, id="shorten_file" + ), + ), +) +def test_lintable_updated( + path: str, content: str, updated_content: str, updated: bool +) -> None: + """Validate ``Lintable.updated`` when setting ``Lintable.content``.""" + lintable = Lintable(path, content) + + assert lintable.content == content + + lintable.content = updated_content + + assert lintable.content == updated_content + + assert lintable.updated is updated + + +@pytest.mark.parametrize( + "updated_content", ((None,), (b"bytes",)), ids=("none", "bytes") +) +def test_lintable_content_setter_with_bad_types(updated_content: Any) -> None: + """Validate ``Lintable.updated`` when setting ``Lintable.content``.""" + lintable = Lintable("bad_type.yaml", BASIC_PLAYBOOK) + assert lintable.content == BASIC_PLAYBOOK + + with pytest.raises(TypeError): + lintable.content = updated_content + + assert not lintable.updated + + +def test_lintable_with_new_file(tmp_path: Path) -> None: + """Validate ``Lintable.updated`` for a new file.""" + lintable = Lintable(tmp_path / "new.yaml") + + lintable.content = BASIC_PLAYBOOK + lintable.content = BASIC_PLAYBOOK + assert lintable.content == BASIC_PLAYBOOK + + assert lintable.updated + + assert not lintable.path.exists() + lintable.write() + assert lintable.path.exists() + assert lintable.path.read_text(encoding="utf-8") == BASIC_PLAYBOOK + + +@pytest.mark.parametrize( + ("path", "force", "content", "updated_content", "updated"), + ( + pytest.param( + "no_change.yaml", + False, + BASIC_PLAYBOOK, + BASIC_PLAYBOOK, + False, + id="no_change", + ), + pytest.param( + "forced.yaml", + True, + BASIC_PLAYBOOK, + BASIC_PLAYBOOK, + False, + id="forced_rewrite", + ), + pytest.param( + "quotes.yaml", + False, + BASIC_PLAYBOOK, + BASIC_PLAYBOOK.replace('"', "'"), + True, + id="updated_quotes", + ), + pytest.param( + "shorten.yaml", + False, + BASIC_PLAYBOOK, + "# short file\n", + True, + id="shorten_file", + ), + pytest.param( + "forced.yaml", + True, + BASIC_PLAYBOOK, + BASIC_PLAYBOOK.replace('"', "'"), + True, + id="forced_and_updated", + ), + ), +) +def test_lintable_write( + tmp_updated_lintable: Lintable, + force: bool, + content: str, + updated_content: str, + updated: bool, +) -> None: + """Validate ``Lintable.write`` writes when it should.""" + pre_updated = tmp_updated_lintable.updated + pre_stat = tmp_updated_lintable.path.stat() + + tmp_updated_lintable.write(force=force) + + post_stat = tmp_updated_lintable.path.stat() + post_updated = tmp_updated_lintable.updated + + # write() should not hide that an update happened + assert pre_updated == post_updated == updated + + if force or updated: + assert pre_stat.st_mtime < post_stat.st_mtime + else: + assert pre_stat.st_mtime == post_stat.st_mtime + + with tmp_updated_lintable.path.open("r", encoding="utf-8") as f: + post_content = f.read() + + if updated: + assert content != post_content + else: + assert content == post_content + assert post_content == updated_content + + +@pytest.mark.parametrize( + ("path", "content", "updated_content"), + ( + pytest.param( + "quotes.yaml", + BASIC_PLAYBOOK, + BASIC_PLAYBOOK.replace('"', "'"), + id="updated_quotes", + ), + ), +) +def test_lintable_content_deleter( + tmp_updated_lintable: Lintable, + content: str, + updated_content: str, +) -> None: + """Ensure that resetting content cache triggers re-reading file.""" + assert content != updated_content + assert tmp_updated_lintable.content == updated_content + del tmp_updated_lintable.content + assert tmp_updated_lintable.content == content + + +@pytest.mark.parametrize( + ("path", "result"), + ( + pytest.param("foo", "foo", id="rel"), + pytest.param(os.path.expanduser("~/xxx"), "~/xxx", id="rel-to-home"), + pytest.param("/a/b/c", "/a/b/c", id="absolute"), + pytest.param( + "examples/playbooks/roles", "examples/roles", id="resolve-symlink" + ), + ), +) +def test_normpath_path(path: str, result: str) -> None: + """Tests behavior of normpath.""" + assert normpath_path(path) == Path(result) + + +def test_bug_2513( + tmp_path: Path, + default_rules_collection: RulesCollection, +) -> None: + """Regression test for bug 2513. + + Test that when CWD is outside ~, and argument is like ~/playbook.yml + we will still be able to process the files. + See: https://github.com/ansible/ansible-lint/issues/2513 + """ + filename = "~/.cache/ansible-lint/playbook.yml" + os.makedirs(os.path.dirname(os.path.expanduser(filename)), exist_ok=True) + lintable = Lintable(filename, content="---\n- hosts: all\n") + lintable.write(force=True) + with cwd(str(tmp_path)): + results = Runner(filename, rules=default_rules_collection).run() + assert len(results) == 1 + assert results[0].rule.id == "name" diff --git a/test/test_formatter.py b/test/test_formatter.py new file mode 100644 index 0000000..29153f8 --- /dev/null +++ b/test/test_formatter.py @@ -0,0 +1,68 @@ +"""Test for output formatter.""" +# Copyright (c) 2016 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +import pathlib + +from ansiblelint.errors import MatchError +from ansiblelint.file_utils import Lintable +from ansiblelint.formatters import Formatter +from ansiblelint.rules import AnsibleLintRule + +rule = AnsibleLintRule() +rule.id = "TCF0001" +formatter = Formatter(pathlib.Path.cwd(), display_relative_path=True) +# These details would generate a rich rendering error if not escaped: +DETAILS = "Some [/tmp/foo] details." + + +def test_format_coloured_string() -> None: + """Test formetting colored.""" + match = MatchError( + message="message", + linenumber=1, + details=DETAILS, + filename=Lintable("filename.yml", content=""), + rule=rule, + ) + formatter.format(match) + + +def test_unicode_format_string() -> None: + """Test formatting unicode.""" + match = MatchError( + message="\U0001f427", + linenumber=1, + details=DETAILS, + filename=Lintable("filename.yml", content=""), + rule=rule, + ) + formatter.format(match) + + +def test_dict_format_line() -> None: + """Test formatting dictionary details.""" + match = MatchError( + message="xyz", + linenumber=1, + details={"hello": "world"}, # type: ignore + filename=Lintable("filename.yml", content=""), + rule=rule, + ) + formatter.format(match) diff --git a/test/test_formatter_base.py b/test/test_formatter_base.py new file mode 100644 index 0000000..3e444a3 --- /dev/null +++ b/test/test_formatter_base.py @@ -0,0 +1,66 @@ +"""Tests related to base formatter.""" +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest + +from ansiblelint.formatters import BaseFormatter + + +@pytest.mark.parametrize( + ("base_dir", "relative_path"), + ( + (None, True), + ("/whatever", False), + (Path("/whatever"), False), + ), +) +@pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) +def test_base_formatter_when_base_dir( + base_dir: Any, relative_path: bool, path: str +) -> None: + """Check that base formatter accepts relative pathlib and str.""" + # Given + base_formatter = BaseFormatter(base_dir, relative_path) # type: ignore + + # When + output_path = base_formatter._format_path(path) # pylint: disable=protected-access + + # Then + assert isinstance(output_path, (str, Path)) + # pylint: disable=protected-access + assert base_formatter._base_dir is None or isinstance( + base_formatter._base_dir, (str, Path) + ) + assert output_path == path + + +@pytest.mark.parametrize( + "base_dir", + ( + Path("/whatever"), + "/whatever", + ), +) +@pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) +def test_base_formatter_when_base_dir_is_given_and_relative_is_true( + path: str | Path, base_dir: str | Path +) -> None: + """Check that the base formatter equally accepts pathlib and str.""" + # Given + base_formatter = BaseFormatter(base_dir, True) # type: ignore + + # When + # pylint: disable=protected-access + output_path = base_formatter._format_path(path) + + # Then + assert isinstance(output_path, (str, Path)) + # pylint: disable=protected-access + assert isinstance(base_formatter._base_dir, (str, Path)) + assert output_path == Path(path).name + + +# vim: et:sw=4:syntax=python:ts=4: diff --git a/test/test_formatter_json.py b/test/test_formatter_json.py new file mode 100644 index 0000000..9e995aa --- /dev/null +++ b/test/test_formatter_json.py @@ -0,0 +1,131 @@ +"""Test the codeclimate JSON formatter.""" +from __future__ import annotations + +import json +import pathlib +import subprocess +import sys + +import pytest + +from ansiblelint.errors import MatchError +from ansiblelint.file_utils import Lintable +from ansiblelint.formatters import CodeclimateJSONFormatter +from ansiblelint.rules import AnsibleLintRule + + +class TestCodeclimateJSONFormatter: + """Unit test for CodeclimateJSONFormatter.""" + + rule = AnsibleLintRule() + matches: list[MatchError] = [] + formatter: CodeclimateJSONFormatter | None = None + + def setup_class(self) -> None: + """Set up few MatchError objects.""" + self.rule = AnsibleLintRule() + self.rule.id = "TCF0001" + self.rule.severity = "VERY_HIGH" + self.matches = [] + self.matches.append( + MatchError( + message="message", + linenumber=1, + details="hello", + filename=Lintable("filename.yml", content=""), + rule=self.rule, + ) + ) + self.matches.append( + MatchError( + message="message", + linenumber=2, + details="hello", + filename=Lintable("filename.yml", content=""), + rule=self.rule, + ) + ) + self.formatter = CodeclimateJSONFormatter( + pathlib.Path.cwd(), display_relative_path=True + ) + + def test_format_list(self) -> None: + """Test if the return value is a string.""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + assert isinstance(self.formatter.format_result(self.matches), str) + + def test_result_is_json(self) -> None: + """Test if returned string value is a JSON.""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + json.loads(self.formatter.format_result(self.matches)) + + def test_single_match(self) -> None: + """Test negative case. Only lists are allowed. Otherwise a RuntimeError will be raised.""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + with pytest.raises(RuntimeError): + self.formatter.format_result(self.matches[0]) # type: ignore + + def test_result_is_list(self) -> None: + """Test if the return JSON contains a list with a length of 2.""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + result = json.loads(self.formatter.format_result(self.matches)) + assert len(result) == 2 + + def test_validate_codeclimate_schema(self) -> None: + """Test if the returned JSON is a valid codeclimate report.""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + result = json.loads(self.formatter.format_result(self.matches)) + single_match = result[0] + assert "type" in single_match + assert single_match["type"] == "issue" + assert "check_name" in single_match + assert "categories" in single_match + assert isinstance(single_match["categories"], list) + assert "severity" in single_match + assert single_match["severity"] == "blocker" + assert "description" in single_match + assert "fingerprint" in single_match + assert "location" in single_match + assert "path" in single_match["location"] + assert single_match["location"]["path"] == self.matches[0].filename + assert "lines" in single_match["location"] + assert single_match["location"]["lines"]["begin"] == self.matches[0].linenumber + assert "positions" not in single_match["location"] + + def test_validate_codeclimate_schema_with_positions(self) -> None: + """Test if the returned JSON is a valid codeclimate report (containing 'positions' instead of 'lines').""" + assert isinstance(self.formatter, CodeclimateJSONFormatter) + result = json.loads( + self.formatter.format_result( + [ + MatchError( + message="message", + linenumber=1, + column=42, + details="hello", + filename=Lintable("filename.yml", content=""), + rule=self.rule, + ) + ] + ) + ) + assert result[0]["location"]["positions"]["begin"]["line"] == 1 + assert result[0]["location"]["positions"]["begin"]["column"] == 42 + assert "lines" not in result[0]["location"] + + +def test_code_climate_parsable_ignored() -> None: + """Test that -p option does not alter codeclimate format.""" + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "-v", + "-p", + ] + file = "examples/playbooks/empty_playbook.yml" + result = subprocess.run([*cmd, file], check=False) + result2 = subprocess.run([*cmd, "-p", file], check=False) + + assert result.returncode == result2.returncode + assert result.stdout == result2.stdout diff --git a/test/test_formatter_sarif.py b/test/test_formatter_sarif.py new file mode 100644 index 0000000..7ef87b5 --- /dev/null +++ b/test/test_formatter_sarif.py @@ -0,0 +1,167 @@ +"""Test the codeclimate JSON formatter.""" +from __future__ import annotations + +import json +import os +import pathlib +import subprocess +import sys +from tempfile import NamedTemporaryFile + +import pytest + +from ansiblelint.errors import MatchError +from ansiblelint.file_utils import Lintable +from ansiblelint.formatters import SarifFormatter +from ansiblelint.rules import AnsibleLintRule + + +class TestSarifFormatter: + """Unit test for SarifFormatter.""" + + rule = AnsibleLintRule() + matches: list[MatchError] = [] + formatter: SarifFormatter | None = None + + def setup_class(self) -> None: + """Set up few MatchError objects.""" + self.rule = AnsibleLintRule() + self.rule.id = "TCF0001" + self.rule.severity = "VERY_HIGH" + self.rule.description = "This is the rule description." + self.rule.link = "https://rules/help#TCF0001" + self.rule.tags = ["tag1", "tag2"] + self.matches = [] + self.matches.append( + MatchError( + message="message", + linenumber=1, + column=10, + details="hello", + filename=Lintable("filename.yml", content=""), + rule=self.rule, + tag="yaml[test]", + ) + ) + self.matches.append( + MatchError( + message="message", + linenumber=2, + details="hello", + filename=Lintable("filename.yml", content=""), + rule=self.rule, + tag="yaml[test]", + ) + ) + self.formatter = SarifFormatter(pathlib.Path.cwd(), display_relative_path=True) + + def test_format_list(self) -> None: + """Test if the return value is a string.""" + assert isinstance(self.formatter, SarifFormatter) + assert isinstance(self.formatter.format_result(self.matches), str) + + def test_result_is_json(self) -> None: + """Test if returned string value is a JSON.""" + assert isinstance(self.formatter, SarifFormatter) + json.loads(self.formatter.format_result(self.matches)) + + def test_single_match(self) -> None: + """Test negative case. Only lists are allowed. Otherwise a RuntimeError will be raised.""" + assert isinstance(self.formatter, SarifFormatter) + with pytest.raises(RuntimeError): + self.formatter.format_result(self.matches[0]) # type: ignore + + def test_result_is_list(self) -> None: + """Test if the return SARIF object contains the results with length of 2.""" + assert isinstance(self.formatter, SarifFormatter) + sarif = json.loads(self.formatter.format_result(self.matches)) + assert len(sarif["runs"][0]["results"]) == 2 + + def test_validate_sarif_schema(self) -> None: + """Test if the returned JSON is a valid SARIF report.""" + assert isinstance(self.formatter, SarifFormatter) + sarif = json.loads(self.formatter.format_result(self.matches)) + assert sarif["$schema"] == SarifFormatter.SARIF_SCHEMA + assert sarif["version"] == SarifFormatter.SARIF_SCHEMA_VERSION + driver = sarif["runs"][0]["tool"]["driver"] + assert driver["name"] == SarifFormatter.TOOL_NAME + assert driver["informationUri"] == SarifFormatter.TOOL_URL + rules = driver["rules"] + assert len(rules) == 1 + assert rules[0]["id"] == self.matches[0].tag + assert rules[0]["name"] == self.matches[0].tag + assert rules[0]["shortDescription"]["text"] == self.matches[0].message + assert rules[0]["defaultConfiguration"]["level"] == "error" + assert rules[0]["help"]["text"] == self.matches[0].rule.description + assert rules[0]["properties"]["tags"] == self.matches[0].rule.tags + assert rules[0]["helpUri"] == self.rule.link + results = sarif["runs"][0]["results"] + assert len(results) == 2 + for i, result in enumerate(results): + assert result["ruleId"] == self.matches[i].tag + assert result["message"]["text"] == self.matches[0].message + assert ( + result["locations"][0]["physicalLocation"]["artifactLocation"]["uri"] + == self.matches[i].filename + ) + assert ( + result["locations"][0]["physicalLocation"]["artifactLocation"][ + "uriBaseId" + ] + == SarifFormatter.BASE_URI_ID + ) + assert ( + result["locations"][0]["physicalLocation"]["region"]["startLine"] + == self.matches[i].linenumber + ) + if self.matches[i].column: + assert ( + result["locations"][0]["physicalLocation"]["region"]["startColumn"] + == self.matches[i].column + ) + else: + assert ( + "startColumn" + not in result["locations"][0]["physicalLocation"]["region"] + ) + assert sarif["runs"][0]["originalUriBaseIds"][SarifFormatter.BASE_URI_ID]["uri"] + + +def test_sarif_parsable_ignored() -> None: + """Test that -p option does not alter SARIF format.""" + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "-v", + "-p", + ] + file = "examples/playbooks/empty_playbook.yml" + result = subprocess.run([*cmd, file], check=False) + result2 = subprocess.run([*cmd, "-p", file], check=False) + + assert result.returncode == result2.returncode + assert result.stdout == result2.stdout + + +@pytest.mark.parametrize( + ("file", "return_code"), + ( + pytest.param("examples/playbooks/valid.yml", 0), + pytest.param("playbook.yml", 2), + ), +) +def test_sarif_file(file: str, return_code: int) -> None: + """Test ability to dump sarif file (--sarif-file).""" + with NamedTemporaryFile(mode="w", suffix=".sarif", prefix="output") as output_file: + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "--sarif-file", + str(output_file.name), + ] + result = subprocess.run([*cmd, file], check=False, capture_output=True) + assert result.returncode == return_code + assert os.path.exists(output_file.name) + assert os.path.getsize(output_file.name) > 0 diff --git a/test/test_import_include_role.py b/test/test_import_include_role.py new file mode 100644 index 0000000..5ecfa77 --- /dev/null +++ b/test/test_import_include_role.py @@ -0,0 +1,150 @@ +"""Tests related to role imports.""" +from __future__ import annotations + +from pathlib import Path + +import pytest +from _pytest.fixtures import SubRequest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +ROLE_TASKS_MAIN = """\ +--- +- name: Shell instead of command + shell: echo hello world # noqa: fqcn no-free-form + changed_when: false +""" + +ROLE_TASKS_WORLD = """\ +--- +- ansible.builtin.debug: + msg: "this is a task without a name" +""" + +PLAY_IMPORT_ROLE = """\ +--- +- name: Test fixture + hosts: all + + tasks: + - name: Some import # noqa: fqcn + import_role: + name: test-role +""" + +PLAY_IMPORT_ROLE_FQCN = """\ +--- +- name: Test fixture + hosts: all + + tasks: + - name: Some import + ansible.builtin.import_role: + name: test-role +""" + +PLAY_IMPORT_ROLE_INLINE = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Some import + import_role: name=test-role # noqa: no-free-form fqcn +""" + +PLAY_INCLUDE_ROLE = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Some import + include_role: + name: test-role + tasks_from: world +""" + +PLAY_INCLUDE_ROLE_FQCN = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Some import + ansible.builtin.include_role: + name: test-role + tasks_from: world +""" + +PLAY_INCLUDE_ROLE_INLINE = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Some import + include_role: name=test-role tasks_from=world # noqa: no-free-form +""" + + +@pytest.fixture(name="playbook_path") +def fixture_playbook_path(request: SubRequest, tmp_path: Path) -> str: + """Create a reusable per-test role skeleton.""" + playbook_text = request.param + role_tasks_dir = tmp_path / "test-role" / "tasks" + role_tasks_dir.mkdir(parents=True) + (role_tasks_dir / "main.yml").write_text(ROLE_TASKS_MAIN) + (role_tasks_dir / "world.yml").write_text(ROLE_TASKS_WORLD) + play_path = tmp_path / "playbook.yml" + play_path.write_text(playbook_text) + return str(play_path) + + +@pytest.mark.parametrize( + ("playbook_path", "messages"), + ( + pytest.param( + PLAY_IMPORT_ROLE, + ["only when shell functionality is required", "All tasks should be named"], + id="IMPORT_ROLE", + ), + pytest.param( + PLAY_IMPORT_ROLE_FQCN, + ["only when shell functionality is required", "All tasks should be named"], + id="IMPORT_ROLE_FQCN", + ), + pytest.param( + PLAY_IMPORT_ROLE_INLINE, + ["only when shell functionality is require", "All tasks should be named"], + id="IMPORT_ROLE_INLINE", + ), + pytest.param( + PLAY_INCLUDE_ROLE, + ["only when shell functionality is require", "All tasks should be named"], + id="INCLUDE_ROLE", + ), + pytest.param( + PLAY_INCLUDE_ROLE_FQCN, + ["only when shell functionality is require", "All tasks should be named"], + id="INCLUDE_ROLE_FQCN", + ), + pytest.param( + PLAY_INCLUDE_ROLE_INLINE, + ["only when shell functionality is require", "All tasks should be named"], + id="INCLUDE_ROLE_INLINE", + ), + ), + indirect=("playbook_path",), +) +def test_import_role2( + default_rules_collection: RulesCollection, playbook_path: str, messages: list[str] +) -> None: + """Test that include_role digs deeper than import_role.""" + runner = Runner( + playbook_path, + rules=default_rules_collection, + skip_list=["fqcn[action-core]"], + ) + results = runner.run() + for message in messages: + assert message in str(results) + # Ensure no other unexpected messages are present + assert len(messages) == len(results), results diff --git a/test/test_import_playbook.py b/test/test_import_playbook.py new file mode 100644 index 0000000..66d8763 --- /dev/null +++ b/test/test_import_playbook.py @@ -0,0 +1,18 @@ +"""Test ability to import playbooks.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_task_hook_import_playbook(default_rules_collection: RulesCollection) -> None: + """Assures import_playbook includes are recognized.""" + playbook_path = "examples/playbooks/playbook-parent.yml" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + results_text = str(results) + assert len(runner.lintables) == 2 + assert len(results) == 2 + # Assures we detected the issues from imported playbook + assert "Commands should not change things" in results_text + assert "[name]" in results_text + assert "All tasks should be named" in results_text diff --git a/test/test_import_tasks.py b/test/test_import_tasks.py new file mode 100644 index 0000000..3254121 --- /dev/null +++ b/test/test_import_tasks.py @@ -0,0 +1,28 @@ +"""Test related to import of invalid files.""" +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +@pytest.mark.parametrize( + "playbook_path", + ( + pytest.param( + "examples/playbooks/test_import_with_conflicting_action_statements.yml", + id="0", + ), + pytest.param("examples/playbooks/test_import_with_malformed.yml", id="1"), + ), +) +def test_import_tasks( + default_rules_collection: RulesCollection, playbook_path: str +) -> None: + """Assures import_playbook includes are recognized.""" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + assert len(runner.lintables) == 1 + assert len(results) == 1 + # Assures we detected the issues from imported file + assert results[0].rule.id == "syntax-check" diff --git a/test/test_include_miss_file_with_role.py b/test/test_include_miss_file_with_role.py new file mode 100644 index 0000000..6834758 --- /dev/null +++ b/test/test_include_miss_file_with_role.py @@ -0,0 +1,43 @@ +"""Tests related to inclusions.""" +import pytest +from _pytest.logging import LogCaptureFixture + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_cases_warning_message(default_rules_collection: RulesCollection) -> None: + """Test that including a non-existing file produces an error.""" + playbook_path = "examples/playbooks/play_miss_include.yml" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + assert len(runner.lintables) == 3 + assert len(results) == 1 + assert "No such file or directory" in results[0].message + + +@pytest.mark.parametrize( + "playbook_path", + ( + pytest.param("examples/playbooks/test_include_inplace.yml", id="inplace"), + pytest.param("examples/playbooks/test_include_relative.yml", id="relative"), + ), +) +def test_cases_that_do_not_report( + playbook_path: str, + default_rules_collection: RulesCollection, + caplog: LogCaptureFixture, +) -> None: + """Test that relative inclusions are properly followed.""" + runner = Runner(playbook_path, rules=default_rules_collection) + result = runner.run() + noexist_message_count = 0 + + for record in caplog.records: + for msg in ("No such file or directory", "Couldn't open"): + if msg in str(record): + noexist_message_count += 1 + + assert noexist_message_count == 0 + assert len(result) == 0 diff --git a/test/test_internal_rules.py b/test/test_internal_rules.py new file mode 100644 index 0000000..b949238 --- /dev/null +++ b/test/test_internal_rules.py @@ -0,0 +1,8 @@ +"""Tests for internal rules.""" +from ansiblelint._internal.rules import BaseRule + + +def test_base_rule_url() -> None: + """Test that rule URL is set to expected value.""" + rule = BaseRule() + assert rule.url == "https://ansible-lint.readthedocs.io/rules/" diff --git a/test/test_lint_rule.py b/test/test_lint_rule.py new file mode 100644 index 0000000..2e13aa2 --- /dev/null +++ b/test/test_lint_rule.py @@ -0,0 +1,46 @@ +"""Tests for lintable.""" +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from test.rules.fixtures import ematcher, raw_task + +import pytest + +from ansiblelint.file_utils import Lintable + + +@pytest.fixture(name="lintable") +def fixture_lintable() -> Lintable: + """Return a playbook Lintable for use in this file's tests.""" + return Lintable("examples/playbooks/ematcher-rule.yml", kind="playbook") + + +def test_rule_matching(lintable: Lintable) -> None: + """Test rule.matchlines() on a playbook.""" + rule = ematcher.EMatcherRule() + matches = rule.matchlines(lintable) + assert len(matches) == 3 + + +def test_raw_rule_matching(lintable: Lintable) -> None: + """Test rule.matchlines() on a playbook.""" + rule = raw_task.RawTaskRule() + matches = rule.matchtasks(lintable) + assert len(matches) == 1 diff --git a/test/test_list_rules.py b/test/test_list_rules.py new file mode 100644 index 0000000..eab512b --- /dev/null +++ b/test/test_list_rules.py @@ -0,0 +1,76 @@ +"""Tests related to our logging/verbosity setup.""" + +import os + +import pytest + +from ansiblelint.testing import run_ansible_lint + + +def test_list_rules_includes_opt_in_rules() -> None: + """Checks that listing rules also includes the opt-in rules.""" + # Piggyback off the .yamllint in the root of the repo, just for testing. + # We'll "override" it with the one in the fixture. + cwd = os.path.realpath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") + ) + fakerole = os.path.join("test", "fixtures", "list-rules-tests") + + result_list_rules = run_ansible_lint("-L", fakerole, cwd=cwd) + + assert ("opt-in" in result_list_rules.stdout) is True + + +@pytest.mark.parametrize( + ("result", "returncode", "format_string"), + ( + (False, 0, "brief"), + (False, 0, "full"), + (False, 0, "md"), + (True, 2, "json"), + (True, 2, "codeclimate"), + (True, 2, "quiet"), + (True, 2, "pep8"), + (True, 2, "foo"), + ), + ids=( + "plain", + "full", + "md", + "json", + "codeclimate", + "quiet", + "pep8", + "foo", + ), +) +def test_list_rules_with_format_option( + result: bool, returncode: int, format_string: str +) -> None: + """Checks that listing rules with format options works.""" + # Piggyback off the .yamllint in the root of the repo, just for testing. + # We'll "override" it with the one in the fixture. + cwd = os.path.realpath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") + ) + fakerole = os.path.join("test", "fixtures", "list-rules-tests") + + result_list_rules = run_ansible_lint("-f", format_string, "-L", fakerole, cwd=cwd) + + assert (f"invalid choice: '{format_string}'" in result_list_rules.stderr) is result + assert ("syntax-check" in result_list_rules.stdout) is not result + assert result_list_rules.returncode is returncode + + +def test_list_tags_includes_opt_in_rules() -> None: + """Checks that listing tags also includes the opt-in rules.""" + # Piggyback off the .yamllint in the root of the repo, just for testing. + # We'll "override" it with the one in the fixture. + cwd = os.path.realpath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") + ) + fakerole = os.path.join("test", "fixtures", "list-rules-tests") + + result_list_tags = run_ansible_lint("-L", fakerole, cwd=cwd) + + assert ("opt-in" in result_list_tags.stdout) is True diff --git a/test/test_load_failure.py b/test/test_load_failure.py new file mode 100644 index 0000000..c0e008a --- /dev/null +++ b/test/test_load_failure.py @@ -0,0 +1,13 @@ +"""Tests for LoadFailureRule.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_load_failure_encoding(default_rules_collection: RulesCollection) -> None: + """Check that we fail when file encoding is wrong.""" + runner = Runner("examples/broken/encoding.j2", rules=default_rules_collection) + matches = runner.run() + assert len(matches) == 1, matches + assert matches[0].rule.id == "load-failure" + assert "'utf-8' codec can't decode byte" in matches[0].message + assert matches[0].tag == "load-failure[unicodedecodeerror]" diff --git a/test/test_loaders.py b/test/test_loaders.py new file mode 100644 index 0000000..d07c26e --- /dev/null +++ b/test/test_loaders.py @@ -0,0 +1,8 @@ +"""Tests for loaders submodule.""" +from ansiblelint.loaders import load_ignore_txt + + +def test_load_ignore_txt() -> None: + """Test load_ignore_txt.""" + result = load_ignore_txt(".ansible-lint-ignore") + assert result == {"playbook2.yml": {"foo-bar", "package-latest"}} diff --git a/test/test_local_content.py b/test/test_local_content.py new file mode 100644 index 0000000..8455aaf --- /dev/null +++ b/test/test_local_content.py @@ -0,0 +1,13 @@ +"""Test playbooks with local content.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_local_collection(default_rules_collection: RulesCollection) -> None: + """Assures local collections are found.""" + playbook_path = "test/local-content/test-collection.yml" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + assert len(runner.lintables) == 1 + assert len(results) == 0 diff --git a/test/test_main.py b/test/test_main.py new file mode 100644 index 0000000..ae84f12 --- /dev/null +++ b/test/test_main.py @@ -0,0 +1,78 @@ +"""Tests related to ansiblelint.__main__ module.""" +import os +import shutil +import subprocess +import sys +import time +from pathlib import Path +from typing import Any + +import pytest + +from ansiblelint.config import get_version_warning + + +@pytest.mark.parametrize( + ("expected_warning"), + (False, True), + ids=("normal", "isolated"), +) +def test_call_from_outside_venv(expected_warning: bool) -> None: + """Asserts ability to be called w/ or w/o venv activation.""" + git_location = shutil.which("git") + assert git_location + + if expected_warning: + env = {"HOME": str(Path.home()), "PATH": str(os.path.dirname(git_location))} + else: + env = os.environ.copy() + + for v in ("COVERAGE_FILE", "COVERAGE_PROCESS_START"): + if v in os.environ: + env[v] = os.environ[v] + + py_path = os.path.dirname(sys.executable) + # Passing custom env prevents the process from inheriting PATH or other + # environment variables from the current process, so we emulate being + # called from outside the venv. + proc = subprocess.run( + [f"{py_path}/ansible-lint", "--version"], + check=False, + capture_output=True, + text=True, + env=env, + ) + assert proc.returncode == 0, proc + warning_found = "PATH altered to include" in proc.stderr + assert warning_found is expected_warning + + +@pytest.mark.parametrize( + ("ver_diff", "found", "check", "outlen"), + ( + ("v1.2.2", True, "pre-release", 1), + ("v1.2.3", False, "", 1), + ("v1.2.4", True, "new release", 2), + ), +) +def test_get_version_warning( + mocker: Any, ver_diff: str, found: bool, check: str, outlen: int +) -> None: + """Assert get_version_warning working as expected.""" + data = '{"html_url": "https://127.0.0.1", "tag_name": "' + f"{ver_diff}" + '"}' + # simulate cache file + mocker.patch("os.path.exists", return_value=True) + mocker.patch("os.path.getmtime", return_value=time.time()) + mocker.patch("builtins.open", mocker.mock_open(read_data=data)) + # overwrite ansible-lint version + mocker.patch("ansiblelint.config.__version__", "1.2.3") + # overwrite install method to custom one. This one will increase msg line count + # to easily detect unwanted call to it. + mocker.patch("ansiblelint.config.guess_install_method", return_value="\n") + msg = get_version_warning() + + if not found: + assert msg == check + else: + assert check in msg + assert len(msg.split("\n")) == outlen diff --git a/test/test_matcherrror.py b/test/test_matcherrror.py new file mode 100644 index 0000000..041b7ce --- /dev/null +++ b/test/test_matcherrror.py @@ -0,0 +1,203 @@ +"""Tests for MatchError.""" + +import operator +from typing import Any, Callable + +import pytest + +from ansiblelint.errors import MatchError +from ansiblelint.file_utils import Lintable +from ansiblelint.rules.no_changed_when import CommandHasChangesCheckRule +from ansiblelint.rules.partial_become import BecomeUserWithoutBecomeRule + + +class DummyTestObject: + """A dummy object for equality tests.""" + + def __repr__(self) -> str: + """Return a dummy object representation for parametrize.""" + return f"{self.__class__.__name__}()" + + def __eq__(self, other: object) -> bool: + """Report the equality check failure with any object.""" + return False + + def __ne__(self, other: object) -> bool: + """Report the confirmation of inequality with any object.""" + return True + + +class DummySentinelTestObject: + """A dummy object for equality protocol tests with sentinel.""" + + def __eq__(self, other: object) -> bool: + """Return sentinel as result of equality check w/ anything.""" + return "EQ_SENTINEL" # type: ignore + + def __ne__(self, other: object) -> bool: + """Return sentinel as result of inequality check w/ anything.""" + return "NE_SENTINEL" # type: ignore + + def __lt__(self, other: object) -> bool: + """Return sentinel as result of less than check w/ anything.""" + return "LT_SENTINEL" # type: ignore + + def __gt__(self, other: object) -> bool: + """Return sentinel as result of greater than chk w/ anything.""" + return "GT_SENTINEL" # type: ignore + + +@pytest.mark.parametrize( + ("left_match_error", "right_match_error"), + ( + (MatchError("foo"), MatchError("foo")), + (MatchError("a", details="foo"), MatchError("a", details="foo")), + ), +) +def test_matcherror_compare( + left_match_error: MatchError, right_match_error: MatchError +) -> None: + """Check that MatchError instances with similar attrs are equivalent.""" + assert left_match_error == right_match_error + + +def test_matcherror_invalid() -> None: + """Ensure that MatchError requires message or rule.""" + expected_err = ( + r"^MatchError\(\) missing a required argument: one of 'message' or 'rule'$" + ) + with pytest.raises(TypeError, match=expected_err): + raise MatchError() + + +@pytest.mark.parametrize( + ("left_match_error", "right_match_error"), + ( + # sorting by message + (MatchError("z"), MatchError("a")), + # filenames takes priority in sorting + ( + MatchError("a", filename=Lintable("b", content="")), + MatchError("a", filename=Lintable("a", content="")), + ), + # rule id partial-become > rule id no-changed-when + ( + MatchError(rule=BecomeUserWithoutBecomeRule()), + MatchError(rule=CommandHasChangesCheckRule()), + ), + # details are taken into account + (MatchError("a", details="foo"), MatchError("a", details="bar")), + # columns are taken into account + (MatchError("a", column=3), MatchError("a", column=1)), + (MatchError("a", column=3), MatchError("a")), + ), +) +class TestMatchErrorCompare: + """Test the comparison of MatchError instances.""" + + @staticmethod + def test_match_error_less_than( + left_match_error: MatchError, right_match_error: MatchError + ) -> None: + """Check 'less than' protocol implementation in MatchError.""" + assert right_match_error < left_match_error + + @staticmethod + def test_match_error_greater_than( + left_match_error: MatchError, right_match_error: MatchError + ) -> None: + """Check 'greater than' protocol implementation in MatchError.""" + assert left_match_error > right_match_error + + @staticmethod + def test_match_error_not_equal( + left_match_error: MatchError, right_match_error: MatchError + ) -> None: + """Check 'not equals' protocol implementation in MatchError.""" + assert left_match_error != right_match_error + + +@pytest.mark.parametrize( + "other", + ( + None, + "foo", + 42, + Exception("foo"), + ), + ids=repr, +) +@pytest.mark.parametrize( + ("operation", "operator_char"), + ( + pytest.param(operator.le, "<=", id="<="), + pytest.param(operator.gt, ">", id=">"), + ), +) +def test_matcherror_compare_no_other_fallback( + other: Any, operation: Callable[..., bool], operator_char: str +) -> None: + """Check that MatchError comparison with other types causes TypeError.""" + expected_error = ( + r"^(" + r"unsupported operand type\(s\) for {operator!s}:|" + r"'{operator!s}' not supported between instances of" + r") 'MatchError' and '{other_type!s}'$".format( + other_type=type(other).__name__, operator=operator_char + ) + ) + with pytest.raises(TypeError, match=expected_error): + operation(MatchError("foo"), other) + + +@pytest.mark.parametrize( + "other", + ( + None, + "foo", + 42, + Exception("foo"), + DummyTestObject(), + ), + ids=repr, +) +@pytest.mark.parametrize( + ("operation", "expected_value"), + ( + (operator.eq, False), + (operator.ne, True), + ), + ids=("==", "!="), +) +def test_matcherror_compare_with_other_fallback( + other: object, + operation: Callable[..., bool], + expected_value: bool, +) -> None: + """Check that MatchError comparison runs other types fallbacks.""" + assert operation(MatchError("foo"), other) is expected_value + + +@pytest.mark.parametrize( + ("operation", "expected_value"), + ( + (operator.eq, "EQ_SENTINEL"), + (operator.ne, "NE_SENTINEL"), + # NOTE: these are swapped because when we do `x < y`, and `x.__lt__(y)` + # NOTE: returns `NotImplemented`, Python will reverse the check into + # NOTE: `y > x`, and so `y.__gt__(x) is called. + # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__ + (operator.lt, "GT_SENTINEL"), + (operator.gt, "LT_SENTINEL"), + ), + ids=("==", "!=", "<", ">"), +) +def test_matcherror_compare_with_dummy_sentinel( + operation: Callable[..., bool], expected_value: str +) -> None: + """Check that MatchError comparison runs other types fallbacks.""" + dummy_obj = DummySentinelTestObject() + # NOTE: This assertion abuses the CPython property to cache short string + # NOTE: objects because the identity check is more precise and we don't + # NOTE: want extra operator protocol methods to influence the test. + assert operation(MatchError("foo"), dummy_obj) is expected_value # type: ignore diff --git a/test/test_mockings.py b/test/test_mockings.py new file mode 100644 index 0000000..304cb6c --- /dev/null +++ b/test/test_mockings.py @@ -0,0 +1,13 @@ +"""Test mockings module.""" +import pytest + +from ansiblelint._mockings import _make_module_stub +from ansiblelint.constants import INVALID_CONFIG_RC + + +def test_make_module_stub() -> None: + """Test make module stub.""" + with pytest.raises(SystemExit) as exc: + _make_module_stub("") + assert exc.type == SystemExit + assert exc.value.code == INVALID_CONFIG_RC diff --git a/test/test_profiles.py b/test/test_profiles.py new file mode 100644 index 0000000..6f7c34f --- /dev/null +++ b/test/test_profiles.py @@ -0,0 +1,50 @@ +"""Tests for the --profile feature.""" +import platform +import subprocess +import sys + +from _pytest.capture import CaptureFixture + +from ansiblelint.rules import RulesCollection, filter_rules_with_profile +from ansiblelint.rules.risky_shell_pipe import ShellWithoutPipefail + + +def test_profile_min() -> None: + """Asserts our ability to unload rules based on profile.""" + collection = RulesCollection() + assert len(collection.rules) == 4, "Unexpected number of implicit rules." + # register one extra rule that we know not to be part of "min" profile + + collection.register(ShellWithoutPipefail()) + assert len(collection.rules) == 5, "Failed to register new rule." + + filter_rules_with_profile(collection.rules, "min") + assert ( + len(collection.rules) == 3 + ), "Failed to unload rule that is not part of 'min' profile." + + +def test_profile_listing(capfd: CaptureFixture[str]) -> None: + """Test that run without arguments it will detect and lint the entire repository.""" + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "-P", + ] + result = subprocess.run(cmd, check=False).returncode + assert result == 0 + + out, err = capfd.readouterr() + + # Confirmation that it runs in auto-detect mode + assert "command-instead-of-module" in out + if sys.version_info < (3, 9): + assert "ansible-lint is no longer tested" in err + else: + # On WSL we might see this warning on stderr: + # [WARNING]: Ansible is being run in a world writable directory + # WSL2 has "WSL2" in platform name but WSL1 has "microsoft": + platform_name = platform.platform().lower() + if all(word not in platform_name for word in ["wsl", "microsoft"]): + assert err == "", platform_name diff --git a/test/test_progressive.py b/test/test_progressive.py new file mode 100644 index 0000000..805291b --- /dev/null +++ b/test/test_progressive.py @@ -0,0 +1,93 @@ +"""Test for the --progressive mode.""" +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +from ansiblelint.constants import GIT_CMD +from ansiblelint.file_utils import cwd + +FAULTY_PLAYBOOK = """--- +- name: faulty + hosts: localhost + tasks: + - name: hello + debug: + msg: world +""" + +CORRECT_PLAYBOOK = """--- +- name: Correct + hosts: localhost + tasks: + - name: Hello + ansible.builtin.debug: + msg: world +""" + + +def git_init() -> None: + """Init temporary git repository.""" + subprocess.run([*GIT_CMD, "init", "--initial-branch=main"], check=True) + subprocess.run([*GIT_CMD, "config", "user.email", "test@example.com"], check=True) + subprocess.run([*GIT_CMD, "config", "user.name", "test"], check=True) + + +def git_commit(filename: Path, content: str) -> None: + """Create and commit a file.""" + filename.write_text(content) + subprocess.run([*GIT_CMD, "add", filename], check=True) + subprocess.run( + [ + *GIT_CMD, + "commit", + "--no-gpg-sign", + "-a", + "-m", + f"Commit {filename}", + ], + check=True, + ) + + +def run_lint(cmd: list[str]) -> subprocess.CompletedProcess[str]: + """Run ansible-lint.""" + # pylint: disable=subprocess-run-check + return subprocess.run( + cmd, + capture_output=True, + text=True, + ) + + +def test_validate_progressive_mode_json_output(tmp_path: Path) -> None: + """Test that covers the following scenarios for progressive mode. + + 1. JSON output is valid in quiet and verbose modes + 2. New files are correctly handled whether lintable paths are passed or not + 3. Regression is not reported when the last commit doesn't add any new violations + """ + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "--progressive", + "-f", + "json", + ] + with cwd(tmp_path): + git_init() + git_commit(tmp_path / "README.md", "pytest") + git_commit(tmp_path / "playbook-faulty.yml", FAULTY_PLAYBOOK) + cmd.append("-q") + res = run_lint(cmd) + assert res.returncode == 2 + json.loads(res.stdout) + + git_commit(tmp_path / "playbook-correct.yml", CORRECT_PLAYBOOK) + cmd.extend(["-vv", "playbook-correct.yml"]) + res = run_lint(cmd) + assert res.returncode == 0 + json.loads(res.stdout) diff --git a/test/test_rule_properties.py b/test/test_rule_properties.py new file mode 100644 index 0000000..7db3afd --- /dev/null +++ b/test/test_rule_properties.py @@ -0,0 +1,16 @@ +"""Tests related to rule properties.""" +from ansiblelint.rules import RulesCollection + + +def test_severity_valid(default_rules_collection: RulesCollection) -> None: + """Test that rules collection only has allow-listed severities.""" + valid_severity_values = [ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW", + "VERY_LOW", + "INFO", + ] + for rule in default_rules_collection: + assert rule.severity in valid_severity_values diff --git a/test/test_rules_collection.py b/test/test_rules_collection.py new file mode 100644 index 0000000..60a2b83 --- /dev/null +++ b/test/test_rules_collection.py @@ -0,0 +1,170 @@ +"""Tests for rule collection class.""" +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +from __future__ import annotations + +import collections +import os +import re + +import pytest + +from ansiblelint.config import options +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.testing import run_ansible_lint + + +@pytest.fixture(name="test_rules_collection") +def fixture_test_rules_collection() -> RulesCollection: + """Create a shared rules collection test instance.""" + return RulesCollection([os.path.abspath("./test/rules/fixtures")]) + + +@pytest.fixture(name="ematchtestfile") +def fixture_ematchtestfile() -> Lintable: + """Produce a test lintable with an id violation.""" + return Lintable("examples/playbooks/ematcher-rule.yml", kind="playbook") + + +@pytest.fixture(name="bracketsmatchtestfile") +def fixture_bracketsmatchtestfile() -> Lintable: + """Produce a test lintable with matching brackets.""" + return Lintable("examples/playbooks/bracketsmatchtest.yml", kind="playbook") + + +def test_load_collection_from_directory(test_rules_collection: RulesCollection) -> None: + """Test that custom rules extend the default ones.""" + # two detected rules plus the internal ones + assert len(test_rules_collection) == 7 + + +def test_run_collection( + test_rules_collection: RulesCollection, ematchtestfile: Lintable +) -> None: + """Test that default rules match pre-meditated violations.""" + matches = test_rules_collection.run(ematchtestfile) + assert len(matches) == 4 # 3 occurrences of BANNED using TEST0001 + 1 for raw-task + assert matches[0].linenumber == 3 + + +def test_tags( + test_rules_collection: RulesCollection, + ematchtestfile: Lintable, + bracketsmatchtestfile: Lintable, +) -> None: + """Test that tags are treated as skip markers.""" + matches = test_rules_collection.run(ematchtestfile, tags={"test1"}) + assert len(matches) == 3 + matches = test_rules_collection.run(ematchtestfile, tags={"test2"}) + assert len(matches) == 0 + matches = test_rules_collection.run(bracketsmatchtestfile, tags={"test1"}) + assert len(matches) == 0 + matches = test_rules_collection.run(bracketsmatchtestfile, tags={"test2"}) + assert len(matches) == 2 + + +def test_skip_tags( + test_rules_collection: RulesCollection, + ematchtestfile: Lintable, + bracketsmatchtestfile: Lintable, +) -> None: + """Test that tags can be skipped.""" + matches = test_rules_collection.run(ematchtestfile, skip_list=["test1", "test3"]) + assert len(matches) == 0 + matches = test_rules_collection.run(ematchtestfile, skip_list=["test2", "test3"]) + assert len(matches) == 3 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["test1"]) + assert len(matches) == 2 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["test2"]) + assert len(matches) == 0 + + +def test_skip_id( + test_rules_collection: RulesCollection, + ematchtestfile: Lintable, + bracketsmatchtestfile: Lintable, +) -> None: + """Check that skipping valid IDs excludes their violations.""" + matches = test_rules_collection.run( + ematchtestfile, skip_list=["TEST0001", "raw-task"] + ) + assert len(matches) == 0 + matches = test_rules_collection.run( + ematchtestfile, skip_list=["TEST0002", "raw-task"] + ) + assert len(matches) == 3 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["TEST0001"]) + assert len(matches) == 2 + matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["TEST0002"]) + assert len(matches) == 0 + + +def test_skip_non_existent_id( + test_rules_collection: RulesCollection, ematchtestfile: Lintable +) -> None: + """Check that skipping invalid IDs changes nothing.""" + matches = test_rules_collection.run(ematchtestfile, skip_list=["DOESNOTEXIST"]) + assert len(matches) == 4 + + +def test_no_duplicate_rule_ids() -> None: + """Check that rules of the collection don't have duplicate IDs.""" + real_rules = RulesCollection([os.path.abspath("./src/ansiblelint/rules")]) + rule_ids = [rule.id for rule in real_rules] + assert not any(y > 1 for y in collections.Counter(rule_ids).values()) + + +def test_rich_rule_listing() -> None: + """Test that rich list format output is rendered as a table. + + This check also offers the contract of having rule id, short and long + descriptions in the console output. + """ + rules_path = os.path.abspath("./test/rules/fixtures") + result = run_ansible_lint("-r", rules_path, "-f", "full", "-L") + assert result.returncode == 0 + + for rule in RulesCollection([rules_path]): + assert rule.id in result.stdout + assert rule.shortdesc in result.stdout + # description could wrap inside table, so we do not check full length + assert rule.description[:30] in result.stdout + + +def test_rules_id_format() -> None: + """Assure all our rules have consistent format.""" + rule_id_re = re.compile("^[a-z-]{4,30}$") + # options.enable_list = ["no-same-owner", "no-log-password", "no-same-owner"] + rules = RulesCollection( + [os.path.abspath("./src/ansiblelint/rules")], options=options, conditional=False + ) + keys: set[str] = set() + for rule in rules: + assert rule_id_re.match( + rule.id + ), f"Rule id {rule.id} did not match our required format." + keys.add(rule.id) + assert ( + rule.help != "" or rule.description or rule.__doc__ + ), f"Rule {rule.id} must have at least one of: .help, .description, .__doc__" + assert "yaml" in keys, "yaml rule is missing" + assert len(rules) == 50 # update this number when adding new rules! + assert len(keys) == len(rules), "Duplicate rule ids?" diff --git a/test/test_runner.py b/test/test_runner.py new file mode 100644 index 0000000..0cd1c77 --- /dev/null +++ b/test/test_runner.py @@ -0,0 +1,159 @@ +"""Tests for runner submodule.""" +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +from __future__ import annotations + +import os +from typing import Any + +import pytest + +from ansiblelint import formatters +from ansiblelint.file_utils import Lintable, abspath +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +LOTS_OF_WARNINGS_PLAYBOOK = abspath( + "examples/playbooks/lots_of_warnings.yml", os.getcwd() +) + + +@pytest.mark.parametrize( + ("playbook", "exclude", "length"), + ( + pytest.param("examples/playbooks/nomatchestest.yml", [], 0, id="nomatchestest"), + pytest.param("examples/playbooks/unicode.yml", [], 1, id="unicode"), + pytest.param( + LOTS_OF_WARNINGS_PLAYBOOK, + [LOTS_OF_WARNINGS_PLAYBOOK], + 0, + id="lots_of_warnings", + ), + pytest.param("examples/playbooks/become.yml", [], 0, id="become"), + pytest.param( + "examples/playbooks/contains_secrets.yml", [], 0, id="contains_secrets" + ), + ), +) +def test_runner( + default_rules_collection: RulesCollection, + playbook: str, + exclude: list[str], + length: int, +) -> None: + """Test that runner can go through any corner cases.""" + runner = Runner(playbook, rules=default_rules_collection, exclude_paths=exclude) + + matches = runner.run() + + assert len(matches) == length + + +def test_runner_exclude_paths(default_rules_collection: RulesCollection) -> None: + """Test that exclude paths do work.""" + runner = Runner( + "examples/playbooks/example.yml", + rules=default_rules_collection, + exclude_paths=["examples/"], + ) + + matches = runner.run() + assert len(matches) == 0 + + +@pytest.mark.parametrize(("exclude_path"), ("**/playbooks/*.yml",)) +def test_runner_exclude_globs( + default_rules_collection: RulesCollection, exclude_path: str +) -> None: + """Test that globs work.""" + runner = Runner( + "examples/playbooks", + rules=default_rules_collection, + exclude_paths=[exclude_path], + ) + + matches = runner.run() + # we expect to find one error from the only .yaml file we have there. + assert len(matches) == 1 + + +@pytest.mark.parametrize( + ("formatter_cls"), + ( + pytest.param(formatters.Formatter, id="Formatter-plain"), + pytest.param(formatters.ParseableFormatter, id="ParseableFormatter-colored"), + pytest.param(formatters.QuietFormatter, id="QuietFormatter-colored"), + pytest.param(formatters.Formatter, id="Formatter-colored"), + ), +) +def test_runner_unicode_format( + default_rules_collection: RulesCollection, + formatter_cls: type[formatters.BaseFormatter[Any]], +) -> None: + """Check that all formatters are unicode-friendly.""" + formatter = formatter_cls(os.getcwd(), display_relative_path=True) + runner = Runner( + Lintable("examples/playbooks/unicode.yml", kind="playbook"), + rules=default_rules_collection, + ) + + matches = runner.run() + + formatter.format(matches[0]) + + +@pytest.mark.parametrize("directory_name", ("test/", os.path.abspath("test"))) +def test_runner_with_directory( + default_rules_collection: RulesCollection, directory_name: str +) -> None: + """Check that runner detects a directory as role.""" + runner = Runner(directory_name, rules=default_rules_collection) + + expected = Lintable(name=directory_name, kind="role") + assert expected in runner.lintables + + +def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> None: + """Ensure that lintables aren't double-checked.""" + checked_files: set[Lintable] = set() + + filename = os.path.abspath("examples/playbooks/common-include-1.yml") + runner = Runner( + filename, + rules=default_rules_collection, + verbosity=0, + checked_files=checked_files, + ) + run1 = runner.run() + assert len(runner.checked_files) == 2 + assert len(run1) == 1 + + filename = os.path.abspath("examples/playbooks/common-include-2.yml") + runner = Runner( + filename, + rules=default_rules_collection, + verbosity=0, + checked_files=checked_files, + ) + run2 = runner.run() + assert len(runner.checked_files) == 3 + # this second run should return 0 because the included filed was already + # processed and added to checked_files, which acts like a bypass list. + assert len(run2) == 0 diff --git a/test/test_schemas.py b/test/test_schemas.py new file mode 100644 index 0000000..5c89c11 --- /dev/null +++ b/test/test_schemas.py @@ -0,0 +1,99 @@ +"""Test schemas modules.""" +import logging +import subprocess +import sys +import urllib +from time import sleep +from typing import Any +from unittest.mock import DEFAULT, MagicMock, patch + +import pytest + +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner +from ansiblelint.schemas import refresh_schemas, validate_file_schema + + +def test_refresh_schemas() -> None: + """Test for schema update skip.""" + # This is written as a single test in order to avoid concurrency issues, + # which caused random issues on CI when the two tests run in parallel + # and or in different order. + assert refresh_schemas(min_age_seconds=3600 * 24 * 365 * 10) == 0 + sleep(1) + # this should disable the cache and force an update + assert refresh_schemas(min_age_seconds=0) == 1 + sleep(1) + # should be cached now + assert refresh_schemas(min_age_seconds=10) == 0 + + +@pytest.mark.parametrize( + ("file", "expected_tags"), + ( + pytest.param( + "examples/changelogs/changelog.yaml", ["schema[changelog]"], id="changelog" + ), + ), +) +def test_schema( + default_rules_collection: RulesCollection, + file: str, + expected_tags: list[str], +) -> None: + """Test that runner can go through any corner cases.""" + runner = Runner(file, rules=default_rules_collection) + matches = runner.run() + + assert len(matches) == len(expected_tags) + for i, match in enumerate(matches): + assert match.tag == expected_tags[i] + + +def urlopen_side_effect(*_args: Any, **kwargs: Any) -> DEFAULT: + """Actual test that timeout parameter is defined.""" + assert "timeout" in kwargs + assert kwargs["timeout"] > 0 + return DEFAULT + + +@patch("urllib.request") +def test_requests_uses_timeout(mock_request: MagicMock) -> None: + """Test that schema refresh uses timeout.""" + mock_request.urlopen.side_effect = urlopen_side_effect + refresh_schemas(min_age_seconds=0) + mock_request.urlopen.assert_called() + + +@patch("urllib.request") +def test_request_timeouterror_handling( + mock_request: MagicMock, caplog: pytest.LogCaptureFixture +) -> None: + """Test that schema refresh can handle time out errors.""" + error_msg = "Simulating handshake operation time out." + mock_request.urlopen.side_effect = urllib.error.URLError(TimeoutError(error_msg)) + with caplog.at_level(logging.DEBUG): + assert refresh_schemas(min_age_seconds=0) == 1 + mock_request.urlopen.assert_called() + assert "Skipped schema refresh due to unexpected exception: " in caplog.text + assert error_msg in caplog.text + + +def test_schema_refresh_cli() -> None: + """Ensure that we test the cli schema refresh command.""" + proc = subprocess.run( + [sys.executable, "-m", "ansiblelint.schemas"], + check=False, + capture_output=True, + text=True, + ) + assert proc.returncode == 0 + + +def test_validate_file_schema() -> None: + """Test file schema validation failure on unknown file kind.""" + lintable = Lintable("foo.bar", kind="") + result = validate_file_schema(lintable) + assert len(result) == 1, result + assert "Unable to find JSON Schema" in result[0] diff --git a/test/test_skip_import_playbook.py b/test/test_skip_import_playbook.py new file mode 100644 index 0000000..8e333fe --- /dev/null +++ b/test/test_skip_import_playbook.py @@ -0,0 +1,48 @@ +"""Test related to skipping import_playbook.""" +from pathlib import Path + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +IMPORTED_PLAYBOOK = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Success # noqa: no-free-form + ansible.builtin.fail: msg="fail" + when: false +""" + +MAIN_PLAYBOOK = """\ +--- +- name: Fixture + hosts: all + + tasks: + - name: Should be shell # noqa: command-instead-of-shell no-changed-when no-free-form + ansible.builtin.shell: echo lol + +- name: Should not be imported + import_playbook: imported_playbook.yml +""" + + +@pytest.fixture(name="playbook") +def fixture_playbook(tmp_path: Path) -> str: + """Create a reusable per-test playbook.""" + playbook_path = tmp_path / "playbook.yml" + playbook_path.write_text(MAIN_PLAYBOOK) + (tmp_path / "imported_playbook.yml").write_text(IMPORTED_PLAYBOOK) + return str(playbook_path) + + +def test_skip_import_playbook( + default_rules_collection: RulesCollection, playbook: str +) -> None: + """Verify that a playbook import is skipped after a failure.""" + runner = Runner(playbook, rules=default_rules_collection) + results = runner.run() + assert len(results) == 0 diff --git a/test/test_skip_inside_yaml.py b/test/test_skip_inside_yaml.py new file mode 100644 index 0000000..d90c45d --- /dev/null +++ b/test/test_skip_inside_yaml.py @@ -0,0 +1,75 @@ +"""Tests related to use of inline noqa.""" +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner +from ansiblelint.testing import RunFromText, run_ansible_lint + +ROLE_TASKS = """\ +--- +- ansible.builtin.debug: + msg: this should fail linting due lack of name +- ansible.builtin.debug: # noqa: unnamed-task + msg: this should pass due to noqa comment +""" + +ROLE_TASKS_WITH_BLOCK = """\ +--- +- name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d +- name: Bad git 2 + action: ansible.builtin.git a=b c=d +- name: Block with rescue and always section + block: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d + rescue: + - name: Bad git 5 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 6 + action: ansible.builtin.git a=b c=d + always: + - name: Bad git 7 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 8 + action: ansible.builtin.git a=b c=d +""" + + +def test_role_tasks(default_text_runner: RunFromText) -> None: + """Check that role tasks can contain skips.""" + results = default_text_runner.run_role_tasks_main(ROLE_TASKS) + assert len(results) == 1, results + assert results[0].linenumber == 2 + assert results[0].tag == "name[missing]" + assert results[0].rule.id == "name" + + +def test_role_tasks_with_block(default_text_runner: RunFromText) -> None: + """Check that blocks in role tasks can contain skips.""" + results = default_text_runner.run_role_tasks_main(ROLE_TASKS_WITH_BLOCK) + assert len(results) == 12 + + +@pytest.mark.parametrize( + ("lintable", "expected"), + (pytest.param("examples/playbooks/test_skip_inside_yaml.yml", 10, id="yaml"),), +) +def test_inline_skips( + default_rules_collection: RulesCollection, lintable: str, expected: int +) -> None: + """Check that playbooks can contain skips.""" + results = Runner(lintable, rules=default_rules_collection).run() + + assert len(results) == expected + + +def test_role_meta() -> None: + """Test running from inside meta folder.""" + role_path = "examples/roles/meta_noqa" + + result = run_ansible_lint("-v", role_path) + assert len(result.stdout) == 0 + assert result.returncode == 0 diff --git a/test/test_skip_playbook_items.py b/test/test_skip_playbook_items.py new file mode 100644 index 0000000..06729af --- /dev/null +++ b/test/test_skip_playbook_items.py @@ -0,0 +1,119 @@ +"""Tests related to use of noqa inside playbooks.""" +import pytest + +from ansiblelint.testing import RunFromText + +PLAYBOOK_PRE_TASKS = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 2 + action: ansible.builtin.git a=b c=d + pre_tasks: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d +""" + +PLAYBOOK_POST_TASKS = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 2 + action: ansible.builtin.git a=b c=d + post_tasks: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d +""" + +PLAYBOOK_HANDLERS = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 2 + action: ansible.builtin.git a=b c=d + handlers: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d +""" + +PLAYBOOK_TWO_PLAYS = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 2 + action: ansible.builtin.git a=b c=d + +- name: Fixture 2 + hosts: all + tasks: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d +""" + +PLAYBOOK_WITH_BLOCK = """\ +--- +- name: Fixture + hosts: all + tasks: + - name: Bad git 1 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 2 + action: ansible.builtin.git a=b c=d + - name: Block with rescue and always section + block: + - name: Bad git 3 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 4 + action: ansible.builtin.git a=b c=d + rescue: + - name: Bad git 5 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 6 + action: ansible.builtin.git a=b c=d + always: + - name: Bad git 7 # noqa: latest[git] + action: ansible.builtin.git a=b c=d + - name: Bad git 8 + action: ansible.builtin.git a=b c=d +""" + + +@pytest.mark.parametrize( + ("playbook", "length"), + ( + pytest.param(PLAYBOOK_PRE_TASKS, 6, id="PRE_TASKS"), + pytest.param(PLAYBOOK_POST_TASKS, 6, id="POST_TASKS"), + pytest.param(PLAYBOOK_HANDLERS, 6, id="HANDLERS"), + pytest.param(PLAYBOOK_TWO_PLAYS, 6, id="TWO_PLAYS"), + pytest.param(PLAYBOOK_WITH_BLOCK, 12, id="WITH_BLOCK"), + ), +) +def test_pre_tasks( + default_text_runner: RunFromText, playbook: str, length: int +) -> None: + """Check that skipping is possible in different playbook parts.""" + # When + results = default_text_runner.run_playbook(playbook) + + # Then + assert len(results) == length diff --git a/test/test_skiputils.py b/test/test_skiputils.py new file mode 100644 index 0000000..5aff266 --- /dev/null +++ b/test/test_skiputils.py @@ -0,0 +1,230 @@ +"""Validate ansiblelint.skip_utils.""" +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import pytest + +from ansiblelint.constants import SKIPPED_RULES_KEY +from ansiblelint.file_utils import Lintable +from ansiblelint.skip_utils import ( + append_skipped_rules, + get_rule_skips_from_line, + is_nested_task, +) +from ansiblelint.testing import RunFromText + +if TYPE_CHECKING: + from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject + +PLAYBOOK_WITH_NOQA = """\ +--- +- name: Fixture + hosts: all + vars: + SOME_VAR_NOQA: "Foo" # noqa: var-naming + SOME_VAR: "Bar" + tasks: + - name: "Set the SOME_OTHER_VAR" + ansible.builtin.set_fact: + SOME_OTHER_VAR_NOQA: "Baz" # noqa: var-naming + SOME_OTHER_VAR: "Bat" +""" + + +@pytest.mark.parametrize( + ("line", "expected"), + ( + ("foo # noqa: bar", "bar"), + ("foo # noqa bar", "bar"), + ), +) +def test_get_rule_skips_from_line(line: str, expected: str) -> None: + """Validate get_rule_skips_from_line.""" + v = get_rule_skips_from_line(line) + assert v == [expected] + + +def test_playbook_noqa(default_text_runner: RunFromText) -> None: + """Check that noqa is properly taken into account on vars and tasks.""" + results = default_text_runner.run_playbook(PLAYBOOK_WITH_NOQA) + # Should raise error at "SOME_VAR". + assert len(results) == 1 + + +def test_playbook_noqa2(default_text_runner: RunFromText) -> None: + """Check that noqa is properly taken into account on vars and tasks.""" + results = default_text_runner.run_playbook(PLAYBOOK_WITH_NOQA, "test") + # Should raise error at "SOME_VAR". + assert len(results) == 1 + + +@pytest.mark.parametrize( + ("lintable", "yaml", "expected_form"), + ( + pytest.param( + Lintable("examples/playbooks/noqa.yml", kind="playbook"), + [ + { + "hosts": "localhost", + "tasks": [ + { + "name": "This would typically fire latest[git] and partial-become", + "become_user": "alice", + "git": "src=/path/to/git/repo dest=checkout", + "__line__": 4, + "__file__": Path("examples/playbooks/noqa.yml"), + } + ], + "__line__": 2, + "__file__": Path("examples/playbooks/noqa.yml"), + } + ], + [ + { + "hosts": "localhost", + "tasks": [ + { + "name": "This would typically fire latest[git] and partial-become", + "become_user": "alice", + "git": "src=/path/to/git/repo dest=checkout", + "__line__": 4, + "__file__": Path("examples/playbooks/noqa.yml"), + SKIPPED_RULES_KEY: ["latest[git]", "partial-become"], + } + ], + "__line__": 2, + "__file__": Path("examples/playbooks/noqa.yml"), + } + ], + ), + pytest.param( + Lintable("examples/playbooks/noqa-nested.yml", kind="playbook"), + [ + { + "hosts": "localhost", + "tasks": [ + { + "name": "Example of multi-level block", + "block": [ + { + "name": "2nd level", + "block": [ + { + "ansible.builtin.debug": { + "msg": "Test unnamed task in block", + "__line__": 9, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + }, + "__line__": 8, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + } + ], + "__line__": 6, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + } + ], + "__line__": 4, + "__file__": Path("examples/playbooks/noqa-nested.yml"), + } + ], + "__line__": 2, + "__file__": Path("examples/playbooks/noqa-nested.yml"), + } + ], + [ + { + "hosts": "localhost", + "tasks": [ + { + "name": "Example of multi-level block", + "block": [ + { + "name": "2nd level", + "block": [ + { + "ansible.builtin.debug": { + "msg": "Test unnamed task in block", + "__line__": 9, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + }, + "__line__": 8, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + SKIPPED_RULES_KEY: ["name[missing]"], + } + ], + "__line__": 6, + "__file__": Path( + "examples/playbooks/noqa-nested.yml" + ), + SKIPPED_RULES_KEY: ["name[missing]"], + } + ], + "__line__": 4, + "__file__": Path("examples/playbooks/noqa-nested.yml"), + SKIPPED_RULES_KEY: ["name[missing]"], + } + ], + "__line__": 2, + "__file__": Path("examples/playbooks/noqa-nested.yml"), + } + ], + ), + ), +) +def test_append_skipped_rules( + lintable: Lintable, + yaml: AnsibleBaseYAMLObject, + expected_form: AnsibleBaseYAMLObject, +) -> None: + """Check that it appends skipped_rules properly.""" + assert append_skipped_rules(yaml, lintable) == expected_form + + +@pytest.mark.parametrize( + ("task", "expected"), + ( + pytest.param( + { + "name": "ensure apache is at the latest version", + "yum": {"name": "httpd", "state": "latest"}, + }, + False, + ), + pytest.param( + { + "name": "Attempt and graceful roll back", + "block": [ + {"name": "Force a failure", "ansible.builtin.command": "/bin/false"} + ], + "rescue": [ + { + "name": "Force a failure in middle of recovery!", + "ansible.builtin.command": "/bin/false", + } + ], + "always": [ + { + "name": "Always do this", + "ansible.builtin.debug": {"msg": "This always executes"}, + } + ], + }, + True, + ), + ), +) +def test_is_nested_task(task: dict[str, Any], expected: bool) -> None: + """Test is_nested_task() returns expected bool.""" + assert is_nested_task(task) == expected diff --git a/test/test_strict.py b/test/test_strict.py new file mode 100644 index 0000000..9a15619 --- /dev/null +++ b/test/test_strict.py @@ -0,0 +1,26 @@ +"""Test strict mode.""" +import pytest + +from ansiblelint.testing import run_ansible_lint + + +@pytest.mark.parametrize( + ("strict", "returncode", "message"), + ( + pytest.param(True, 2, "Failed", id="on"), + pytest.param(False, 0, "Passed", id="off"), + ), +) +def test_strict(strict: bool, returncode: int, message: str) -> None: + """Test running from inside meta folder.""" + args = ["examples/playbooks/strict-mode.yml"] + if strict: + args.insert(0, "--strict") + result = run_ansible_lint(*args) + assert result.returncode == returncode + assert "args[module]" in result.stdout + for summary_line in result.stderr.splitlines(): + if summary_line.startswith(message): + break + else: + pytest.fail(f"Failed to find {message} inside stderr output") diff --git a/test/test_task_includes.py b/test/test_task_includes.py new file mode 100644 index 0000000..3b02d00 --- /dev/null +++ b/test/test_task_includes.py @@ -0,0 +1,47 @@ +"""Tests related to task inclusions.""" +import pytest + +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +@pytest.mark.parametrize( + ("filename", "file_count", "match_count"), + ( + pytest.param("examples/playbooks/blockincludes.yml", 4, 3, id="blockincludes"), + pytest.param( + "examples/playbooks/blockincludes2.yml", + 4, + 3, + id="blockincludes2", + ), + pytest.param("examples/playbooks/taskincludes.yml", 3, 6, id="taskincludes"), + pytest.param("examples/playbooks/taskimports.yml", 5, 3, id="taskimports"), + pytest.param( + "examples/playbooks/include-in-block.yml", + 3, + 1, + id="include-in-block", + ), + pytest.param( + "examples/playbooks/include-import-tasks-in-role.yml", + 4, + 2, + id="role_with_task_inclusions", + ), + ), +) +def test_included_tasks( + default_rules_collection: RulesCollection, + filename: str, + file_count: int, + match_count: int, +) -> None: + """Check if number of loaded files is correct.""" + lintable = Lintable(filename) + default_rules_collection.options.enable_list = ["name[prefix]"] + runner = Runner(lintable, rules=default_rules_collection) + result = runner.run() + assert len(runner.lintables) == file_count + assert len(result) == match_count diff --git a/test/test_text.py b/test/test_text.py new file mode 100644 index 0000000..fa91fee --- /dev/null +++ b/test/test_text.py @@ -0,0 +1,75 @@ +"""Tests for text module.""" +from typing import Any + +import pytest + +from ansiblelint.text import has_glob, has_jinja, strip_ansi_escape, toidentifier + + +@pytest.mark.parametrize( + ("value", "expected"), + ( + pytest.param("\x1b[1;31mHello", "Hello", id="0"), + pytest.param("\x1b[2;37;41mExample_file.zip", "Example_file.zip", id="1"), + pytest.param(b"ansible-lint", "ansible-lint", id="2"), + ), +) +def test_strip_ansi_escape(value: Any, expected: str) -> None: + """Tests for strip_ansi_escape().""" + assert strip_ansi_escape(value) == expected + + +@pytest.mark.parametrize( + ("value", "expected"), + ( + pytest.param("foo-bar", "foo_bar", id="0"), + pytest.param("foo--bar", "foo_bar", id="1"), + ), +) +def test_toidentifier(value: Any, expected: str) -> None: + """Tests for toidentifier().""" + assert toidentifier(value) == expected + + +@pytest.mark.parametrize( + ("value", "expected"), + (pytest.param("example_test.zip", "Unable to convert role name", id="0"),), +) +def test_toidentifier_fail(value: Any, expected: str) -> None: + """Tests for toidentifier().""" + with pytest.raises(RuntimeError) as err: + toidentifier(value) + assert str(err.value).find(expected) > -1 + + +@pytest.mark.parametrize( + ("value", "expected"), + ( + pytest.param("", False, id="0"), + pytest.param("{{ }}", True, id="1"), + pytest.param("foo {# #} bar", True, id="2"), + pytest.param("foo \n{% %} bar", True, id="3"), + pytest.param(None, False, id="4"), + pytest.param(42, False, id="5"), + pytest.param(True, False, id="6"), + ), +) +def test_has_jinja(value: Any, expected: bool) -> None: + """Tests for has_jinja().""" + assert has_jinja(value) == expected + + +@pytest.mark.parametrize( + ("value", "expected"), + ( + pytest.param("", False, id="0"), + pytest.param("*", True, id="1"), + pytest.param("foo.*", True, id="2"), + pytest.param(None, False, id="4"), + pytest.param(42, False, id="5"), + pytest.param(True, False, id="6"), + ), +) +def test_has_glob(value: Any, expected: bool) -> None: + """Tests for has_jinja().""" + assert has_glob(value) == expected diff --git a/test/test_transform_mixin.py b/test/test_transform_mixin.py new file mode 100644 index 0000000..5efdf37 --- /dev/null +++ b/test/test_transform_mixin.py @@ -0,0 +1,133 @@ +"""Tests for TransformMixin.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from ansiblelint.rules import TransformMixin + +if TYPE_CHECKING: + from typing import Any, Dict, List, MutableMapping, MutableSequence, Type, Union + + +DUMMY_MAP: dict[str, Any] = { + "foo": "text", + "bar": {"some": "text2"}, + "fruits": ["apple", "orange"], + "answer": [{"forty-two": ["life", "universe", "everything"]}], +} +DUMMY_LIST: list[dict[str, Any]] = [ + {"foo": "text"}, + {"bar": {"some": "text2"}, "fruits": ["apple", "orange"]}, + {"answer": [{"forty-two": ["life", "universe", "everything"]}]}, +] + + +@pytest.mark.parametrize( + ("yaml_path", "data", "expected_error"), + ( + ([0], DUMMY_MAP, KeyError), + (["bar", 0], DUMMY_MAP, KeyError), + (["fruits", 100], DUMMY_MAP, IndexError), + (["answer", 1], DUMMY_MAP, IndexError), + (["answer", 0, 42], DUMMY_MAP, KeyError), + (["answer", 0, "42"], DUMMY_MAP, KeyError), + ([100], DUMMY_LIST, IndexError), + ([0, 0], DUMMY_LIST, KeyError), + ([0, "wrong key"], DUMMY_LIST, KeyError), + ([1, "bar", "wrong key"], DUMMY_LIST, KeyError), + ([1, "fruits", "index should be int"], DUMMY_LIST, TypeError), + ([1, "fruits", 100], DUMMY_LIST, IndexError), + ), +) +def test_seek_with_bad_path( + yaml_path: list[int | str], + data: MutableMapping[str, Any] | MutableSequence[Any] | str, + expected_error: type[Exception], +) -> None: + """Verify that TransformMixin.seek() propagates errors.""" + with pytest.raises(expected_error): + TransformMixin.seek(yaml_path, data) + + +@pytest.mark.parametrize( + ("yaml_path", "data", "expected"), + ( + ([], DUMMY_MAP, DUMMY_MAP), + (["foo"], DUMMY_MAP, DUMMY_MAP["foo"]), + (["bar"], DUMMY_MAP, DUMMY_MAP["bar"]), + (["bar", "some"], DUMMY_MAP, DUMMY_MAP["bar"]["some"]), + (["fruits"], DUMMY_MAP, DUMMY_MAP["fruits"]), + (["fruits", 0], DUMMY_MAP, DUMMY_MAP["fruits"][0]), + (["fruits", 1], DUMMY_MAP, DUMMY_MAP["fruits"][1]), + (["answer"], DUMMY_MAP, DUMMY_MAP["answer"]), + (["answer", 0], DUMMY_MAP, DUMMY_MAP["answer"][0]), + (["answer", 0, "forty-two"], DUMMY_MAP, DUMMY_MAP["answer"][0]["forty-two"]), + ( + ["answer", 0, "forty-two", 0], + DUMMY_MAP, + DUMMY_MAP["answer"][0]["forty-two"][0], + ), + ( + ["answer", 0, "forty-two", 1], + DUMMY_MAP, + DUMMY_MAP["answer"][0]["forty-two"][1], + ), + ( + ["answer", 0, "forty-two", 2], + DUMMY_MAP, + DUMMY_MAP["answer"][0]["forty-two"][2], + ), + ([], DUMMY_LIST, DUMMY_LIST), + ([0], DUMMY_LIST, DUMMY_LIST[0]), + ([0, "foo"], DUMMY_LIST, DUMMY_LIST[0]["foo"]), + ([1], DUMMY_LIST, DUMMY_LIST[1]), + ([1, "bar"], DUMMY_LIST, DUMMY_LIST[1]["bar"]), + ([1, "bar", "some"], DUMMY_LIST, DUMMY_LIST[1]["bar"]["some"]), + ([1, "fruits"], DUMMY_LIST, DUMMY_LIST[1]["fruits"]), + ([1, "fruits", 0], DUMMY_LIST, DUMMY_LIST[1]["fruits"][0]), + ([1, "fruits", 1], DUMMY_LIST, DUMMY_LIST[1]["fruits"][1]), + ([2], DUMMY_LIST, DUMMY_LIST[2]), + ([2, "answer"], DUMMY_LIST, DUMMY_LIST[2]["answer"]), + ([2, "answer", 0], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]), + ( + [2, "answer", 0, "forty-two"], + DUMMY_LIST, + DUMMY_LIST[2]["answer"][0]["forty-two"], + ), + ( + [2, "answer", 0, "forty-two", 0], + DUMMY_LIST, + DUMMY_LIST[2]["answer"][0]["forty-two"][0], + ), + ( + [2, "answer", 0, "forty-two", 1], + DUMMY_LIST, + DUMMY_LIST[2]["answer"][0]["forty-two"][1], + ), + ( + [2, "answer", 0, "forty-two", 2], + DUMMY_LIST, + DUMMY_LIST[2]["answer"][0]["forty-two"][2], + ), + ( + [], + "this is a string that should be returned as is, ignoring path.", + "this is a string that should be returned as is, ignoring path.", + ), + ( + [2, "answer", 0, "forty-two", 2], + "this is a string that should be returned as is, ignoring path.", + "this is a string that should be returned as is, ignoring path.", + ), + ), +) +def test_seek( + yaml_path: list[int | str], + data: MutableMapping[str, Any] | MutableSequence[Any] | str, + expected: Any, +) -> None: + """Ensure TransformMixin.seek() retrieves the correct data.""" + actual = TransformMixin.seek(yaml_path, data) + assert actual == expected diff --git a/test/test_transformer.py b/test/test_transformer.py new file mode 100644 index 0000000..4b593c0 --- /dev/null +++ b/test/test_transformer.py @@ -0,0 +1,156 @@ +"""Tests for Transformer.""" +from __future__ import annotations + +import os +import pathlib +import shutil +from argparse import Namespace +from typing import Iterator + +import pytest + +from ansiblelint.rules import RulesCollection + +# noinspection PyProtectedMember +from ansiblelint.runner import LintResult, _get_matches +from ansiblelint.transformer import Transformer + + +@pytest.fixture(name="copy_examples_dir") +def fixture_copy_examples_dir( + tmp_path: pathlib.Path, config_options: Namespace +) -> Iterator[tuple[pathlib.Path, pathlib.Path]]: + """Fixture that copies the examples/ dir into a tmpdir.""" + examples_dir = pathlib.Path("examples") + + shutil.copytree(examples_dir, tmp_path / "examples") + old_cwd = os.getcwd() + try: + os.chdir(tmp_path) + config_options.cwd = tmp_path + yield pathlib.Path(old_cwd), tmp_path + finally: + os.chdir(old_cwd) + + +@pytest.fixture(name="runner_result") +def fixture_runner_result( + config_options: Namespace, + default_rules_collection: RulesCollection, + playbook: str, +) -> LintResult: + """Fixture that runs the Runner to populate a LintResult for a given file.""" + config_options.lintables = [playbook] + result = _get_matches(rules=default_rules_collection, options=config_options) + return result + + +@pytest.mark.parametrize( + ("playbook", "matches_count", "transformed"), + ( + # reuse TestRunner::test_runner test cases to ensure transformer does not mangle matches + pytest.param( + "examples/playbooks/nomatchestest.yml", 0, False, id="nomatchestest" + ), + pytest.param("examples/playbooks/unicode.yml", 1, False, id="unicode"), + pytest.param( + "examples/playbooks/lots_of_warnings.yml", 992, False, id="lots_of_warnings" + ), + pytest.param("examples/playbooks/become.yml", 0, False, id="become"), + pytest.param( + "examples/playbooks/contains_secrets.yml", 0, False, id="contains_secrets" + ), + pytest.param( + "examples/playbooks/vars/empty_vars.yml", 0, False, id="empty_vars" + ), + pytest.param("examples/playbooks/vars/strings.yml", 0, True, id="strings"), + ), +) +def test_transformer( # pylint: disable=too-many-arguments, too-many-locals + config_options: Namespace, + copy_examples_dir: tuple[pathlib.Path, pathlib.Path], + playbook: str, + runner_result: LintResult, + transformed: bool, + matches_count: int, +) -> None: + """ + Test that transformer can go through any corner cases. + + Based on TestRunner::test_runner + """ + setattr(config_options, "write_list", ["all"]) + transformer = Transformer(result=runner_result, options=config_options) + transformer.run() + + matches = runner_result.matches + assert len(matches) == matches_count + + orig_dir, tmp_dir = copy_examples_dir + orig_playbook = orig_dir / playbook + expected_playbook = orig_dir / playbook.replace(".yml", ".transformed.yml") + transformed_playbook = tmp_dir / playbook + + orig_playbook_content = orig_playbook.read_text() + expected_playbook_content = expected_playbook.read_text() + transformed_playbook_content = transformed_playbook.read_text() + + if transformed: + assert orig_playbook_content != transformed_playbook_content + else: + assert orig_playbook_content == transformed_playbook_content + + assert transformed_playbook_content == expected_playbook_content + + +@pytest.mark.parametrize( + ("write_list", "expected"), + ( + # 1 item + (["all"], {"all"}), + (["none"], {"none"}), + (["rule-id"], {"rule-id"}), + # 2 items + (["all", "all"], {"all"}), + (["all", "none"], {"none"}), + (["all", "rule-id"], {"all"}), + (["none", "all"], {"all"}), + (["none", "none"], {"none"}), + (["none", "rule-id"], {"rule-id"}), + (["rule-id", "all"], {"all"}), + (["rule-id", "none"], {"none"}), + (["rule-id", "rule-id"], {"rule-id"}), + # 3 items + (["all", "all", "all"], {"all"}), + (["all", "all", "none"], {"none"}), + (["all", "all", "rule-id"], {"all"}), + (["all", "none", "all"], {"all"}), + (["all", "none", "none"], {"none"}), + (["all", "none", "rule-id"], {"rule-id"}), + (["all", "rule-id", "all"], {"all"}), + (["all", "rule-id", "none"], {"none"}), + (["all", "rule-id", "rule-id"], {"all"}), + (["none", "all", "all"], {"all"}), + (["none", "all", "none"], {"none"}), + (["none", "all", "rule-id"], {"all"}), + (["none", "none", "all"], {"all"}), + (["none", "none", "none"], {"none"}), + (["none", "none", "rule-id"], {"rule-id"}), + (["none", "rule-id", "all"], {"all"}), + (["none", "rule-id", "none"], {"none"}), + (["none", "rule-id", "rule-id"], {"rule-id"}), + (["rule-id", "all", "all"], {"all"}), + (["rule-id", "all", "none"], {"none"}), + (["rule-id", "all", "rule-id"], {"all"}), + (["rule-id", "none", "all"], {"all"}), + (["rule-id", "none", "none"], {"none"}), + (["rule-id", "none", "rule-id"], {"rule-id"}), + (["rule-id", "rule-id", "all"], {"all"}), + (["rule-id", "rule-id", "none"], {"none"}), + (["rule-id", "rule-id", "rule-id"], {"rule-id"}), + ), +) +def test_effective_write_set(write_list: list[str], expected: set[str]) -> None: + """Make sure effective_write_set handles all/none keywords correctly.""" + actual = Transformer.effective_write_set(write_list) + assert actual == expected diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 0000000..ca2461c --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,405 @@ +# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Tests for generic utility functions.""" +from __future__ import annotations + +import logging +import subprocess +import sys +from argparse import Namespace +from pathlib import Path +from typing import Any, Sequence + +import pytest +from _pytest.capture import CaptureFixture +from _pytest.logging import LogCaptureFixture +from _pytest.monkeypatch import MonkeyPatch +from ansible.utils.sentinel import Sentinel +from ansible_compat.runtime import Runtime + +from ansiblelint import cli, constants, utils +from ansiblelint.__main__ import initialize_logger +from ansiblelint.cli import get_rules_dirs +from ansiblelint.constants import VIOLATIONS_FOUND_RC +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + +runtime = Runtime(require_module=True) + + +@pytest.mark.parametrize( + ("string", "expected_cmd", "expected_args", "expected_kwargs"), + ( + pytest.param("", "", [], {}, id="blank"), + pytest.param("vars:", "vars", [], {}, id="single_word"), + pytest.param("hello: a=1", "hello", [], {"a": "1"}, id="string_module_and_arg"), + pytest.param("action: hello a=1", "hello", [], {"a": "1"}, id="strips_action"), + pytest.param( + "action: whatever bobbins x=y z=x c=3", + "whatever", + ["bobbins", "x=y", "z=x", "c=3"], + {}, + id="more_than_one_arg", + ), + pytest.param( + "action: command chdir=wxy creates=zyx tar xzf zyx.tgz", + "command", + ["tar", "xzf", "zyx.tgz"], + {"chdir": "wxy", "creates": "zyx"}, + id="command_with_args", + ), + ), +) +def test_tokenize( + string: str, + expected_cmd: str, + expected_args: Sequence[str], + expected_kwargs: dict[str, Any], +) -> None: + """Test that tokenize works for different input types.""" + (cmd, args, kwargs) = utils.tokenize(string) + assert cmd == expected_cmd + assert args == expected_args + assert kwargs == expected_kwargs + + +@pytest.mark.parametrize( + ("reference_form", "alternate_forms"), + ( + pytest.param( + {"name": "hello", "action": "command chdir=abc echo hello world"}, + ({"name": "hello", "command": "chdir=abc echo hello world"},), + id="simple_command", + ), + pytest.param( + {"git": {"version": "abc"}, "args": {"repo": "blah", "dest": "xyz"}}, + ( + {"git": {"version": "abc", "repo": "blah", "dest": "xyz"}}, + {"git": "version=abc repo=blah dest=xyz"}, + { + "git": None, + "args": {"repo": "blah", "dest": "xyz", "version": "abc"}, + }, + ), + id="args", + ), + ), +) +def test_normalize( + reference_form: dict[str, Any], alternate_forms: tuple[dict[str, Any]] +) -> None: + """Test that tasks specified differently are normalized same way.""" + normal_form = utils.normalize_task(reference_form, "tasks.yml") + + for form in alternate_forms: + assert normal_form == utils.normalize_task(form, "tasks.yml") + + +def test_normalize_complex_command() -> None: + """Test that tasks specified differently are normalized same way.""" + task1 = { + "name": "hello", + "action": {"module": "pip", "name": "df", "editable": "false"}, + } + task2 = {"name": "hello", "pip": {"name": "df", "editable": "false"}} + task3 = {"name": "hello", "pip": "name=df editable=false"} + task4 = {"name": "hello", "action": "pip name=df editable=false"} + assert utils.normalize_task(task1, "tasks.yml") == utils.normalize_task( + task2, "tasks.yml" + ) + assert utils.normalize_task(task2, "tasks.yml") == utils.normalize_task( + task3, "tasks.yml" + ) + assert utils.normalize_task(task3, "tasks.yml") == utils.normalize_task( + task4, "tasks.yml" + ) + + +@pytest.mark.parametrize( + ("task", "expected_form"), + ( + pytest.param( + { + "name": "ensure apache is at the latest version", + "yum": {"name": "httpd", "state": "latest"}, + }, + { + "delegate_to": Sentinel, + "name": "ensure apache is at the latest version", + "action": { + "__ansible_module__": "yum", + "__ansible_module_original__": "yum", + "__ansible_arguments__": [], + "name": "httpd", + "state": "latest", + }, + }, + ), + pytest.param( + { + "name": "Attempt and graceful roll back", + "block": [ + { + "name": "Install httpd and memcached", + "ansible.builtin.yum": ["httpd", "memcached"], + "state": "present", + } + ], + }, + { + "name": "Attempt and graceful roll back", + "block": [ + { + "name": "Install httpd and memcached", + "ansible.builtin.yum": ["httpd", "memcached"], + "state": "present", + } + ], + "action": { + "__ansible_module__": "block/always/rescue", + "__ansible_module_original__": "block/always/rescue", + }, + }, + ), + ), +) +def test_normalize_task_v2(task: dict[str, Any], expected_form: dict[str, Any]) -> None: + """Check that it normalizes task and returns the expected form.""" + assert utils.normalize_task_v2(task) == expected_form + + +def test_extract_from_list() -> None: + """Check that tasks get extracted from blocks if present.""" + block = { + "block": [{"tasks": {"name": "hello", "command": "whoami"}}], + "test_none": None, + "test_string": "foo", + } + blocks = [block] + + test_list = utils.extract_from_list(blocks, ["block"]) + test_none = utils.extract_from_list(blocks, ["test_none"]) + + assert list(block["block"]) == test_list # type: ignore + assert not test_none + with pytest.raises(RuntimeError): + utils.extract_from_list(blocks, ["test_string"]) + + +def test_extract_from_list_recursive() -> None: + """Check that tasks get extracted from blocks if present.""" + block = { + "block": [{"block": [{"name": "hello", "command": "whoami"}]}], + } + blocks = [block] + + test_list = utils.extract_from_list(blocks, ["block"]) + assert list(block["block"]) == test_list + + test_list_recursive = utils.extract_from_list(blocks, ["block"], recursive=True) + assert block["block"] + block["block"][0]["block"] == test_list_recursive + + +@pytest.mark.parametrize( + ("template", "output"), + ( + pytest.param("{{ playbook_dir }}", "/a/b/c", id="simple"), + pytest.param( + "{{ 'hello' | doesnotexist }}", + "hello", # newer implementation ignores unknown filters + id="unknown_filter", + ), + pytest.param( + "{{ hello | to_json }}", + "{{ hello | to_json }}", + id="to_json_filter_on_undefined_variable", + ), + pytest.param( + "{{ hello | to_nice_yaml }}", + "{{ hello | to_nice_yaml }}", + id="to_nice_yaml_filter_on_undefined_variable", + ), + ), +) +def test_template(template: str, output: str) -> None: + """Verify that resolvable template vars and filters get rendered.""" + result = utils.template( + basedir="/base/dir", + value=template, + variables={"playbook_dir": "/a/b/c"}, + fail_on_error=False, + ) + assert result == output + + +def test_task_to_str_unicode() -> None: + """Ensure that extracting messages from tasks preserves Unicode.""" + task = {"fail": {"msg": "unicode é ô à "}} + result = utils.task_to_str(utils.normalize_task(task, "filename.yml")) + assert result == "fail msg=unicode é ô à " + + +def test_logger_debug(caplog: LogCaptureFixture) -> None: + """Test that the double verbosity arg causes logger to be DEBUG.""" + options = cli.get_config(["-vv"]) + initialize_logger(options.verbosity) + + expected_info = ( + "ansiblelint.__main__", + logging.DEBUG, + "Logging initialized to level 10", + ) + + assert expected_info in caplog.record_tuples + + +def test_cli_auto_detect(capfd: CaptureFixture[str]) -> None: + """Test that run without arguments it will detect and lint the entire repository.""" + cmd = [ + sys.executable, + "-m", + "ansiblelint", + "-x", + "schema", # exclude schema as our test file would fail it + "-v", + "-p", + "--nocolor", + ] + result = subprocess.run(cmd, check=False).returncode + + # We de expect to fail on our own repo due to test examples we have + assert result == VIOLATIONS_FOUND_RC + + out, err = capfd.readouterr() + + # Confirmation that it runs in auto-detect mode + assert "Discovered files to lint using: git" in err + assert "Excluded removed files using: git" in err + # An expected rule match from our examples + assert ( + "examples/playbooks/empty_playbook.yml:1:1: " + "syntax-check[empty-playbook]: Empty playbook, nothing to do" in out + ) + # assures that our ansible-lint config exclude was effective in excluding github files + assert "Identified: .github/" not in out + # assures that we can parse playbooks as playbooks + assert "Identified: test/test/always-run-success.yml" not in err + assert ( + "Executing syntax check on playbook examples/playbooks/mocked_dependency.yml" + in err + ) + + +def test_is_playbook() -> None: + """Verify that we can detect a playbook as a playbook.""" + assert utils.is_playbook("examples/playbooks/always-run-success.yml") + + +def test_auto_detect_exclude(monkeypatch: MonkeyPatch) -> None: + """Verify that exclude option can be used to narrow down detection.""" + options = cli.get_config(["--exclude", "foo"]) + + # pylint: disable=unused-argument + def mockreturn(options: Namespace) -> list[str]: + return ["foo/playbook.yml", "bar/playbook.yml"] + + monkeypatch.setattr(utils, "discover_lintables", mockreturn) + result = utils.get_lintables(options) + assert result == [Lintable("bar/playbook.yml", kind="playbook")] + + +_DEFAULT_RULEDIRS = [constants.DEFAULT_RULESDIR] +_CUSTOM_RULESDIR = Path(__file__).parent / "custom_rules" +_CUSTOM_RULEDIRS = [ + str(_CUSTOM_RULESDIR / "example_inc"), + str(_CUSTOM_RULESDIR / "example_com"), +] + + +@pytest.mark.parametrize( + ("user_ruledirs", "use_default", "expected"), + ( + ([], True, _DEFAULT_RULEDIRS), + ([], False, _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, True, _CUSTOM_RULEDIRS + _DEFAULT_RULEDIRS), + (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS), + ), +) +def test_get_rules_dirs( + user_ruledirs: list[str], use_default: bool, expected: list[str] +) -> None: + """Test it returns expected dir lists.""" + assert get_rules_dirs(user_ruledirs, use_default) == expected + + +@pytest.mark.parametrize( + ("user_ruledirs", "use_default", "expected"), + ( + ([], True, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), + ([], False, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), + ( + _CUSTOM_RULEDIRS, + True, + _CUSTOM_RULEDIRS + sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS, + ), + (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS), + ), +) +def test_get_rules_dirs_with_custom_rules( + user_ruledirs: list[str], + use_default: bool, + expected: list[str], + monkeypatch: MonkeyPatch, +) -> None: + """Test it returns expected dir lists when custom rules exist.""" + monkeypatch.setenv(constants.CUSTOM_RULESDIR_ENVVAR, str(_CUSTOM_RULESDIR)) + assert get_rules_dirs(user_ruledirs, use_default) == expected + + +def test_nested_items() -> None: + """Verify correct function of nested_items().""" + data = {"foo": "text", "bar": {"some": "text2"}, "fruits": ["apple", "orange"]} + + items = [ + ("foo", "text", ""), + ("bar", {"some": "text2"}, ""), + ("some", "text2", "bar"), + ("fruits", ["apple", "orange"], ""), + ("list-item", "apple", "fruits"), + ("list-item", "orange", "fruits"), + ] + with pytest.deprecated_call( + match=r"Call to deprecated function ansiblelint\.utils\.nested_items.*" + ): + assert list(utils.nested_items(data)) == items + + +def test_find_children() -> None: + """Verify correct function of find_children().""" + utils.find_children(Lintable("examples/playbooks/find_children.yml")) + + +def test_find_children_in_task(default_rules_collection: RulesCollection) -> None: + """Verify correct function of find_children() in tasks.""" + Runner( + Lintable("examples/playbooks/tasks/bug-2875.yml"), + rules=default_rules_collection, + ).run() diff --git a/test/test_verbosity.py b/test/test_verbosity.py new file mode 100644 index 0000000..18d7fd5 --- /dev/null +++ b/test/test_verbosity.py @@ -0,0 +1,96 @@ +"""Tests related to our logging/verbosity setup.""" +from __future__ import annotations + +import os + +import pytest + +from ansiblelint.testing import run_ansible_lint + + +# substrs is a list of tuples, where: +# component 1 is the substring in question +# component 2 is whether or not to invert ("NOT") the match +@pytest.mark.parametrize( + ("verbosity", "substrs"), + ( + ( + "", + [ + ("WARNING Listing 1 violation(s) that are fatal", False), + ("DEBUG ", True), + ("INFO ", True), + ], + ), + ( + "-q", + [ + ("WARNING ", True), + ("DEBUG ", True), + ("INFO ", True), + ], + ), + ( + "-qq", + [ + ("WARNING ", True), + ("DEBUG ", True), + ("INFO ", True), + ], + ), + ( + "-v", + [ + ("WARNING Listing 1 violation(s) that are fatal", False), + ("INFO Set ANSIBLE_LIBRARY=", False), + ("DEBUG ", True), + ], + ), + ( + "-vv", + [ + # ("DEBUG Loading custom .yamllint config file,", False), + ("WARNING Listing 1 violation(s) that are fatal", False), + ("INFO Set ANSIBLE_LIBRARY=", False), + # ("DEBUG Effective yamllint rules used", False), + ], + ), + ( + "-vvvvvvvvvvvvvvvvvvvvvvvvv", + [ + # ("DEBUG Loading custom .yamllint config file,", False), + ("WARNING Listing 1 violation(s) that are fatal", False), + ("INFO Set ANSIBLE_LIBRARY=", False), + # ("DEBUG Effective yamllint rules used", False), + ], + ), + ), + ids=( + "default-verbosity", + "quiet", + "really-quiet", + "loquacious", + "really-loquacious", + 'really-loquacious but with more "v"s -- same as -vv', + ), +) +def test_default_verbosity(verbosity: str, substrs: list[tuple[str, bool]]) -> None: + """Checks that our default verbosity displays (only) warnings.""" + # Piggyback off the .yamllint in the root of the repo, just for testing. + # We'll "override" it with the one in the fixture, to produce a warning. + cwd = os.path.realpath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") + ) + + fakerole = os.path.join("test", "fixtures", "verbosity-tests") + + if verbosity: + result = run_ansible_lint(verbosity, fakerole, cwd=cwd) + else: + result = run_ansible_lint(fakerole, cwd=cwd) + + for substr, invert in substrs: + if invert: + assert substr not in result.stderr, result.stderr + else: + assert substr in result.stderr, result.stderr diff --git a/test/test_with_skip_tagid.py b/test/test_with_skip_tagid.py new file mode 100644 index 0000000..34973f5 --- /dev/null +++ b/test/test_with_skip_tagid.py @@ -0,0 +1,58 @@ +"""Tests related to skip tag id.""" +from ansiblelint.rules import RulesCollection +from ansiblelint.rules.yaml_rule import YamllintRule +from ansiblelint.runner import Runner +from ansiblelint.testing import run_ansible_lint + +FILE = "examples/playbooks/with-skip-tag-id.yml" +collection = RulesCollection() +collection.register(YamllintRule()) + + +def test_negative_no_param() -> None: + """Negative test no param.""" + bad_runner = Runner(FILE, rules=collection) + errs = bad_runner.run() + assert len(errs) > 0 + + +def test_negative_with_id() -> None: + """Negative test with_id.""" + with_id = "yaml" + bad_runner = Runner(FILE, rules=collection, tags=frozenset([with_id])) + errs = bad_runner.run() + assert len(errs) == 1 + + +def test_negative_with_tag() -> None: + """Negative test with_tag.""" + with_tag = "trailing-spaces" + bad_runner = Runner(FILE, rules=collection, tags=frozenset([with_tag])) + errs = bad_runner.run() + assert len(errs) == 1 + + +def test_positive_skip_id() -> None: + """Positive test skip_id.""" + skip_id = "yaml" + good_runner = Runner(FILE, rules=collection, skip_list=[skip_id]) + assert [] == good_runner.run() + + +def test_positive_skip_tag() -> None: + """Positive test skip_tag.""" + skip_tag = "yaml[trailing-spaces]" + good_runner = Runner(FILE, rules=collection, skip_list=[skip_tag]) + assert [] == good_runner.run() + + +def test_run_skip_rule() -> None: + """Test that we can skip a rule with -x.""" + result = run_ansible_lint( + "-x", + "name[casing]", + "examples/playbooks/rule-name-casing.yml", + executable="ansible-lint", + ) + assert result.returncode == 0 + assert result.stdout == "" diff --git a/test/test_yaml_utils.py b/test/test_yaml_utils.py new file mode 100644 index 0000000..8af486b --- /dev/null +++ b/test/test_yaml_utils.py @@ -0,0 +1,905 @@ +"""Tests for yaml-related utility functions.""" +from __future__ import annotations + +from io import StringIO +from pathlib import Path +from typing import Any + +import pytest +from ruamel.yaml.comments import CommentedMap, CommentedSeq +from ruamel.yaml.emitter import Emitter +from ruamel.yaml.main import YAML +from yamllint.linter import run as run_yamllint + +import ansiblelint.yaml_utils +from ansiblelint.file_utils import Lintable + +fixtures_dir = Path(__file__).parent / "fixtures" +formatting_before_fixtures_dir = fixtures_dir / "formatting-before" +formatting_prettier_fixtures_dir = fixtures_dir / "formatting-prettier" +formatting_after_fixtures_dir = fixtures_dir / "formatting-after" + + +@pytest.fixture(name="empty_lintable") +def fixture_empty_lintable() -> Lintable: + """Return a Lintable with no contents.""" + lintable = Lintable("__empty_file__.yaml", content="") + return lintable + + +def test_iter_tasks_in_file_with_empty_file(empty_lintable: Lintable) -> None: + """Make sure that iter_tasks_in_file returns early when files are empty.""" + res = list(ansiblelint.yaml_utils.iter_tasks_in_file(empty_lintable)) + assert not res + + +def test_nested_items_path() -> None: + """Verify correct function of nested_items_path().""" + data = { + "foo": "text", + "bar": {"some": "text2"}, + "fruits": ["apple", "orange"], + "answer": [{"forty-two": ["life", "universe", "everything"]}], + } + + items = [ + ("foo", "text", []), + ("bar", {"some": "text2"}, []), + ("some", "text2", ["bar"]), + ("fruits", ["apple", "orange"], []), + (0, "apple", ["fruits"]), + (1, "orange", ["fruits"]), + ("answer", [{"forty-two": ["life", "universe", "everything"]}], []), + (0, {"forty-two": ["life", "universe", "everything"]}, ["answer"]), + ("forty-two", ["life", "universe", "everything"], ["answer", 0]), + (0, "life", ["answer", 0, "forty-two"]), + (1, "universe", ["answer", 0, "forty-two"]), + (2, "everything", ["answer", 0, "forty-two"]), + ] + assert list(ansiblelint.yaml_utils.nested_items_path(data)) == items + + +@pytest.mark.parametrize( + "invalid_data_input", + ( + "string", + 42, + 1.234, + ("tuple",), + {"set"}, + # NoneType is no longer include, as we assume we have to ignore it + ), +) +def test_nested_items_path_raises_typeerror(invalid_data_input: Any) -> None: + """Verify non-dict/non-list types make nested_items_path() raises TypeError.""" + with pytest.raises(TypeError, match=r"Expected a dict or a list.*"): + list(ansiblelint.yaml_utils.nested_items_path(invalid_data_input)) + + +_input_playbook = [ + { + "name": "It's a playbook", # unambiguous; no quotes needed + "tasks": [ + { + "name": '"fun" task', # should be a single-quoted string + "debug": { + # ruamel.yaml default to single-quotes + # our Emitter defaults to double-quotes + "msg": "{{ msg }}", + }, + } + ], + } +] +_SINGLE_QUOTE_WITHOUT_INDENTS = """\ +--- +- name: It's a playbook + tasks: + - name: '"fun" task' + debug: + msg: '{{ msg }}' +""" +_SINGLE_QUOTE_WITH_INDENTS = """\ +--- + - name: It's a playbook + tasks: + - name: '"fun" task' + debug: + msg: '{{ msg }}' +""" +_DOUBLE_QUOTE_WITHOUT_INDENTS = """\ +--- +- name: It's a playbook + tasks: + - name: '"fun" task' + debug: + msg: "{{ msg }}" +""" +_DOUBLE_QUOTE_WITH_INDENTS_EXCEPT_ROOT_LEVEL = """\ +--- +- name: It's a playbook + tasks: + - name: '"fun" task' + debug: + msg: "{{ msg }}" +""" + + +@pytest.mark.parametrize( + ( + "map_indent", + "sequence_indent", + "sequence_dash_offset", + "alternate_emitter", + "expected_output", + ), + ( + pytest.param( + 2, + 2, + 0, + None, + _SINGLE_QUOTE_WITHOUT_INDENTS, + id="single_quote_without_indents", + ), + pytest.param( + 2, + 4, + 2, + None, + _SINGLE_QUOTE_WITH_INDENTS, + id="single_quote_with_indents", + ), + pytest.param( + 2, + 2, + 0, + ansiblelint.yaml_utils.FormattedEmitter, + _DOUBLE_QUOTE_WITHOUT_INDENTS, + id="double_quote_without_indents", + ), + pytest.param( + 2, + 4, + 2, + ansiblelint.yaml_utils.FormattedEmitter, + _DOUBLE_QUOTE_WITH_INDENTS_EXCEPT_ROOT_LEVEL, + id="double_quote_with_indents_except_root_level", + ), + ), +) +def test_custom_ruamel_yaml_emitter( + map_indent: int, + sequence_indent: int, + sequence_dash_offset: int, + alternate_emitter: Emitter | None, + expected_output: str, +) -> None: + """Test ``ruamel.yaml.YAML.dump()`` sequence formatting and quotes.""" + yaml = YAML(typ="rt") + # NB: ruamel.yaml does not have typehints, so mypy complains about everything here. + yaml.explicit_start = True # type: ignore[assignment] + yaml.map_indent = map_indent # type: ignore[assignment] + yaml.sequence_indent = sequence_indent # type: ignore[assignment] + yaml.sequence_dash_offset = sequence_dash_offset + if alternate_emitter is not None: + yaml.Emitter = alternate_emitter + # ruamel.yaml only writes to a stream (there is no `dumps` function) + with StringIO() as output_stream: + yaml.dump(_input_playbook, output_stream) + output = output_stream.getvalue() + assert output == expected_output + + +@pytest.fixture(name="yaml_formatting_fixtures") +def fixture_yaml_formatting_fixtures(fixture_filename: str) -> tuple[str, str, str]: + """Get the contents for the formatting fixture files. + + To regenerate these fixtures, please run ``pytest --regenerate-formatting-fixtures``. + + Ideally, prettier should not have to change any ``formatting-after`` fixtures. + """ + before_path = formatting_before_fixtures_dir / fixture_filename + prettier_path = formatting_prettier_fixtures_dir / fixture_filename + after_path = formatting_after_fixtures_dir / fixture_filename + before_content = before_path.read_text() + prettier_content = prettier_path.read_text() + formatted_content = after_path.read_text() + return before_content, prettier_content, formatted_content + + +@pytest.mark.parametrize( + "fixture_filename", + ( + "fmt-1.yml", + "fmt-2.yml", + "fmt-3.yml", + ), +) +def test_formatted_yaml_loader_dumper( + yaml_formatting_fixtures: tuple[str, str, str], + fixture_filename: str, +) -> None: + """Ensure that FormattedYAML loads/dumps formatting fixtures consistently.""" + # pylint: disable=unused-argument + before_content, prettier_content, after_content = yaml_formatting_fixtures + assert before_content != prettier_content + assert before_content != after_content + + yaml = ansiblelint.yaml_utils.FormattedYAML() + + data_before = yaml.loads(before_content) + dump_from_before = yaml.dumps(data_before) + data_prettier = yaml.loads(prettier_content) + dump_from_prettier = yaml.dumps(data_prettier) + data_after = yaml.loads(after_content) + dump_from_after = yaml.dumps(data_after) + + # comparing data does not work because the Comment objects + # have different IDs even if contents do not match. + + assert dump_from_before == after_content + assert dump_from_prettier == after_content + assert dump_from_after == after_content + + # We can't do this because FormattedYAML is stricter in some cases: + # assert prettier_content == after_content + # + # Instead, `pytest --regenerate-formatting-fixtures` will fail if prettier would + # change any files in test/fixtures/formatting-after + + # Running our files through yamllint, after we reformatted them, + # should not yield any problems. + config = ansiblelint.yaml_utils.load_yamllint_config() + assert not list(run_yamllint(after_content, config)) + + +@pytest.fixture(name="lintable") +def fixture_lintable(file_path: str) -> Lintable: + """Return a playbook Lintable for use in ``get_path_to_*`` tests.""" + return Lintable(file_path) + + +@pytest.fixture(name="ruamel_data") +def fixture_ruamel_data(lintable: Lintable) -> CommentedMap | CommentedSeq: + """Return the loaded YAML data for the Lintable.""" + yaml = ansiblelint.yaml_utils.FormattedYAML() + data: CommentedMap | CommentedSeq = yaml.loads(lintable.content) + return data + + +@pytest.mark.parametrize( + ("file_path", "line_number", "expected_path"), + ( + # ignored lintables + pytest.param( + "examples/playbooks/tasks/passing_task.yml", 2, [], id="ignore_tasks_file" + ), + pytest.param( + "examples/roles/more_complex/handlers/main.yml", + 2, + [], + id="ignore_handlers_file", + ), + pytest.param("examples/playbooks/vars/other.yml", 2, [], id="ignore_vars_file"), + pytest.param( + "examples/host_vars/localhost.yml", 2, [], id="ignore_host_vars_file" + ), + pytest.param("examples/group_vars/all.yml", 2, [], id="ignore_group_vars_file"), + pytest.param( + "examples/inventory/inventory.yml", 2, [], id="ignore_inventory_file" + ), + pytest.param( + "examples/roles/dependency_in_meta/meta/main.yml", + 2, + [], + id="ignore_meta_file", + ), + pytest.param( + "examples/reqs_v1/requirements.yml", 2, [], id="ignore_requirements_v1_file" + ), + pytest.param( + "examples/reqs_v2/requirements.yml", 2, [], id="ignore_requirements_v2_file" + ), + # we don't have any release notes examples. Oh well. + # pytest.param("examples/", 2, [], id="ignore_release_notes_file"), + pytest.param( + ".pre-commit-config.yaml", 2, [], id="ignore_unrecognized_yaml_file" + ), + # playbook lintables + pytest.param( + "examples/playbooks/become.yml", + 1, + [], + id="1_play_playbook-line_before_play", + ), + pytest.param( + "examples/playbooks/become.yml", + 2, + [0], + id="1_play_playbook-first_line_in_play", + ), + pytest.param( + "examples/playbooks/become.yml", + 10, + [0], + id="1_play_playbook-middle_line_in_play", + ), + pytest.param( + "examples/playbooks/become.yml", + 100, + [0], + id="1_play_playbook-line_after_eof", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 1, + [], + id="4_play_playbook-line_before_play_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 2, + [0], + id="4_play_playbook-first_line_in_play_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 5, + [0], + id="4_play_playbook-middle_line_in_play_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 9, + [0], + id="4_play_playbook-last_line_in_play_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 10, + [1], + id="4_play_playbook-first_line_in_play_2", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 14, + [1], + id="4_play_playbook-middle_line_in_play_2", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 18, + [1], + id="4_play_playbook-last_line_in_play_2", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 19, + [2], + id="4_play_playbook-first_line_in_play_3", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 23, + [2], + id="4_play_playbook-middle_line_in_play_3", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 27, + [2], + id="4_play_playbook-last_line_in_play_3", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 28, + [3], + id="4_play_playbook-first_line_in_play_4", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 31, + [3], + id="4_play_playbook-middle_line_in_play_4", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 35, + [3], + id="4_play_playbook-last_line_in_play_4", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 100, + [3], + id="4_play_playbook-line_after_eof", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 1, + [], + id="import_playbook-line_before_play_1", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 2, + [0], + id="import_playbook-first_line_in_play_1", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 3, + [0], + id="import_playbook-middle_line_in_play_1", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 4, + [0], + id="import_playbook-last_line_in_play_1", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 5, + [1], + id="import_playbook-first_line_in_play_2", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 6, + [1], + id="import_playbook-middle_line_in_play_2", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 7, + [1], + id="import_playbook-last_line_in_play_2", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 8, + [2], + id="import_playbook-first_line_in_play_3", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 9, + [2], + id="import_playbook-last_line_in_play_3", + ), + pytest.param( + "examples/playbooks/playbook-parent.yml", + 15, + [2], + id="import_playbook-line_after_eof", + ), + ), +) +def test_get_path_to_play( + lintable: Lintable, + line_number: int, + ruamel_data: CommentedMap | CommentedSeq, + expected_path: list[int | str], +) -> None: + """Ensure ``get_path_to_play`` returns the expected path given a file + line.""" + path_to_play = ansiblelint.yaml_utils.get_path_to_play( + lintable, line_number, ruamel_data + ) + assert path_to_play == expected_path + + +@pytest.mark.parametrize( + ("file_path", "line_number", "expected_path"), + ( + # ignored lintables + pytest.param("examples/playbooks/vars/other.yml", 2, [], id="ignore_vars_file"), + pytest.param( + "examples/host_vars/localhost.yml", 2, [], id="ignore_host_vars_file" + ), + pytest.param("examples/group_vars/all.yml", 2, [], id="ignore_group_vars_file"), + pytest.param( + "examples/inventory/inventory.yml", 2, [], id="ignore_inventory_file" + ), + pytest.param( + "examples/roles/dependency_in_meta/meta/main.yml", + 2, + [], + id="ignore_meta_file", + ), + pytest.param( + "examples/reqs_v1/requirements.yml", 2, [], id="ignore_requirements_v1_file" + ), + pytest.param( + "examples/reqs_v2/requirements.yml", 2, [], id="ignore_requirements_v2_file" + ), + # we don't have any release notes examples. Oh well. + # pytest.param("examples/", 2, [], id="ignore_release_notes_file"), + pytest.param( + ".pre-commit-config.yaml", 2, [], id="ignore_unrecognized_yaml_file" + ), + # tasks-containing lintables + pytest.param( + "examples/playbooks/become.yml", + 4, + [], + id="1_task_playbook-line_before_tasks", + ), + pytest.param( + "examples/playbooks/become.yml", + 5, + [0, "tasks", 0], + id="1_task_playbook-first_line_in_task_1", + ), + pytest.param( + "examples/playbooks/become.yml", + 10, + [0, "tasks", 0], + id="1_task_playbook-middle_line_in_task_1", + ), + pytest.param( + "examples/playbooks/become.yml", + 15, + [0, "tasks", 0], + id="1_task_playbook-last_line_in_task_1", + ), + pytest.param( + "examples/playbooks/become.yml", + 100, + [0, "tasks", 0], + id="1_task_playbook-line_after_eof_without_anything_after_task", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 1, + [], + id="4_play_playbook-play_1_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 7, + [0, "tasks", 0], + id="4_play_playbook-play_1_first_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 9, + [0, "tasks", 0], + id="4_play_playbook-play_1_last_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 10, + [], + id="4_play_playbook-play_2_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 12, + [], + id="4_play_playbook-play_2_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 13, + [1, "tasks", 0], + id="4_play_playbook-play_2_first_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 18, + [1, "tasks", 0], + id="4_play_playbook-play_2_middle_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 18, + [1, "tasks", 0], + id="4_play_playbook-play_2_last_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 19, + [], + id="4_play_playbook-play_3_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 22, + [], + id="4_play_playbook-play_3_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 23, + [2, "tasks", 0], + id="4_play_playbook-play_3_first_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 25, + [2, "tasks", 0], + id="4_play_playbook-play_3_middle_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 27, + [2, "tasks", 0], + id="4_play_playbook-play_3_last_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 28, + [], + id="4_play_playbook-play_4_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 31, + [], + id="4_play_playbook-play_4_line_before_tasks", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 32, + [3, "tasks", 0], + id="4_play_playbook-play_4_first_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 33, + [3, "tasks", 0], + id="4_play_playbook-play_4_middle_line_task_1", + ), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 35, + [3, "tasks", 0], + id="4_play_playbook-play_4_last_line_task_1", + ), + # playbook with multiple tasks + tasks blocks in a play + pytest.param( + # must have at least one key after one of the tasks blocks + "examples/playbooks/include.yml", + 6, + [0, "pre_tasks", 0], + id="playbook-multi_tasks_blocks-pre_tasks_last_task_before_roles", + ), + pytest.param( + "examples/playbooks/include.yml", + 7, + [], + id="playbook-multi_tasks_blocks-roles_after_pre_tasks", + ), + pytest.param( + "examples/playbooks/include.yml", + 10, + [], + id="playbook-multi_tasks_blocks-roles_before_tasks", + ), + pytest.param( + "examples/playbooks/include.yml", + 12, + [0, "tasks", 0], + id="playbook-multi_tasks_blocks-tasks_first_task", + ), + pytest.param( + "examples/playbooks/include.yml", + 14, + [0, "tasks", 1], + id="playbook-multi_tasks_blocks-tasks_last_task_before_handlers", + ), + pytest.param( + "examples/playbooks/include.yml", + 16, + [0, "handlers", 0], + id="playbook-multi_tasks_blocks-handlers_task", + ), + # playbook with subtasks blocks + pytest.param( + "examples/playbooks/blockincludes.yml", + 14, + [0, "tasks", 0, "block", 1, "block", 0], + id="playbook-deeply_nested_task", + ), + pytest.param( + "examples/playbooks/block.yml", + 12, + [0, "tasks", 0, "block", 1], + id="playbook-subtasks-block_task_2", + ), + pytest.param( + "examples/playbooks/block.yml", + 22, + [0, "tasks", 0, "rescue", 2], + id="playbook-subtasks-rescue_task_3", + ), + pytest.param( + "examples/playbooks/block.yml", + 25, + [0, "tasks", 0, "always", 0], + id="playbook-subtasks-always_task_3", + ), + # tasks files + pytest.param("examples/playbooks/tasks/x.yml", 2, [0], id="tasks-null_task"), + pytest.param( + "examples/playbooks/tasks/x.yml", 6, [1], id="tasks-null_task_next" + ), + pytest.param( + "examples/playbooks/tasks/empty_blocks.yml", + 7, + [0], # this IS part of the first task and "rescue" does not have subtasks. + id="tasks-null_rescue", + ), + pytest.param( + "examples/playbooks/tasks/empty_blocks.yml", + 8, + [0], # this IS part of the first task and "always" does not have subtasks. + id="tasks-empty_always", + ), + pytest.param( + "examples/playbooks/tasks/empty_blocks.yml", + 16, + [1, "always", 0], + id="tasks-task_beyond_empty_blocks", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 1, + [], + id="tasks-line_before_tasks", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 2, + [0], + id="tasks-first_line_in_task_1", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 3, + [0], + id="tasks-middle_line_in_task_1", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 4, + [0], + id="tasks-last_line_in_task_1", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 5, + [1], + id="tasks-first_line_in_task_2", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 6, + [1], + id="tasks-middle_line_in_task_2", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 7, + [1], + id="tasks-last_line_in_task_2", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 8, + [2], + id="tasks-first_line_in_task_3", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 9, + [2], + id="tasks-last_line_in_task_3", + ), + pytest.param( + "examples/roles/more_complex/tasks/main.yml", + 100, + [2], + id="tasks-line_after_eof", + ), + # handlers + pytest.param( + "examples/roles/more_complex/handlers/main.yml", + 1, + [], + id="handlers-line_before_tasks", + ), + pytest.param( + "examples/roles/more_complex/handlers/main.yml", + 2, + [0], + id="handlers-first_line_in_task_1", + ), + pytest.param( + "examples/roles/more_complex/handlers/main.yml", + 3, + [0], + id="handlers-last_line_in_task_1", + ), + pytest.param( + "examples/roles/more_complex/handlers/main.yml", + 100, + [0], + id="handlers-line_after_eof", + ), + ), +) +def test_get_path_to_task( + lintable: Lintable, + line_number: int, + ruamel_data: CommentedMap | CommentedSeq, + expected_path: list[int | str], +) -> None: + """Ensure ``get_task_to_play`` returns the expected path given a file + line.""" + path_to_task = ansiblelint.yaml_utils.get_path_to_task( + lintable, line_number, ruamel_data + ) + assert path_to_task == expected_path + + +@pytest.mark.parametrize( + ("file_path", "line_number"), + ( + pytest.param("examples/playbooks/become.yml", 0, id="1_play_playbook"), + pytest.param( + "examples/playbooks/rule-partial-become-without-become-pass.yml", + 0, + id="4_play_playbook", + ), + pytest.param("examples/playbooks/playbook-parent.yml", 0, id="import_playbook"), + pytest.param("examples/playbooks/become.yml", 0, id="1_task_playbook"), + ), +) +def test_get_path_to_play_raises_value_error_for_bad_line_number( + lintable: Lintable, + line_number: int, + ruamel_data: CommentedMap | CommentedSeq, +) -> None: + """Ensure ``get_path_to_play`` raises ValueError for line_number < 1.""" + with pytest.raises( + ValueError, match=f"expected line_number >= 1, got {line_number}" + ): + ansiblelint.yaml_utils.get_path_to_play(lintable, line_number, ruamel_data) + + +@pytest.mark.parametrize( + ("file_path", "line_number"), + (pytest.param("examples/roles/more_complex/tasks/main.yml", 0, id="tasks"),), +) +def test_get_path_to_task_raises_value_error_for_bad_line_number( + lintable: Lintable, + line_number: int, + ruamel_data: CommentedMap | CommentedSeq, +) -> None: + """Ensure ``get_task_to_play`` raises ValueError for line_number < 1.""" + with pytest.raises( + ValueError, match=f"expected line_number >= 1, got {line_number}" + ): + ansiblelint.yaml_utils.get_path_to_task(lintable, line_number, ruamel_data) + + +@pytest.mark.parametrize( + ("before", "after"), + ( + pytest.param(None, None, id="1"), + pytest.param(1, 1, id="2"), + pytest.param({}, {}, id="3"), + pytest.param({"__file__": 1}, {}, id="simple"), + pytest.param({"foo": {"__file__": 1}}, {"foo": {}}, id="nested"), + pytest.param([{"foo": {"__file__": 1}}], [{"foo": {}}], id="nested-in-lint"), + pytest.param({"foo": [{"__file__": 1}]}, {"foo": [{}]}, id="nested-in-lint"), + ), +) +def test_deannotate( + before: Any, + after: Any, +) -> None: + """Ensure deannotate works as intended.""" + assert ansiblelint.yaml_utils.deannotate(before) == after |