mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-22 21:00:44 +00:00
Merge pull request #32 from xoxys/refactor-formatting
add yapf as formatter
This commit is contained in:
commit
209d5b3d2e
@ -59,9 +59,9 @@ local PipelineTest = {
|
|||||||
CODECOV_TOKEN: { from_secret: 'codecov_token' },
|
CODECOV_TOKEN: { from_secret: 'codecov_token' },
|
||||||
},
|
},
|
||||||
commands: [
|
commands: [
|
||||||
'pip install codecov',
|
'pip install codecov -qq',
|
||||||
'coverage combine .tox/py*/.coverage',
|
'coverage combine .tox/py*/.coverage',
|
||||||
'codecov --required',
|
'codecov --required -X gcov',
|
||||||
],
|
],
|
||||||
depends_on: [
|
depends_on: [
|
||||||
'python35-ansible',
|
'python35-ansible',
|
||||||
|
@ -74,9 +74,9 @@ steps:
|
|||||||
- name: codecov
|
- name: codecov
|
||||||
image: python:3.7
|
image: python:3.7
|
||||||
commands:
|
commands:
|
||||||
- pip install codecov
|
- pip install codecov -qq
|
||||||
- coverage combine .tox/py*/.coverage
|
- coverage combine .tox/py*/.coverage
|
||||||
- codecov --required
|
- codecov --required -X gcov
|
||||||
environment:
|
environment:
|
||||||
CODECOV_TOKEN:
|
CODECOV_TOKEN:
|
||||||
from_secret: codecov_token
|
from_secret: codecov_token
|
||||||
@ -470,6 +470,6 @@ depends_on:
|
|||||||
|
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: 4b5f6077a3352113853e936f62396caf68d9a7a2014245f6b24cca6ae0fa8b3b
|
hmac: 03c91a03dab38f62ce4e792238c87e34ac583fa570ff806bf4208df7ed579cd8
|
||||||
|
|
||||||
...
|
...
|
||||||
|
18
.flake8
18
.flake8
@ -1,8 +1,18 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
# Temp disable Docstring checks D101, D102, D103, D107
|
ignore = D102, D103, D107, D202, W503
|
||||||
ignore = E501, W503, F401, N813, D101, D102, D103, D107
|
max-line-length = 99
|
||||||
max-line-length = 110
|
|
||||||
inline-quotes = double
|
inline-quotes = double
|
||||||
exclude = .git,.tox,__pycache__,build,dist,tests,*.pyc,*.egg-info,.cache,.eggs
|
exclude =
|
||||||
|
.git
|
||||||
|
.tox
|
||||||
|
__pycache__
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
tests
|
||||||
|
*.pyc
|
||||||
|
*.egg-info
|
||||||
|
.cache
|
||||||
|
.eggs
|
||||||
|
env*
|
||||||
application-import-names = ansiblelater
|
application-import-names = ansiblelater
|
||||||
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
* BUGFIX
|
* ENHANCEMENT
|
||||||
* replace removed dependency `ansible.module_utils.parsing.convert_bool`
|
* improve ANSIBLE0001 logic to avoid false positive results
|
||||||
* decode task actions to prevent errors on multiline strings
|
* improve log output readability
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
__author__ = "Robert Kaussow"
|
__author__ = "Robert Kaussow"
|
||||||
__project__ = "ansible-later"
|
__project__ = "ansible-later"
|
||||||
__version__ = "0.3.1"
|
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
__maintainer__ = "Robert Kaussow"
|
__maintainer__ = "Robert Kaussow"
|
||||||
__email__ = "mail@geeklabor.de"
|
__email__ = "mail@geeklabor.de"
|
||||||
__status__ = "Production"
|
__url__ = "https://github.com/xoxys/ansible-later"
|
||||||
|
__version__ = "0.3.1"
|
||||||
|
|
||||||
from ansiblelater import logger
|
from ansiblelater import logger
|
||||||
|
|
||||||
|
@ -15,19 +15,34 @@ from ansiblelater.command import candidates
|
|||||||
def main():
|
def main():
|
||||||
"""Run main program."""
|
"""Run main program."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Validate ansible files against best pratice guideline")
|
description="Validate ansible files against best pratice guideline"
|
||||||
parser.add_argument("-c", "--config", dest="config_file",
|
)
|
||||||
help="location of configuration file")
|
parser.add_argument(
|
||||||
parser.add_argument("-r", "--rules", dest="rules.standards",
|
"-c", "--config", dest="config_file", help="location of configuration file"
|
||||||
help="location of standards rules")
|
)
|
||||||
parser.add_argument("-s", "--standards", dest="rules.filter", action="append",
|
parser.add_argument(
|
||||||
help="limit standards to given ID's")
|
"-r", "--rules", dest="rules.standards", help="location of standards rules"
|
||||||
parser.add_argument("-x", "--exclude-standards", dest="rules.exclude_filter", action="append",
|
)
|
||||||
help="exclude standards by given ID's")
|
parser.add_argument(
|
||||||
parser.add_argument("-v", dest="logging.level", action="append_const", const=-1,
|
"-s",
|
||||||
help="increase log level")
|
"--standards",
|
||||||
parser.add_argument("-q", dest="logging.level", action="append_const",
|
dest="rules.filter",
|
||||||
const=1, help="decrease log level")
|
action="append",
|
||||||
|
help="limit standards to given ID's"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-x",
|
||||||
|
"--exclude-standards",
|
||||||
|
dest="rules.exclude_filter",
|
||||||
|
action="append",
|
||||||
|
help="exclude standards by given ID's"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-q", dest="logging.level", action="append_const", const=1, help="decrease log level"
|
||||||
|
)
|
||||||
parser.add_argument("rules.files", nargs="*")
|
parser.add_argument("rules.files", nargs="*")
|
||||||
parser.add_argument("--version", action="version", version="%(prog)s {}".format(__version__))
|
parser.add_argument("--version", action="version", version="%(prog)s {}".format(__version__))
|
||||||
|
|
||||||
|
@ -20,9 +20,7 @@ def get_settings(args):
|
|||||||
:returns: Settings object
|
:returns: Settings object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = settings.Settings(
|
config = settings.Settings(args=args)
|
||||||
args=args,
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@ -33,13 +31,15 @@ def get_standards(filepath):
|
|||||||
standards = importlib.import_module("standards")
|
standards = importlib.import_module("standards")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
utils.sysexit_with_message(
|
utils.sysexit_with_message(
|
||||||
"Could not import standards from directory %s: %s" % (filepath, str(e)))
|
"Could not import standards from directory %s: %s" % (filepath, str(e))
|
||||||
|
)
|
||||||
|
|
||||||
if getattr(standards, "ansible_min_version", None) and \
|
if getattr(standards, "ansible_min_version", None) and \
|
||||||
LooseVersion(standards.ansible_min_version) > LooseVersion(ansible.__version__):
|
LooseVersion(standards.ansible_min_version) > LooseVersion(ansible.__version__):
|
||||||
utils.sysexit_with_message("Standards require ansible version %s (current version %s). "
|
utils.sysexit_with_message(
|
||||||
"Please upgrade ansible." %
|
"Standards require ansible version %s (current version %s). "
|
||||||
(standards.ansible_min_version, ansible.__version__))
|
"Please upgrade ansible." % (standards.ansible_min_version, ansible.__version__)
|
||||||
|
)
|
||||||
|
|
||||||
if getattr(standards, "ansible_later_min_version", None) and \
|
if getattr(standards, "ansible_later_min_version", None) and \
|
||||||
LooseVersion(standards.ansible_later_min_version) > LooseVersion(
|
LooseVersion(standards.ansible_later_min_version) > LooseVersion(
|
||||||
@ -47,13 +47,15 @@ def get_standards(filepath):
|
|||||||
utils.sysexit_with_message(
|
utils.sysexit_with_message(
|
||||||
"Standards require ansible-later version %s (current version %s). "
|
"Standards require ansible-later version %s (current version %s). "
|
||||||
"Please upgrade ansible-later." %
|
"Please upgrade ansible-later." %
|
||||||
(standards.ansible_later_min_version, utils.get_property("__version__")))
|
(standards.ansible_later_min_version, utils.get_property("__version__"))
|
||||||
|
)
|
||||||
|
|
||||||
normalized_std = (list(toolz.remove(lambda x: x.id == "", standards.standards)))
|
normalized_std = (list(toolz.remove(lambda x: x.id == "", standards.standards)))
|
||||||
unique_std = len(list(toolz.unique(normalized_std, key=lambda x: x.id)))
|
unique_std = len(list(toolz.unique(normalized_std, key=lambda x: x.id)))
|
||||||
all_std = len(normalized_std)
|
all_std = len(normalized_std)
|
||||||
if not all_std == unique_std:
|
if not all_std == unique_std:
|
||||||
utils.sysexit_with_message(
|
utils.sysexit_with_message(
|
||||||
"Detect duplicate ID's in standards definition. Please use unique ID's only.")
|
"Detect duplicate ID's in standards definition. Please use unique ID's only."
|
||||||
|
)
|
||||||
|
|
||||||
return standards.standards
|
return standards.standards
|
||||||
|
@ -77,15 +77,18 @@ class Candidate(object):
|
|||||||
"%s %s is in a role that contains a meta/main.yml without a declared "
|
"%s %s is in a role that contains a meta/main.yml without a declared "
|
||||||
"standards version. "
|
"standards version. "
|
||||||
"Using latest standards version %s" %
|
"Using latest standards version %s" %
|
||||||
(type(self).__name__, self.path, version))
|
(type(self).__name__, self.path, version)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"%s %s does not present standards version. "
|
"%s %s does not present standards version. "
|
||||||
"Using latest standards version %s" %
|
"Using latest standards version %s" %
|
||||||
(type(self).__name__, self.path, version))
|
(type(self).__name__, self.path, version)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
LOG.info("%s %s declares standards version %s" %
|
LOG.info(
|
||||||
(type(self).__name__, self.path, version))
|
"%s %s declares standards version %s" % (type(self).__name__, self.path, version)
|
||||||
|
)
|
||||||
|
|
||||||
return version
|
return version
|
||||||
|
|
||||||
@ -113,39 +116,61 @@ class Candidate(object):
|
|||||||
result = standard.check(self, settings.config)
|
result = standard.check(self, settings.config)
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
utils.sysexit_with_message("Standard '{}' returns an empty result object.".format(
|
utils.sysexit_with_message(
|
||||||
standard.check.__name__))
|
"Standard '{}' returns an empty result object.".format(
|
||||||
|
standard.check.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
labels = {"tag": "review", "standard": standard.name, "file": self.path, "passed": True}
|
labels = {
|
||||||
|
"tag": "review",
|
||||||
|
"standard": standard.name,
|
||||||
|
"file": self.path,
|
||||||
|
"passed": True
|
||||||
|
}
|
||||||
|
|
||||||
if standard.id and standard.id.strip():
|
if standard.id and standard.id.strip():
|
||||||
labels["id"] = standard.id
|
labels["id"] = standard.id
|
||||||
|
|
||||||
for err in [err for err in result.errors
|
for err in [
|
||||||
if not err.lineno or utils.is_line_in_ranges(err.lineno, utils.lines_ranges(lines))]: # noqa
|
err for err in result.errors if not err.lineno
|
||||||
|
or utils.is_line_in_ranges(err.lineno, utils.lines_ranges(lines))
|
||||||
|
]: # noqa
|
||||||
err_labels = copy.copy(labels)
|
err_labels = copy.copy(labels)
|
||||||
err_labels["passed"] = False
|
err_labels["passed"] = False
|
||||||
if isinstance(err, Error):
|
if isinstance(err, Error):
|
||||||
err_labels.update(err.to_dict())
|
err_labels.update(err.to_dict())
|
||||||
|
|
||||||
if not standard.version:
|
if not standard.version:
|
||||||
LOG.warning("{id}Best practice '{name}' not met:\n{path}:{error}".format(
|
LOG.warning(
|
||||||
|
"{id}Best practice '{name}' not met:\n{path}:{error}".format(
|
||||||
id=self._format_id(standard.id),
|
id=self._format_id(standard.id),
|
||||||
name=standard.name,
|
name=standard.name,
|
||||||
path=self.path,
|
path=self.path,
|
||||||
error=err), extra=flag_extra(err_labels))
|
error=err
|
||||||
|
),
|
||||||
|
extra=flag_extra(err_labels)
|
||||||
|
)
|
||||||
elif LooseVersion(standard.version) > LooseVersion(self.version):
|
elif LooseVersion(standard.version) > LooseVersion(self.version):
|
||||||
LOG.warning("{id}Future standard '{name}' not met:\n{path}:{error}".format(
|
LOG.warning(
|
||||||
|
"{id}Future standard '{name}' not met:\n{path}:{error}".format(
|
||||||
id=self._format_id(standard.id),
|
id=self._format_id(standard.id),
|
||||||
name=standard.name,
|
name=standard.name,
|
||||||
path=self.path,
|
path=self.path,
|
||||||
error=err), extra=flag_extra(err_labels))
|
error=err
|
||||||
|
),
|
||||||
|
extra=flag_extra(err_labels)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
LOG.error("{id}Standard '{name}' not met:\n{path}:{error}".format(
|
LOG.error(
|
||||||
|
"{id}Standard '{name}' not met:\n{path}:{error}".format(
|
||||||
id=self._format_id(standard.id),
|
id=self._format_id(standard.id),
|
||||||
name=standard.name,
|
name=standard.name,
|
||||||
path=self.path,
|
path=self.path,
|
||||||
error=err), extra=flag_extra(err_labels))
|
error=err
|
||||||
|
),
|
||||||
|
extra=flag_extra(err_labels)
|
||||||
|
)
|
||||||
errors = errors + 1
|
errors = errors + 1
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
@ -164,6 +189,8 @@ class Candidate(object):
|
|||||||
|
|
||||||
|
|
||||||
class RoleFile(Candidate):
|
class RoleFile(Candidate):
|
||||||
|
"""Object classified as Ansible role file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]):
|
def __init__(self, filename, settings={}, standards=[]):
|
||||||
super(RoleFile, self).__init__(filename, settings, standards)
|
super(RoleFile, self).__init__(filename, settings, standards)
|
||||||
|
|
||||||
@ -177,76 +204,110 @@ class RoleFile(Candidate):
|
|||||||
|
|
||||||
|
|
||||||
class Playbook(Candidate):
|
class Playbook(Candidate):
|
||||||
|
"""Object classified as Ansible playbook."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Task(RoleFile):
|
class Task(RoleFile):
|
||||||
|
"""Object classified as Ansible task file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]):
|
def __init__(self, filename, settings={}, standards=[]):
|
||||||
super(Task, self).__init__(filename, settings, standards)
|
super(Task, self).__init__(filename, settings, standards)
|
||||||
self.filetype = "tasks"
|
self.filetype = "tasks"
|
||||||
|
|
||||||
|
|
||||||
class Handler(RoleFile):
|
class Handler(RoleFile):
|
||||||
|
"""Object classified as Ansible handler file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]):
|
def __init__(self, filename, settings={}, standards=[]):
|
||||||
super(Handler, self).__init__(filename, settings, standards)
|
super(Handler, self).__init__(filename, settings, standards)
|
||||||
self.filetype = "handlers"
|
self.filetype = "handlers"
|
||||||
|
|
||||||
|
|
||||||
class Vars(Candidate):
|
class Vars(Candidate):
|
||||||
|
"""Object classified as Ansible vars file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Unversioned(Candidate):
|
class Unversioned(Candidate):
|
||||||
|
"""Object classified as unversioned file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]):
|
def __init__(self, filename, settings={}, standards=[]):
|
||||||
super(Unversioned, self).__init__(filename, settings, standards)
|
super(Unversioned, self).__init__(filename, settings, standards)
|
||||||
self.expected_version = False
|
self.expected_version = False
|
||||||
|
|
||||||
|
|
||||||
class InventoryVars(Unversioned):
|
class InventoryVars(Unversioned):
|
||||||
|
"""Object classified as Ansible inventory vars."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HostVars(InventoryVars):
|
class HostVars(InventoryVars):
|
||||||
|
"""Object classified as Ansible host vars."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GroupVars(InventoryVars):
|
class GroupVars(InventoryVars):
|
||||||
|
"""Object classified as Ansible group vars."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RoleVars(RoleFile):
|
class RoleVars(RoleFile):
|
||||||
|
"""Object classified as Ansible role vars."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Meta(RoleFile):
|
class Meta(RoleFile):
|
||||||
|
"""Object classified as Ansible meta file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Inventory(Unversioned):
|
class Inventory(Unversioned):
|
||||||
|
"""Object classified as Ansible inventory file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Code(Unversioned):
|
class Code(Unversioned):
|
||||||
|
"""Object classified as code file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Template(RoleFile):
|
class Template(RoleFile):
|
||||||
|
"""Object classified as Ansible template file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Doc(Unversioned):
|
class Doc(Unversioned):
|
||||||
|
"""Object classified as documentation file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Makefile(Unversioned):
|
class Makefile(Unversioned):
|
||||||
|
"""Object classified as makefile."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class File(RoleFile):
|
class File(RoleFile):
|
||||||
|
"""Object classified as generic file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Rolesfile(Unversioned):
|
class Rolesfile(Unversioned):
|
||||||
|
"""Object classified as Ansible roles file."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -281,13 +342,14 @@ class Error(object):
|
|||||||
|
|
||||||
|
|
||||||
class Result(object):
|
class Result(object):
|
||||||
|
"""Generic result object."""
|
||||||
|
|
||||||
def __init__(self, candidate, errors=None):
|
def __init__(self, candidate, errors=None):
|
||||||
self.candidate = candidate
|
self.candidate = candidate
|
||||||
self.errors = errors or []
|
self.errors = errors or []
|
||||||
|
|
||||||
def message(self):
|
def message(self):
|
||||||
return "\n".join(["{0}:{1}".format(self.candidate, error)
|
return "\n".join(["{0}:{1}".format(self.candidate, error) for error in self.errors])
|
||||||
for error in self.errors])
|
|
||||||
|
|
||||||
|
|
||||||
def classify(filename, settings={}, standards=[]):
|
def classify(filename, settings={}, standards=[]):
|
||||||
@ -306,8 +368,10 @@ def classify(filename, settings={}, standards=[]):
|
|||||||
return HostVars(filename, settings, standards)
|
return HostVars(filename, settings, standards)
|
||||||
if parentdir in ["meta"]:
|
if parentdir in ["meta"]:
|
||||||
return Meta(filename, settings, standards)
|
return Meta(filename, settings, standards)
|
||||||
if parentdir in ["library", "lookup_plugins", "callback_plugins",
|
if (
|
||||||
"filter_plugins"] or filename.endswith(".py"):
|
parentdir in ["library", "lookup_plugins", "callback_plugins", "filter_plugins"]
|
||||||
|
or filename.endswith(".py")
|
||||||
|
):
|
||||||
return Code(filename, settings, standards)
|
return Code(filename, settings, standards)
|
||||||
if "inventory" == basename or "hosts" == basename or parentdir in ["inventories"]:
|
if "inventory" == basename or "hosts" == basename or parentdir in ["inventories"]:
|
||||||
return Inventory(filename, settings, standards)
|
return Inventory(filename, settings, standards)
|
||||||
|
@ -27,217 +27,257 @@ from ansiblelater.rules.yamlfiles import check_yaml_hyphens
|
|||||||
from ansiblelater.rules.yamlfiles import check_yaml_indent
|
from ansiblelater.rules.yamlfiles import check_yaml_indent
|
||||||
from ansiblelater.standard import Standard
|
from ansiblelater.standard import Standard
|
||||||
|
|
||||||
tasks_should_be_separated = Standard(dict(
|
tasks_should_be_separated = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0001",
|
id="ANSIBLE0001",
|
||||||
name="Single tasks should be separated by empty line",
|
name="Single tasks should be separated by empty line",
|
||||||
check=check_line_between_tasks,
|
check=check_line_between_tasks,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
role_must_contain_meta_main = Standard(dict(
|
role_must_contain_meta_main = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0002",
|
id="ANSIBLE0002",
|
||||||
name="Roles must contain suitable meta/main.yml",
|
name="Roles must contain suitable meta/main.yml",
|
||||||
check=check_meta_main,
|
check=check_meta_main,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["meta"]
|
types=["meta"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_are_uniquely_named = Standard(dict(
|
tasks_are_uniquely_named = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0003",
|
id="ANSIBLE0003",
|
||||||
name="Tasks and handlers must be uniquely named within a single file",
|
name="Tasks and handlers must be uniquely named within a single file",
|
||||||
check=check_unique_named_task,
|
check=check_unique_named_task,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_spaces_between_variable_braces = Standard(dict(
|
use_spaces_between_variable_braces = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0004",
|
id="ANSIBLE0004",
|
||||||
name="YAML should use consistent number of spaces around variables",
|
name="YAML should use consistent number of spaces around variables",
|
||||||
check=check_braces_spaces,
|
check=check_braces_spaces,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
roles_scm_not_in_src = Standard(dict(
|
roles_scm_not_in_src = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0005",
|
id="ANSIBLE0005",
|
||||||
name="Use scm key rather than src: scm+url",
|
name="Use scm key rather than src: scm+url",
|
||||||
check=check_scm_in_src,
|
check=check_scm_in_src,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["rolesfile"]
|
types=["rolesfile"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_are_named = Standard(dict(
|
tasks_are_named = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0006",
|
id="ANSIBLE0006",
|
||||||
name="Tasks and handlers must be named",
|
name="Tasks and handlers must be named",
|
||||||
check=check_named_task,
|
check=check_named_task,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_names_are_formatted = Standard(dict(
|
tasks_names_are_formatted = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0007",
|
id="ANSIBLE0007",
|
||||||
name="Name of tasks and handlers must be formatted",
|
name="Name of tasks and handlers must be formatted",
|
||||||
check=check_name_format,
|
check=check_name_format,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
commands_should_not_be_used_in_place_of_modules = Standard(dict(
|
commands_should_not_be_used_in_place_of_modules = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0008",
|
id="ANSIBLE0008",
|
||||||
name="Commands should not be used in place of modules",
|
name="Commands should not be used in place of modules",
|
||||||
check=check_command_instead_of_module,
|
check=check_command_instead_of_module,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
package_installs_should_not_use_latest = Standard(dict(
|
package_installs_should_not_use_latest = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0009",
|
id="ANSIBLE0009",
|
||||||
name="Package installs should use present, not latest",
|
name="Package installs should use present, not latest",
|
||||||
check=check_install_use_latest,
|
check=check_install_use_latest,
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_shell_only_when_necessary = Standard(dict(
|
use_shell_only_when_necessary = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0010",
|
id="ANSIBLE0010",
|
||||||
name="Shell should only be used when essential",
|
name="Shell should only be used when essential",
|
||||||
check=check_shell_instead_command,
|
check=check_shell_instead_command,
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
commands_should_be_idempotent = Standard(dict(
|
commands_should_be_idempotent = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0011",
|
id="ANSIBLE0011",
|
||||||
name="Commands should be idempotent",
|
name="Commands should be idempotent",
|
||||||
check=check_command_has_changes,
|
check=check_command_has_changes,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task"]
|
types=["playbook", "task"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
dont_compare_to_empty_string = Standard(dict(
|
dont_compare_to_empty_string = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0012",
|
id="ANSIBLE0012",
|
||||||
name="Don't compare to \"\" - use `when: var` or `when: not var`",
|
name="Don't compare to \"\" - use `when: var` or `when: not var`",
|
||||||
check=check_empty_string_compare,
|
check=check_empty_string_compare,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "template"]
|
types=["playbook", "task", "handler", "template"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
dont_compare_to_literal_bool = Standard(dict(
|
dont_compare_to_literal_bool = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0013",
|
id="ANSIBLE0013",
|
||||||
name="Don't compare to True or False - use `when: var` or `when: not var`",
|
name="Don't compare to True or False - use `when: var` or `when: not var`",
|
||||||
check=check_compare_to_literal_bool,
|
check=check_compare_to_literal_bool,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "template"]
|
types=["playbook", "task", "handler", "template"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
literal_bool_should_be_formatted = Standard(dict(
|
literal_bool_should_be_formatted = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0014",
|
id="ANSIBLE0014",
|
||||||
name="Literal bools should start with a capital letter",
|
name="Literal bools should start with a capital letter",
|
||||||
check=check_literal_bool_format,
|
check=check_literal_bool_format,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
"hostvars", "groupvars"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
use_become_with_become_user = Standard(dict(
|
use_become_with_become_user = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0015",
|
id="ANSIBLE0015",
|
||||||
name="become should be combined with become_user",
|
name="become should be combined with become_user",
|
||||||
check=check_become_user,
|
check=check_become_user,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_spaces_around_filters = Standard(dict(
|
use_spaces_around_filters = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0016",
|
id="ANSIBLE0016",
|
||||||
name="jinja2 filters should be separated with spaces",
|
name="jinja2 filters should be separated with spaces",
|
||||||
check=check_filter_separation,
|
check=check_filter_separation,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
"hostvars", "groupvars"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_not_contain_unnecessarily_empty_lines = Standard(dict(
|
files_should_not_contain_unnecessarily_empty_lines = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0001",
|
id="LINT0001",
|
||||||
name="YAML should not contain unnecessarily empty lines",
|
name="YAML should not contain unnecessarily empty lines",
|
||||||
check=check_yaml_empty_lines,
|
check=check_yaml_empty_lines,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_be_indented = Standard(dict(
|
files_should_be_indented = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0002",
|
id="LINT0002",
|
||||||
name="YAML should be correctly indented",
|
name="YAML should be correctly indented",
|
||||||
check=check_yaml_indent,
|
check=check_yaml_indent,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_use_consistent_spaces_after_hyphens = Standard(dict(
|
files_should_use_consistent_spaces_after_hyphens = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0003",
|
id="LINT0003",
|
||||||
name="YAML should use consistent number of spaces after hyphens",
|
name="YAML should use consistent number of spaces after hyphens",
|
||||||
check=check_yaml_hyphens,
|
check=check_yaml_hyphens,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_contain_document_start_marker = Standard(dict(
|
files_should_contain_document_start_marker = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0004",
|
id="LINT0004",
|
||||||
name="YAML should contain document start marker",
|
name="YAML should contain document start marker",
|
||||||
check=check_yaml_document_start,
|
check=check_yaml_document_start,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
spaces_around_colons = Standard(dict(
|
spaces_around_colons = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0005",
|
id="LINT0005",
|
||||||
name="YAML should use consistent number of spaces around colons",
|
name="YAML should use consistent number of spaces around colons",
|
||||||
check=check_yaml_colons,
|
check=check_yaml_colons,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
rolesfile_should_be_in_yaml = Standard(dict(
|
rolesfile_should_be_in_yaml = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0006",
|
id="LINT0006",
|
||||||
name="Roles file should be in yaml format",
|
name="Roles file should be in yaml format",
|
||||||
check=check_yaml_file,
|
check=check_yaml_file,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["rolesfile"]
|
types=["rolesfile"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
files_should_not_be_purposeless = Standard(dict(
|
files_should_not_be_purposeless = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0007",
|
id="LINT0007",
|
||||||
name="Files should contain useful content",
|
name="Files should contain useful content",
|
||||||
check=check_yaml_has_content,
|
check=check_yaml_has_content,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
types=["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_yaml_rather_than_key_value = Standard(dict(
|
use_yaml_rather_than_key_value = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0008",
|
id="LINT0008",
|
||||||
name="Use YAML format for tasks and handlers rather than key=value",
|
name="Use YAML format for tasks and handlers rather than key=value",
|
||||||
check=check_native_yaml,
|
check=check_native_yaml,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
files_should_contain_document_end_marker = Standard(dict(
|
files_should_contain_document_end_marker = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0009",
|
id="LINT0009",
|
||||||
name="YAML should contain document end marker",
|
name="YAML should contain document end marker",
|
||||||
check=check_yaml_document_end,
|
check=check_yaml_document_end,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
ansible_min_version = "2.5"
|
ansible_min_version = "2.5"
|
||||||
ansible_later_min_version = "0.3.0"
|
ansible_later_min_version = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
standards = [
|
standards = [
|
||||||
# Ansible
|
# Ansible
|
||||||
tasks_should_be_separated,
|
tasks_should_be_separated,
|
||||||
|
@ -9,7 +9,7 @@ import colorama
|
|||||||
from pythonjsonlogger import jsonlogger
|
from pythonjsonlogger import jsonlogger
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
CONSOLE_FORMAT = "%(levelname)s: %(message)s"
|
CONSOLE_FORMAT = "%(levelname)s: {}%(message)s"
|
||||||
JSON_FORMAT = "(asctime) (levelname) (message)"
|
JSON_FORMAT = "(asctime) (levelname) (message)"
|
||||||
|
|
||||||
|
|
||||||
@ -63,6 +63,7 @@ class MultilineFormatter(logging.Formatter):
|
|||||||
|
|
||||||
def format(self, record): # noqa
|
def format(self, record): # noqa
|
||||||
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
|
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
|
||||||
|
record.msg = record.msg + "\n"
|
||||||
return logging.Formatter.format(self, record)
|
return logging.Formatter.format(self, record)
|
||||||
|
|
||||||
|
|
||||||
@ -157,22 +158,22 @@ def _get_critical_handler(json=False):
|
|||||||
|
|
||||||
def critical(message):
|
def critical(message):
|
||||||
"""Format critical messages and return string."""
|
"""Format critical messages and return string."""
|
||||||
return color_text(colorama.Fore.RED, "{}".format(message))
|
return color_text(colorama.Fore.RED, message)
|
||||||
|
|
||||||
|
|
||||||
def error(message):
|
def error(message):
|
||||||
"""Format error messages and return string."""
|
"""Format error messages and return string."""
|
||||||
return color_text(colorama.Fore.RED, "{}".format(message))
|
return color_text(colorama.Fore.RED, message)
|
||||||
|
|
||||||
|
|
||||||
def warn(message):
|
def warn(message):
|
||||||
"""Format warn messages and return string."""
|
"""Format warn messages and return string."""
|
||||||
return color_text(colorama.Fore.YELLOW, "{}".format(message))
|
return color_text(colorama.Fore.YELLOW, message)
|
||||||
|
|
||||||
|
|
||||||
def info(message):
|
def info(message):
|
||||||
"""Format info messages and return string."""
|
"""Format info messages and return string."""
|
||||||
return color_text(colorama.Fore.BLUE, "{}".format(message))
|
return color_text(colorama.Fore.BLUE, message)
|
||||||
|
|
||||||
|
|
||||||
def color_text(color, msg):
|
def color_text(color, msg):
|
||||||
@ -184,4 +185,5 @@ def color_text(color, msg):
|
|||||||
:returns: string
|
:returns: string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
msg = msg.format(colorama.Style.BRIGHT)
|
||||||
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)
|
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)
|
||||||
|
@ -16,7 +16,8 @@ def check_braces_spaces(candidate, settings):
|
|||||||
yamllines, errors = get_normalized_yaml(candidate, settings)
|
yamllines, errors = get_normalized_yaml(candidate, settings)
|
||||||
conf = settings["ansible"]["double-braces"]
|
conf = settings["ansible"]["double-braces"]
|
||||||
description = "no suitable numbers of spaces (min: {min} max: {max})".format(
|
description = "no suitable numbers of spaces (min: {min} max: {max})".format(
|
||||||
min=conf["min-spaces-inside"], max=conf["max-spaces-inside"])
|
min=conf["min-spaces-inside"], max=conf["max-spaces-inside"]
|
||||||
|
)
|
||||||
|
|
||||||
matches = []
|
matches = []
|
||||||
braces = re.compile("{{(.*?)}}")
|
braces = re.compile("{{(.*?)}}")
|
||||||
@ -33,8 +34,8 @@ def check_braces_spaces(candidate, settings):
|
|||||||
sum_spaces = leading + trailing
|
sum_spaces = leading + trailing
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(sum_spaces < conf["min-spaces-inside"] * 2)
|
sum_spaces < conf["min-spaces-inside"] * 2
|
||||||
or (sum_spaces > conf["min-spaces-inside"] * 2)
|
or sum_spaces > conf["min-spaces-inside"] * 2
|
||||||
):
|
):
|
||||||
errors.append(Error(i, description))
|
errors.append(Error(i, description))
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
@ -42,9 +43,10 @@ def check_braces_spaces(candidate, settings):
|
|||||||
|
|
||||||
def check_named_task(candidate, settings):
|
def check_named_task(candidate, settings):
|
||||||
tasks, errors = get_normalized_tasks(candidate, settings)
|
tasks, errors = get_normalized_tasks(candidate, settings)
|
||||||
nameless_tasks = ["meta", "debug", "include_role", "import_role",
|
nameless_tasks = [
|
||||||
"include_tasks", "import_tasks", "include_vars",
|
"meta", "debug", "include_role", "import_role", "include_tasks", "import_tasks",
|
||||||
"block"]
|
"include_vars", "block"
|
||||||
|
]
|
||||||
description = "module '%s' used without or empty name attribute"
|
description = "module '%s' used without or empty name attribute"
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
@ -93,11 +95,22 @@ def check_command_instead_of_module(candidate, settings):
|
|||||||
tasks, errors = get_normalized_tasks(candidate, settings)
|
tasks, errors = get_normalized_tasks(candidate, settings)
|
||||||
commands = ["command", "shell", "raw"]
|
commands = ["command", "shell", "raw"]
|
||||||
modules = {
|
modules = {
|
||||||
"git": "git", "hg": "hg", "curl": "get_url or uri", "wget": "get_url or uri",
|
"git": "git",
|
||||||
"svn": "subversion", "service": "service", "mount": "mount",
|
"hg": "hg",
|
||||||
"rpm": "yum or rpm_key", "yum": "yum", "apt-get": "apt-get",
|
"curl": "get_url or uri",
|
||||||
"unzip": "unarchive", "tar": "unarchive", "chkconfig": "service",
|
"wget": "get_url or uri",
|
||||||
"rsync": "synchronize", "supervisorctl": "supervisorctl", "systemctl": "systemd",
|
"svn": "subversion",
|
||||||
|
"service": "service",
|
||||||
|
"mount": "mount",
|
||||||
|
"rpm": "yum or rpm_key",
|
||||||
|
"yum": "yum",
|
||||||
|
"apt-get": "apt-get",
|
||||||
|
"unzip": "unarchive",
|
||||||
|
"tar": "unarchive",
|
||||||
|
"chkconfig": "service",
|
||||||
|
"rsync": "synchronize",
|
||||||
|
"supervisorctl": "supervisorctl",
|
||||||
|
"systemctl": "systemd",
|
||||||
"sed": "template or lineinfile"
|
"sed": "template or lineinfile"
|
||||||
}
|
}
|
||||||
description = "%s command used in place of %s module"
|
description = "%s command used in place of %s module"
|
||||||
@ -111,26 +124,32 @@ def check_command_instead_of_module(candidate, settings):
|
|||||||
first_cmd_arg = task["action"]["__ansible_arguments__"][0]
|
first_cmd_arg = task["action"]["__ansible_arguments__"][0]
|
||||||
|
|
||||||
executable = os.path.basename(first_cmd_arg)
|
executable = os.path.basename(first_cmd_arg)
|
||||||
if (first_cmd_arg and executable in modules
|
if (
|
||||||
and task["action"].get("warn", True) and "register" not in task):
|
first_cmd_arg and executable in modules and task["action"].get("warn", True)
|
||||||
|
and "register" not in task
|
||||||
|
):
|
||||||
errors.append(
|
errors.append(
|
||||||
Error(task["__line__"], description % (executable, modules[executable])))
|
Error(task["__line__"], description % (executable, modules[executable]))
|
||||||
|
)
|
||||||
|
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_install_use_latest(candidate, settings):
|
def check_install_use_latest(candidate, settings):
|
||||||
tasks, errors = get_normalized_tasks(candidate, settings)
|
tasks, errors = get_normalized_tasks(candidate, settings)
|
||||||
package_managers = ["yum", "apt", "dnf", "homebrew", "pacman", "openbsd_package", "pkg5",
|
package_managers = [
|
||||||
"portage", "pkgutil", "slackpkg", "swdepot", "zypper", "bundler", "pip",
|
"yum", "apt", "dnf", "homebrew", "pacman", "openbsd_package", "pkg5", "portage", "pkgutil",
|
||||||
"pear", "npm", "yarn", "gem", "easy_install", "bower", "package", "apk",
|
"slackpkg", "swdepot", "zypper", "bundler", "pip", "pear", "npm", "yarn", "gem",
|
||||||
"openbsd_pkg", "pkgng", "sorcery", "xbps"]
|
"easy_install", "bower", "package", "apk", "openbsd_pkg", "pkgng", "sorcery", "xbps"
|
||||||
|
]
|
||||||
description = "package installs should use state=present with or without a version"
|
description = "package installs should use state=present with or without a version"
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if (task["action"]["__ansible_module__"] in package_managers
|
if (
|
||||||
and task["action"].get("state") == "latest"):
|
task["action"]["__ansible_module__"] in package_managers
|
||||||
|
and task["action"].get("state") == "latest"
|
||||||
|
):
|
||||||
errors.append(Error(task["__line__"], description))
|
errors.append(Error(task["__line__"], description))
|
||||||
|
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
@ -165,10 +184,11 @@ def check_command_has_changes(candidate, settings):
|
|||||||
if not errors:
|
if not errors:
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task["action"]["__ansible_module__"] in commands:
|
if task["action"]["__ansible_module__"] in commands:
|
||||||
if ("changed_when" not in task and "when" not in task
|
if (
|
||||||
|
"changed_when" not in task and "when" not in task
|
||||||
and "when" not in task["__ansible_action_meta__"]
|
and "when" not in task["__ansible_action_meta__"]
|
||||||
and "creates" not in task["action"]
|
and "creates" not in task["action"] and "removes" not in task["action"]
|
||||||
and "removes" not in task["action"]):
|
):
|
||||||
errors.append(Error(task["__line__"], description))
|
errors.append(Error(task["__line__"], description))
|
||||||
|
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
@ -49,43 +49,39 @@ def check_native_yaml(candidate, settings):
|
|||||||
|
|
||||||
|
|
||||||
def check_yaml_empty_lines(candidate, settings):
|
def check_yaml_empty_lines(candidate, settings):
|
||||||
options = "rules: {{empty-lines: {conf}}}".format(
|
options = "rules: {{empty-lines: {conf}}}".format(conf=settings["yamllint"]["empty-lines"])
|
||||||
conf=settings["yamllint"]["empty-lines"])
|
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_yaml_indent(candidate, settings):
|
def check_yaml_indent(candidate, settings):
|
||||||
options = "rules: {{indentation: {conf}}}".format(
|
options = "rules: {{indentation: {conf}}}".format(conf=settings["yamllint"]["indentation"])
|
||||||
conf=settings["yamllint"]["indentation"])
|
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_yaml_hyphens(candidate, settings):
|
def check_yaml_hyphens(candidate, settings):
|
||||||
options = "rules: {{hyphens: {conf}}}".format(
|
options = "rules: {{hyphens: {conf}}}".format(conf=settings["yamllint"]["hyphens"])
|
||||||
conf=settings["yamllint"]["hyphens"])
|
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_yaml_document_start(candidate, settings):
|
def check_yaml_document_start(candidate, settings):
|
||||||
options = "rules: {{document-start: {conf}}}".format(
|
options = "rules: {{document-start: {conf}}}".format(
|
||||||
conf=settings["yamllint"]["document-start"])
|
conf=settings["yamllint"]["document-start"]
|
||||||
|
)
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_yaml_document_end(candidate, settings):
|
def check_yaml_document_end(candidate, settings):
|
||||||
options = "rules: {{document-end: {conf}}}".format(
|
options = "rules: {{document-end: {conf}}}".format(conf=settings["yamllint"]["document-end"])
|
||||||
conf=settings["yamllint"]["document-end"])
|
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
|
|
||||||
def check_yaml_colons(candidate, settings):
|
def check_yaml_colons(candidate, settings):
|
||||||
options = "rules: {{colons: {conf}}}".format(
|
options = "rules: {{colons: {conf}}}".format(conf=settings["yamllint"]["colons"])
|
||||||
conf=settings["yamllint"]["colons"])
|
|
||||||
errors = run_yamllint(candidate.path, options)
|
errors = run_yamllint(candidate.path, options)
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
|
||||||
@ -95,14 +91,12 @@ def check_yaml_file(candidate, settings):
|
|||||||
filename = candidate.path
|
filename = candidate.path
|
||||||
|
|
||||||
if os.path.isfile(filename) and os.path.splitext(filename)[1][1:] != "yml":
|
if os.path.isfile(filename) and os.path.splitext(filename)[1][1:] != "yml":
|
||||||
errors.append(
|
errors.append(Error(None, "file does not have a .yml extension"))
|
||||||
Error(None, "file does not have a .yml extension"))
|
|
||||||
elif os.path.isfile(filename) and os.path.splitext(filename)[1][1:] == "yml":
|
elif os.path.isfile(filename) and os.path.splitext(filename)[1][1:] == "yml":
|
||||||
with codecs.open(filename, mode="rb", encoding="utf-8") as f:
|
with codecs.open(filename, mode="rb", encoding="utf-8") as f:
|
||||||
try:
|
try:
|
||||||
yaml.safe_load(f)
|
yaml.safe_load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(
|
errors.append(Error(e.problem_mark.line + 1, "syntax error: %s" % (e.problem)))
|
||||||
Error(e.problem_mark.line + 1, "syntax error: %s" % (e.problem)))
|
|
||||||
|
|
||||||
return Result(candidate.path, errors)
|
return Result(candidate.path, errors)
|
||||||
|
@ -71,8 +71,9 @@ class Settings(object):
|
|||||||
defaults = self._get_defaults()
|
defaults = self._get_defaults()
|
||||||
source_files = []
|
source_files = []
|
||||||
source_files.append(self.config_file)
|
source_files.append(self.config_file)
|
||||||
source_files.append(os.path.relpath(
|
source_files.append(
|
||||||
os.path.normpath(os.path.join(os.getcwd(), ".later.yml"))))
|
os.path.relpath(os.path.normpath(os.path.join(os.getcwd(), ".later.yml")))
|
||||||
|
)
|
||||||
cli_options = self.args
|
cli_options = self.args
|
||||||
|
|
||||||
for config in source_files:
|
for config in source_files:
|
||||||
@ -147,10 +148,11 @@ class Settings(object):
|
|||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
schema_error = "Failed validating '{validator}' in schema{schema}".format(
|
schema_error = "Failed validating '{validator}' in schema{schema}".format(
|
||||||
validator=e.validator,
|
validator=e.validator, schema=format_as_index(list(e.relative_schema_path)[:-1])
|
||||||
schema=format_as_index(list(e.relative_schema_path)[:-1])
|
)
|
||||||
|
utils.sysexit_with_message(
|
||||||
|
"{schema}: {msg}".format(schema=schema_error, msg=e.message)
|
||||||
)
|
)
|
||||||
utils.sysexit_with_message("{schema}: {msg}".format(schema=schema_error, msg=e.message))
|
|
||||||
|
|
||||||
def _update_filelist(self):
|
def _update_filelist(self):
|
||||||
includes = self.config["rules"]["files"]
|
includes = self.config["rules"]["files"]
|
||||||
@ -165,8 +167,7 @@ class Settings(object):
|
|||||||
filelist = []
|
filelist = []
|
||||||
for root, dirs, files in os.walk("."):
|
for root, dirs, files in os.walk("."):
|
||||||
for filename in files:
|
for filename in files:
|
||||||
filelist.append(
|
filelist.append(os.path.relpath(os.path.normpath(os.path.join(root, filename))))
|
||||||
os.path.relpath(os.path.normpath(os.path.join(root, filename))))
|
|
||||||
|
|
||||||
valid = []
|
valid = []
|
||||||
includespec = pathspec.PathSpec.from_lines("gitwildmatch", includes)
|
includespec = pathspec.PathSpec.from_lines("gitwildmatch", includes)
|
||||||
|
@ -22,7 +22,5 @@ class Standard(object):
|
|||||||
self.check = standard_dict.get("check")
|
self.check = standard_dict.get("check")
|
||||||
self.types = standard_dict.get("types")
|
self.types = standard_dict.get("types")
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self): # noqa
|
def __repr__(self): # noqa
|
||||||
return "Standard: %s (version: %s, types: %s)" % (
|
return "Standard: %s (version: %s, types: %s)" % (self.name, self.version, self.types)
|
||||||
self.name, self.version, self.types)
|
|
||||||
|
@ -21,183 +21,217 @@ from ansiblelater.rules.yamlfiles import check_yaml_has_content
|
|||||||
from ansiblelater.rules.yamlfiles import check_yaml_hyphens
|
from ansiblelater.rules.yamlfiles import check_yaml_hyphens
|
||||||
from ansiblelater.rules.yamlfiles import check_yaml_indent
|
from ansiblelater.rules.yamlfiles import check_yaml_indent
|
||||||
|
|
||||||
tasks_should_be_separated = Standard(dict(
|
tasks_should_be_separated = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0001",
|
id="ANSIBLE0001",
|
||||||
name="Single tasks should be separated by empty line",
|
name="Single tasks should be separated by empty line",
|
||||||
check=check_line_between_tasks,
|
check=check_line_between_tasks,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
role_must_contain_meta_main = Standard(dict(
|
role_must_contain_meta_main = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0002",
|
id="ANSIBLE0002",
|
||||||
name="Roles must contain suitable meta/main.yml",
|
name="Roles must contain suitable meta/main.yml",
|
||||||
check=check_meta_main,
|
check=check_meta_main,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["meta"]
|
types=["meta"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_are_uniquely_named = Standard(dict(
|
tasks_are_uniquely_named = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0003",
|
id="ANSIBLE0003",
|
||||||
name="Tasks and handlers must be uniquely named within a single file",
|
name="Tasks and handlers must be uniquely named within a single file",
|
||||||
check=check_unique_named_task,
|
check=check_unique_named_task,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_spaces_between_variable_braces = Standard(dict(
|
use_spaces_between_variable_braces = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0004",
|
id="ANSIBLE0004",
|
||||||
name="YAML should use consistent number of spaces around variables",
|
name="YAML should use consistent number of spaces around variables",
|
||||||
check=check_braces_spaces,
|
check=check_braces_spaces,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
roles_scm_not_in_src = Standard(dict(
|
roles_scm_not_in_src = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0005",
|
id="ANSIBLE0005",
|
||||||
name="Use scm key rather than src: scm+url",
|
name="Use scm key rather than src: scm+url",
|
||||||
check=check_scm_in_src,
|
check=check_scm_in_src,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["rolesfile"]
|
types=["rolesfile"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_are_named = Standard(dict(
|
tasks_are_named = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0006",
|
id="ANSIBLE0006",
|
||||||
name="Tasks and handlers must be named",
|
name="Tasks and handlers must be named",
|
||||||
check=check_named_task,
|
check=check_named_task,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tasks_names_are_formatted = Standard(dict(
|
tasks_names_are_formatted = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0007",
|
id="ANSIBLE0007",
|
||||||
name="Name of tasks and handlers must be formatted",
|
name="Name of tasks and handlers must be formatted",
|
||||||
check=check_name_format,
|
check=check_name_format,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"],
|
types=["playbook", "task", "handler"],
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
commands_should_not_be_used_in_place_of_modules = Standard(dict(
|
commands_should_not_be_used_in_place_of_modules = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0008",
|
id="ANSIBLE0008",
|
||||||
name="Commands should not be used in place of modules",
|
name="Commands should not be used in place of modules",
|
||||||
check=check_command_instead_of_module,
|
check=check_command_instead_of_module,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
package_installs_should_not_use_latest = Standard(dict(
|
package_installs_should_not_use_latest = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0009",
|
id="ANSIBLE0009",
|
||||||
name="Package installs should use present, not latest",
|
name="Package installs should use present, not latest",
|
||||||
check=check_install_use_latest,
|
check=check_install_use_latest,
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_shell_only_when_necessary = Standard(dict(
|
use_shell_only_when_necessary = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0010",
|
id="ANSIBLE0010",
|
||||||
name="Shell should only be used when essential",
|
name="Shell should only be used when essential",
|
||||||
check=check_shell_instead_command,
|
check=check_shell_instead_command,
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
commands_should_be_idempotent = Standard(dict(
|
commands_should_be_idempotent = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0011",
|
id="ANSIBLE0011",
|
||||||
name="Commands should be idempotent",
|
name="Commands should be idempotent",
|
||||||
check=check_command_has_changes,
|
check=check_command_has_changes,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task"]
|
types=["playbook", "task"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
dont_compare_to_empty_string = Standard(dict(
|
dont_compare_to_empty_string = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0012",
|
id="ANSIBLE0012",
|
||||||
name="Don't compare to \"\" - use `when: var` or `when: not var`",
|
name="Don't compare to \"\" - use `when: var` or `when: not var`",
|
||||||
check=check_empty_string_compare,
|
check=check_empty_string_compare,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "template"]
|
types=["playbook", "task", "handler", "template"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
dont_compare_to_literal_bool = Standard(dict(
|
dont_compare_to_literal_bool = Standard(
|
||||||
|
dict(
|
||||||
id="ANSIBLE0013",
|
id="ANSIBLE0013",
|
||||||
name="Don't compare to True or False - use `when: var` or `when: not var`",
|
name="Don't compare to True or False - use `when: var` or `when: not var`",
|
||||||
check=check_compare_to_literal_bool,
|
check=check_compare_to_literal_bool,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "template"]
|
types=["playbook", "task", "handler", "template"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
files_should_not_contain_unnecessarily_empty_lines = Standard(dict(
|
files_should_not_contain_unnecessarily_empty_lines = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0001",
|
id="LINT0001",
|
||||||
name="YAML should not contain unnecessarily empty lines",
|
name="YAML should not contain unnecessarily empty lines",
|
||||||
check=check_yaml_empty_lines,
|
check=check_yaml_empty_lines,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_be_indented = Standard(dict(
|
files_should_be_indented = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0002",
|
id="LINT0002",
|
||||||
name="YAML should be correctly indented",
|
name="YAML should be correctly indented",
|
||||||
check=check_yaml_indent,
|
check=check_yaml_indent,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_use_consistent_spaces_after_hyphens = Standard(dict(
|
files_should_use_consistent_spaces_after_hyphens = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0003",
|
id="LINT0003",
|
||||||
name="YAML should use consistent number of spaces after hyphens",
|
name="YAML should use consistent number of spaces after hyphens",
|
||||||
check=check_yaml_hyphens,
|
check=check_yaml_hyphens,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
files_should_contain_document_start_marker = Standard(dict(
|
files_should_contain_document_start_marker = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0004",
|
id="LINT0004",
|
||||||
name="YAML should contain document start marker",
|
name="YAML should contain document start marker",
|
||||||
check=check_yaml_document_start,
|
check=check_yaml_document_start,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
spaces_around_colons = Standard(dict(
|
spaces_around_colons = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0005",
|
id="LINT0005",
|
||||||
name="YAML should use consistent number of spaces around colons",
|
name="YAML should use consistent number of spaces around colons",
|
||||||
check=check_yaml_colons,
|
check=check_yaml_colons,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars",
|
types=["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
"hostvars", "groupvars", "meta"]
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
rolesfile_should_be_in_yaml = Standard(dict(
|
rolesfile_should_be_in_yaml = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0006",
|
id="LINT0006",
|
||||||
name="Roles file should be in yaml format",
|
name="Roles file should be in yaml format",
|
||||||
check=check_yaml_file,
|
check=check_yaml_file,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["rolesfile"]
|
types=["rolesfile"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
files_should_not_be_purposeless = Standard(dict(
|
files_should_not_be_purposeless = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0007",
|
id="LINT0007",
|
||||||
name="Files should contain useful content",
|
name="Files should contain useful content",
|
||||||
check=check_yaml_has_content,
|
check=check_yaml_has_content,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
types=["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
use_yaml_rather_than_key_value = Standard(dict(
|
use_yaml_rather_than_key_value = Standard(
|
||||||
|
dict(
|
||||||
id="LINT0008",
|
id="LINT0008",
|
||||||
name="Use YAML format for tasks and handlers rather than key=value",
|
name="Use YAML format for tasks and handlers rather than key=value",
|
||||||
check=check_native_yaml,
|
check=check_native_yaml,
|
||||||
version="0.1",
|
version="0.1",
|
||||||
types=["playbook", "task", "handler"]
|
types=["playbook", "task", "handler"]
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
ansible_min_version = '2.1'
|
ansible_min_version = '2.1'
|
||||||
ansible_later_min_version = '0.1.0'
|
ansible_later_min_version = '0.1.0'
|
||||||
|
|
||||||
|
|
||||||
standards = [
|
standards = [
|
||||||
# Ansible
|
# Ansible
|
||||||
tasks_should_be_separated,
|
tasks_should_be_separated,
|
||||||
|
@ -21,8 +21,11 @@ def test_critical(capsys, mocker):
|
|||||||
log.critical("foo")
|
log.critical("foo")
|
||||||
_, stderr = capsys.readouterr()
|
_, stderr = capsys.readouterr()
|
||||||
|
|
||||||
print("{}{}{}".format(colorama.Fore.RED, "CRITICAL: foo".rstrip(),
|
print(
|
||||||
colorama.Style.RESET_ALL))
|
"{}CRITICAL: {}foo\n{}".format(
|
||||||
|
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
||||||
|
)
|
||||||
|
)
|
||||||
x, _ = capsys.readouterr()
|
x, _ = capsys.readouterr()
|
||||||
|
|
||||||
assert x == stderr
|
assert x == stderr
|
||||||
@ -33,8 +36,11 @@ def test_error(capsys, mocker):
|
|||||||
log.error("foo")
|
log.error("foo")
|
||||||
_, stderr = capsys.readouterr()
|
_, stderr = capsys.readouterr()
|
||||||
|
|
||||||
print("{}{}{}".format(colorama.Fore.RED, "ERROR: foo".rstrip(),
|
print(
|
||||||
colorama.Style.RESET_ALL))
|
"{}ERROR: {}foo\n{}".format(
|
||||||
|
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
||||||
|
)
|
||||||
|
)
|
||||||
x, _ = capsys.readouterr()
|
x, _ = capsys.readouterr()
|
||||||
|
|
||||||
assert x == stderr
|
assert x == stderr
|
||||||
@ -45,8 +51,11 @@ def test_warn(capsys, mocker):
|
|||||||
log.warning("foo")
|
log.warning("foo")
|
||||||
stdout, _ = capsys.readouterr()
|
stdout, _ = capsys.readouterr()
|
||||||
|
|
||||||
print("{}{}{}".format(colorama.Fore.YELLOW, "WARNING: foo".rstrip(),
|
print(
|
||||||
colorama.Style.RESET_ALL))
|
"{}WARNING: {}foo\n{}".format(
|
||||||
|
colorama.Fore.YELLOW, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
||||||
|
)
|
||||||
|
)
|
||||||
x, _ = capsys.readouterr()
|
x, _ = capsys.readouterr()
|
||||||
|
|
||||||
assert x == stdout
|
assert x == stdout
|
||||||
@ -57,8 +66,11 @@ def test_info(capsys, mocker):
|
|||||||
log.info("foo")
|
log.info("foo")
|
||||||
stdout, _ = capsys.readouterr()
|
stdout, _ = capsys.readouterr()
|
||||||
|
|
||||||
print("{}{}{}".format(colorama.Fore.BLUE, "INFO: foo".rstrip(),
|
print(
|
||||||
colorama.Style.RESET_ALL))
|
"{}INFO: {}foo\n{}".format(
|
||||||
|
colorama.Fore.BLUE, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
||||||
|
)
|
||||||
|
)
|
||||||
x, _ = capsys.readouterr()
|
x, _ = capsys.readouterr()
|
||||||
|
|
||||||
assert x == stdout
|
assert x == stdout
|
||||||
|
@ -17,7 +17,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import configparser # noqa
|
import configparser # noqa
|
||||||
|
|
||||||
|
|
||||||
LOG = logger.get_logger(__name__)
|
LOG = logger.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ def count_spaces(c_string):
|
|||||||
break
|
break
|
||||||
trailing_spaces += 1
|
trailing_spaces += 1
|
||||||
|
|
||||||
return((leading_spaces, trailing_spaces))
|
return ((leading_spaces, trailing_spaces))
|
||||||
|
|
||||||
|
|
||||||
def get_property(prop):
|
def get_property(prop):
|
||||||
@ -43,7 +42,8 @@ def get_property(prop):
|
|||||||
parentdir = os.path.dirname(currentdir)
|
parentdir = os.path.dirname(currentdir)
|
||||||
result = re.search(
|
result = re.search(
|
||||||
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
|
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
|
||||||
open(os.path.join(parentdir, "__init__.py")).read())
|
open(os.path.join(parentdir, "__init__.py")).read()
|
||||||
|
)
|
||||||
return result.group(1)
|
return result.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,7 +81,8 @@ def get_normalized_tasks(candidate, settings):
|
|||||||
# No need to normalize_task if we are skipping it.
|
# No need to normalize_task if we are skipping it.
|
||||||
continue
|
continue
|
||||||
normalized.append(
|
normalized.append(
|
||||||
normalize_task(task, candidate.path, settings["ansible"]["custom_modules"]))
|
normalize_task(task, candidate.path, settings["ansible"]["custom_modules"])
|
||||||
|
)
|
||||||
|
|
||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
import codecs
|
import codecs
|
||||||
import glob
|
import glob
|
||||||
import imp
|
import imp
|
||||||
import inspect
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import ansible.parsing.mod_args
|
import ansible.parsing.mod_args
|
||||||
@ -78,14 +77,46 @@ LINE_NUMBER_KEY = "__line__"
|
|||||||
FILENAME_KEY = "__file__"
|
FILENAME_KEY = "__file__"
|
||||||
|
|
||||||
VALID_KEYS = [
|
VALID_KEYS = [
|
||||||
"name", "action", "when", "async", "poll", "notify",
|
"name",
|
||||||
"first_available_file", "include", "import_playbook",
|
"action",
|
||||||
"tags", "register", "ignore_errors", "delegate_to",
|
"when",
|
||||||
"local_action", "transport", "remote_user", "sudo",
|
"async",
|
||||||
"sudo_user", "sudo_pass", "when", "connection", "environment", "args", "always_run",
|
"poll",
|
||||||
"any_errors_fatal", "changed_when", "failed_when", "check_mode", "delay",
|
"notify",
|
||||||
"retries", "until", "su", "su_user", "su_pass", "no_log", "run_once",
|
"first_available_file",
|
||||||
"become", "become_user", "become_method", FILENAME_KEY,
|
"include",
|
||||||
|
"import_playbook",
|
||||||
|
"tags",
|
||||||
|
"register",
|
||||||
|
"ignore_errors",
|
||||||
|
"delegate_to",
|
||||||
|
"local_action",
|
||||||
|
"transport",
|
||||||
|
"remote_user",
|
||||||
|
"sudo",
|
||||||
|
"sudo_user",
|
||||||
|
"sudo_pass",
|
||||||
|
"when",
|
||||||
|
"connection",
|
||||||
|
"environment",
|
||||||
|
"args",
|
||||||
|
"always_run",
|
||||||
|
"any_errors_fatal",
|
||||||
|
"changed_when",
|
||||||
|
"failed_when",
|
||||||
|
"check_mode",
|
||||||
|
"delay",
|
||||||
|
"retries",
|
||||||
|
"until",
|
||||||
|
"su",
|
||||||
|
"su_user",
|
||||||
|
"su_pass",
|
||||||
|
"no_log",
|
||||||
|
"run_once",
|
||||||
|
"become",
|
||||||
|
"become_user",
|
||||||
|
"become_method",
|
||||||
|
FILENAME_KEY,
|
||||||
]
|
]
|
||||||
|
|
||||||
BLOCK_NAME_TO_ACTION_TYPE_MAP = {
|
BLOCK_NAME_TO_ACTION_TYPE_MAP = {
|
||||||
@ -170,17 +201,16 @@ def find_children(playbook, playbook_dir):
|
|||||||
break
|
break
|
||||||
valid_tokens.append(token)
|
valid_tokens.append(token)
|
||||||
path = " ".join(valid_tokens)
|
path = " ".join(valid_tokens)
|
||||||
results.append({
|
results.append({"path": path_dwim(basedir, path), "type": child["type"]})
|
||||||
"path": path_dwim(basedir, path),
|
|
||||||
"type": child["type"]
|
|
||||||
})
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def template(basedir, value, variables, fail_on_undefined=False, **kwargs):
|
def template(basedir, value, variables, fail_on_undefined=False, **kwargs):
|
||||||
try:
|
try:
|
||||||
value = ansible_template(os.path.abspath(basedir), value, variables,
|
value = ansible_template(
|
||||||
**dict(kwargs, fail_on_undefined=fail_on_undefined))
|
os.path.abspath(basedir), value, variables,
|
||||||
|
**dict(kwargs, fail_on_undefined=fail_on_undefined)
|
||||||
|
)
|
||||||
# Hack to skip the following exception when using to_json filter on a variable.
|
# Hack to skip the following exception when using to_json filter on a variable.
|
||||||
# I guess the filter doesn't like empty vars...
|
# I guess the filter doesn't like empty vars...
|
||||||
except (AnsibleError, ValueError):
|
except (AnsibleError, ValueError):
|
||||||
@ -207,10 +237,12 @@ def play_children(basedir, item, parent_type, playbook_dir):
|
|||||||
|
|
||||||
if k in delegate_map:
|
if k in delegate_map:
|
||||||
if v:
|
if v:
|
||||||
v = template(os.path.abspath(basedir),
|
v = template(
|
||||||
|
os.path.abspath(basedir),
|
||||||
v,
|
v,
|
||||||
dict(playbook_dir=os.path.abspath(basedir)),
|
dict(playbook_dir=os.path.abspath(basedir)),
|
||||||
fail_on_undefined=False)
|
fail_on_undefined=False
|
||||||
|
)
|
||||||
return delegate_map[k](basedir, k, v, parent_type)
|
return delegate_map[k](basedir, k, v, parent_type)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -237,12 +269,23 @@ def _taskshandlers_children(basedir, k, v, parent_type):
|
|||||||
elif "import_tasks" in th:
|
elif "import_tasks" in th:
|
||||||
append_children(th["import_tasks"], basedir, k, parent_type, results)
|
append_children(th["import_tasks"], basedir, k, parent_type, results)
|
||||||
elif "import_role" in th:
|
elif "import_role" in th:
|
||||||
results.extend(_roles_children(basedir, k, [th["import_role"].get("name")], parent_type,
|
results.extend(
|
||||||
main=th["import_role"].get("tasks_from", "main")))
|
_roles_children(
|
||||||
elif "include_role" in th:
|
basedir,
|
||||||
results.extend(_roles_children(basedir, k, [th["include_role"].get("name")],
|
k, [th["import_role"].get("name")],
|
||||||
parent_type,
|
parent_type,
|
||||||
main=th["include_role"].get("tasks_from", "main")))
|
main=th["import_role"].get("tasks_from", "main")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif "include_role" in th:
|
||||||
|
results.extend(
|
||||||
|
_roles_children(
|
||||||
|
basedir,
|
||||||
|
k, [th["include_role"].get("name")],
|
||||||
|
parent_type,
|
||||||
|
main=th["include_role"].get("tasks_from", "main")
|
||||||
|
)
|
||||||
|
)
|
||||||
elif "block" in th:
|
elif "block" in th:
|
||||||
results.extend(_taskshandlers_children(basedir, k, th["block"], parent_type))
|
results.extend(_taskshandlers_children(basedir, k, th["block"], parent_type))
|
||||||
if "rescue" in th:
|
if "rescue" in th:
|
||||||
@ -260,10 +303,7 @@ def append_children(taskhandler, basedir, k, parent_type, results):
|
|||||||
playbook_section = k
|
playbook_section = k
|
||||||
else:
|
else:
|
||||||
playbook_section = parent_type
|
playbook_section = parent_type
|
||||||
results.append({
|
results.append({"path": path_dwim(basedir, taskhandler), "type": playbook_section})
|
||||||
"path": path_dwim(basedir, taskhandler),
|
|
||||||
"type": playbook_section
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _roles_children(basedir, k, v, parent_type, main="main"):
|
def _roles_children(basedir, k, v, parent_type, main="main"):
|
||||||
@ -272,12 +312,16 @@ def _roles_children(basedir, k, v, parent_type, main="main"):
|
|||||||
if isinstance(role, dict):
|
if isinstance(role, dict):
|
||||||
if "role" in role or "name" in role:
|
if "role" in role or "name" in role:
|
||||||
if "tags" not in role or "skip_ansible_later" not in role["tags"]:
|
if "tags" not in role or "skip_ansible_later" not in role["tags"]:
|
||||||
results.extend(_look_for_role_files(basedir,
|
results.extend(
|
||||||
role.get("role", role.get("name")),
|
_look_for_role_files(
|
||||||
main=main))
|
basedir, role.get("role", role.get("name")), main=main
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise SystemExit("role dict {0} does not contain a 'role' "
|
raise SystemExit(
|
||||||
"or 'name' key".format(role))
|
"role dict {0} does not contain a 'role' "
|
||||||
|
"or 'name' key".format(role)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
results.extend(_look_for_role_files(basedir, role, main=main))
|
results.extend(_look_for_role_files(basedir, role, main=main))
|
||||||
return results
|
return results
|
||||||
@ -296,9 +340,7 @@ def _rolepath(basedir, role):
|
|||||||
path_dwim(basedir, os.path.join("roles", role)),
|
path_dwim(basedir, os.path.join("roles", role)),
|
||||||
path_dwim(basedir, role),
|
path_dwim(basedir, role),
|
||||||
# if included from roles/[role]/meta/main.yml
|
# if included from roles/[role]/meta/main.yml
|
||||||
path_dwim(
|
path_dwim(basedir, os.path.join("..", "..", "..", "roles", role)),
|
||||||
basedir, os.path.join("..", "..", "..", "roles", role)
|
|
||||||
),
|
|
||||||
path_dwim(basedir, os.path.join("..", "..", role))
|
path_dwim(basedir, os.path.join("..", "..", role))
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -356,7 +398,7 @@ def normalize_task(task, filename, custom_modules=[]):
|
|||||||
ansible_action_type = task.get("__ansible_action_type__", "task")
|
ansible_action_type = task.get("__ansible_action_type__", "task")
|
||||||
ansible_action_meta = task.get("__ansible_action_meta__", dict())
|
ansible_action_meta = task.get("__ansible_action_meta__", dict())
|
||||||
if "__ansible_action_type__" in task:
|
if "__ansible_action_type__" in task:
|
||||||
del(task["__ansible_action_type__"])
|
del (task["__ansible_action_type__"])
|
||||||
|
|
||||||
normalized = dict()
|
normalized = dict()
|
||||||
# TODO: Workaround for custom modules
|
# TODO: Workaround for custom modules
|
||||||
@ -372,7 +414,7 @@ def normalize_task(task, filename, custom_modules=[]):
|
|||||||
# denormalize shell -> command conversion
|
# denormalize shell -> command conversion
|
||||||
if "_uses_shell" in arguments:
|
if "_uses_shell" in arguments:
|
||||||
action = "shell"
|
action = "shell"
|
||||||
del(arguments["_uses_shell"])
|
del (arguments["_uses_shell"])
|
||||||
|
|
||||||
for (k, v) in list(task.items()):
|
for (k, v) in list(task.items()):
|
||||||
if k in ("action", "local_action", "args", "delegate_to") or k == action:
|
if k in ("action", "local_action", "args", "delegate_to") or k == action:
|
||||||
@ -386,7 +428,7 @@ def normalize_task(task, filename, custom_modules=[]):
|
|||||||
|
|
||||||
if "_raw_params" in arguments:
|
if "_raw_params" in arguments:
|
||||||
normalized["action"]["__ansible_arguments__"] = arguments["_raw_params"].strip().split()
|
normalized["action"]["__ansible_arguments__"] = arguments["_raw_params"].strip().split()
|
||||||
del(arguments["_raw_params"])
|
del (arguments["_raw_params"])
|
||||||
else:
|
else:
|
||||||
normalized["action"]["__ansible_arguments__"] = list()
|
normalized["action"]["__ansible_arguments__"] = list()
|
||||||
normalized["action"].update(arguments)
|
normalized["action"].update(arguments)
|
||||||
@ -410,8 +452,9 @@ def action_tasks(yaml, file):
|
|||||||
block_rescue_always = ("block", "rescue", "always")
|
block_rescue_always = ("block", "rescue", "always")
|
||||||
tasks[:] = [task for task in tasks if all(k not in task for k in block_rescue_always)]
|
tasks[:] = [task for task in tasks if all(k not in task for k in block_rescue_always)]
|
||||||
|
|
||||||
return [task for task in tasks if set(
|
allowed = ["include", "include_tasks", "import_playbook", "import_tasks"]
|
||||||
["include", "include_tasks", "import_playbook", "import_tasks"]).isdisjoint(task.keys())]
|
|
||||||
|
return [task for task in tasks if set(allowed).isdisjoint(task.keys())]
|
||||||
|
|
||||||
|
|
||||||
def task_to_str(task):
|
def task_to_str(task):
|
||||||
@ -419,7 +462,9 @@ def task_to_str(task):
|
|||||||
if name:
|
if name:
|
||||||
return name
|
return name
|
||||||
action = task.get("action")
|
action = task.get("action")
|
||||||
args = " ".join([u"{0}={1}".format(k, v) for (k, v) in action.items()
|
args = " ".join([
|
||||||
|
u"{0}={1}".format(k, v)
|
||||||
|
for (k, v) in action.items()
|
||||||
if k not in ["__ansible_module__", "__ansible_arguments__"]
|
if k not in ["__ansible_module__", "__ansible_arguments__"]
|
||||||
] + action.get("__ansible_arguments__"))
|
] + action.get("__ansible_arguments__"))
|
||||||
return u"{0} {1}".format(action["__ansible_module__"], args)
|
return u"{0} {1}".format(action["__ansible_module__"], args)
|
||||||
@ -439,7 +484,8 @@ def extract_from_list(blocks, candidates):
|
|||||||
elif block[candidate] is not None:
|
elif block[candidate] is not None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Key '%s' defined, but bad value: '%s'" %
|
"Key '%s' defined, but bad value: '%s'" %
|
||||||
(candidate, str(block[candidate])))
|
(candidate, str(block[candidate]))
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
@ -460,6 +506,7 @@ def parse_yaml_linenumbers(data, filename):
|
|||||||
The line numbers are stored in each node's LINE_NUMBER_KEY key.
|
The line numbers are stored in each node's LINE_NUMBER_KEY key.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def compose_node(parent, index):
|
def compose_node(parent, index):
|
||||||
# the line number where the previous token has ended (plus empty lines)
|
# the line number where the previous token has ended (plus empty lines)
|
||||||
line = loader.line
|
line = loader.line
|
||||||
|
14
setup.cfg
14
setup.cfg
@ -10,11 +10,21 @@ default_section = THIRDPARTY
|
|||||||
known_first_party = ansiblelater
|
known_first_party = ansiblelater
|
||||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||||
force_single_line = true
|
force_single_line = true
|
||||||
line_length = 110
|
line_length = 99
|
||||||
skip_glob = **/.env/*,**/docs/*
|
skip_glob = **/.env*,**/env/*,**/docs/*
|
||||||
|
|
||||||
|
[yapf]
|
||||||
|
based_on_style = google
|
||||||
|
column_limit = 99
|
||||||
|
dedent_closing_brackets = true
|
||||||
|
coalesce_brackets = true
|
||||||
|
split_before_logical_operator = true
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
ignore::FutureWarning
|
ignore::FutureWarning
|
||||||
ignore:.*collections.*:DeprecationWarning
|
ignore:.*collections.*:DeprecationWarning
|
||||||
ignore:.*pep8.*:FutureWarning
|
ignore:.*pep8.*:FutureWarning
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
omit = **/tests/*
|
||||||
|
29
setup.py
29
setup.py
@ -15,7 +15,8 @@ def get_property(prop, project):
|
|||||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
result = re.search(
|
result = re.search(
|
||||||
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
|
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
|
||||||
open(os.path.join(current_dir, project, "__init__.py")).read())
|
open(os.path.join(current_dir, project, "__init__.py")).read()
|
||||||
|
)
|
||||||
return result.group(1)
|
return result.group(1)
|
||||||
|
|
||||||
|
|
||||||
@ -28,12 +29,12 @@ def get_readme(filename="README.md"):
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=get_property("__project__", PACKAGE_NAME),
|
name=get_property("__project__", PACKAGE_NAME),
|
||||||
version=get_property("__version__", PACKAGE_NAME),
|
|
||||||
description=("Reviews ansible playbooks, roles and inventories and suggests improvements."),
|
description=("Reviews ansible playbooks, roles and inventories and suggests improvements."),
|
||||||
keywords="ansible code review",
|
keywords="ansible code review",
|
||||||
|
version=get_property("__version__", PACKAGE_NAME),
|
||||||
author=get_property("__author__", PACKAGE_NAME),
|
author=get_property("__author__", PACKAGE_NAME),
|
||||||
author_email=get_property("__email__", PACKAGE_NAME),
|
author_email=get_property("__email__", PACKAGE_NAME),
|
||||||
url="https://github.com/xoxys/ansible-later",
|
url=get_property("__url__", PACKAGE_NAME),
|
||||||
license=get_property("__license__", PACKAGE_NAME),
|
license=get_property("__license__", PACKAGE_NAME),
|
||||||
long_description=get_readme(),
|
long_description=get_readme(),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
@ -59,25 +60,9 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"ansible",
|
"ansible", "six", "pyyaml", "appdirs", "unidiff", "flake8", "yamllint", "nested-lookup",
|
||||||
"six",
|
"colorama", "anyconfig", "python-json-logger", "jsonschema", "pathspec", "toolz"
|
||||||
"pyyaml",
|
|
||||||
"appdirs",
|
|
||||||
"unidiff",
|
|
||||||
"flake8",
|
|
||||||
"yamllint",
|
|
||||||
"nested-lookup",
|
|
||||||
"colorama",
|
|
||||||
"anyconfig",
|
|
||||||
"python-json-logger",
|
|
||||||
"jsonschema",
|
|
||||||
"pathspec",
|
|
||||||
"toolz"
|
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={"console_scripts": ["ansible-later = ansiblelater.__main__:main"]},
|
||||||
"console_scripts": [
|
|
||||||
"ansible-later = ansiblelater.__main__:main"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
test_suite="tests"
|
test_suite="tests"
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
# open issue
|
|
||||||
# https://gitlab.com/pycqa/flake8-docstrings/issues/36
|
|
||||||
pydocstyle<4.0.0
|
pydocstyle<4.0.0
|
||||||
flake8
|
flake8
|
||||||
flake8-colors
|
flake8-colors
|
||||||
flake8-blind-except
|
flake8-blind-except
|
||||||
flake8-builtins
|
flake8-builtins
|
||||||
flake8-colors
|
|
||||||
flake8-docstrings<=3.0.0
|
flake8-docstrings<=3.0.0
|
||||||
flake8-isort
|
flake8-isort
|
||||||
flake8-logging-format
|
flake8-logging-format
|
||||||
@ -17,3 +14,5 @@ pytest
|
|||||||
pytest-mock
|
pytest-mock
|
||||||
pytest-cov
|
pytest-cov
|
||||||
bandit
|
bandit
|
||||||
|
requests-mock
|
||||||
|
yapf
|
||||||
|
2
tox.ini
2
tox.ini
@ -15,4 +15,4 @@ deps =
|
|||||||
ansibledevel: git+https://github.com/ansible/ansible.git
|
ansibledevel: git+https://github.com/ansible/ansible.git
|
||||||
commands =
|
commands =
|
||||||
ansible-later --help
|
ansible-later --help
|
||||||
pytest ansiblelater/tests/ --cov={toxinidir}/ansiblelater/ --no-cov-on-fail
|
pytest --cov={toxinidir}/ansiblelater --no-cov-on-fail
|
||||||
|
Loading…
Reference in New Issue
Block a user