add some useful flake8 plugins and start refactoring formatting

This commit is contained in:
Robert Kaussow 2019-03-27 17:00:38 +01:00
parent 43d5a7c41c
commit 4b011891bf
7 changed files with 180 additions and 130 deletions

View File

@ -1,3 +1,5 @@
"""Default package."""
__author__ = "Robert Kaussow" __author__ = "Robert Kaussow"
__project__ = "ansible-later" __project__ = "ansible-later"
__version__ = "0.1.5" __version__ = "0.1.5"
@ -6,18 +8,17 @@ __maintainer__ = "Robert Kaussow"
__email__ = "mail@geeklabor.de" __email__ = "mail@geeklabor.de"
__status__ = "Production" __status__ = "Production"
import re
import os
import codecs import codecs
import ansible import os
import re
from distutils.version import LooseVersion from distutils.version import LooseVersion
from ansiblelater.utils import info, warn, abort, error
from ansiblelater.utils import read_standards import ansible
from ansiblelater.utils import get_property
from ansiblelater.utils import standards_latest from ansiblelater.utils import (abort, error, get_property, info,
from ansiblelater.utils import is_line_in_ranges is_line_in_ranges, lines_ranges,
from ansiblelater.utils import lines_ranges read_standards, standards_latest, warn)
from .settings import Settings
try: try:
# Ansible 2.4 import of module loader # Ansible 2.4 import of module loader
@ -29,40 +30,87 @@ except ImportError:
from ansible.utils import module_finder as module_loader from ansible.utils import module_finder as module_loader
class AnsibleReviewFormatter(object): config = Settings()
def format(self, match):
formatstr = u"{0}:{1}: [{2}] {3} {4}"
return formatstr.format(match.filename,
match.linenumber,
match.rule.id,
match.message,
match.line
)
class Standard(object): class Standard(object):
"""
Standard definition for all defined rules.
Later lookup the config file for a path to a rules directory
or fallback to default `ansiblelater/examples/*`.
"""
def __init__(self, standard_dict): def __init__(self, standard_dict):
if 'id' not in standard_dict: """
standard_dict.update(id='') Initialize a new standard object and returns None.
:param standard_dict: Dictionary object containing all neseccary attributes
"""
if "id" not in standard_dict:
standard_dict.update(id="")
else: else:
standard_dict.update(id='[{}] '.format(standard_dict.get("id"))) standard_dict.update(id="[{}] ".format(standard_dict.get("id")))
self.id = standard_dict.get("id") self.id = standard_dict.get("id")
self.name = standard_dict.get("name") self.name = standard_dict.get("name")
self.version = standard_dict.get("version") self.version = standard_dict.get("version")
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): 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)
class Candidate(object):
"""
Meta object for all files which later has to process.
Each file passed to later will be classified by type and
bundled with necessary meta informations for rule processing.
"""
def __init__(self, filename):
self.path = filename
self.binary = False
self.vault = False
try:
self.version = find_version(filename)
with codecs.open(filename, mode="rb", encoding="utf-8") as f:
if f.readline().startswith("$ANSIBLE_VAULT"):
self.vault = True
except UnicodeDecodeError:
self.binary = True
self.filetype = type(self).__name__.lower()
self.expected_version = True
def review(self, settings, lines=None):
return candidate_review(self, settings, lines)
def __repr__(self): # noqa
return "%s (%s)" % (type(self).__name__, self.path)
def __getitem__(self, item): # noqa
return self.__dict__.get(item)
class Error(object): class Error(object):
"""Default error object created if a rule failed."""
def __init__(self, lineno, message): def __init__(self, lineno, message):
"""
Initialize a new error object and returns None.
:param lineno: Line number where the error from de rule occures
:param message: Detailed error description provided by the rule
"""
self.lineno = lineno self.lineno = lineno
self.message = message self.message = message
def __repr__(self): def __repr__(self): # noqa
if self.lineno: if self.lineno:
return "%s: %s" % (self.lineno, self.message) return "%s: %s" % (self.lineno, self.message)
else: else:
@ -79,33 +127,6 @@ class Result(object):
for error in self.errors]) for error in self.errors])
class Candidate(object):
def __init__(self, filename):
self.path = filename
self.binary = False
self.vault = False
try:
self.version = find_version(filename)
with codecs.open(filename, mode='rb', encoding='utf-8') as f:
if f.readline().startswith("$ANSIBLE_VAULT"):
self.vault = True
except UnicodeDecodeError:
self.binary = True
self.filetype = type(self).__name__.lower()
self.expected_version = True
def review(self, settings, lines=None):
return candidate_review(self, settings, lines)
def __repr__(self):
return "%s (%s)" % (type(self).__name__, self.path)
def __getitem__(self, item):
return self.__dict__.get(item)
class RoleFile(Candidate): class RoleFile(Candidate):
def __init__(self, filename): def __init__(self, filename):
super(RoleFile, self).__init__(filename) super(RoleFile, self).__init__(filename)
@ -118,7 +139,7 @@ class RoleFile(Candidate):
if self.version: if self.version:
break break
parentdir = os.path.dirname(parentdir) parentdir = os.path.dirname(parentdir)
role_modules = os.path.join(parentdir, 'library') role_modules = os.path.join(parentdir, "library")
if os.path.exists(role_modules): if os.path.exists(role_modules):
module_loader.add_directory(role_modules) module_loader.add_directory(role_modules)
@ -130,13 +151,13 @@ class Playbook(Candidate):
class Task(RoleFile): class Task(RoleFile):
def __init__(self, filename): def __init__(self, filename):
super(Task, self).__init__(filename) super(Task, self).__init__(filename)
self.filetype = 'tasks' self.filetype = "tasks"
class Handler(RoleFile): class Handler(RoleFile):
def __init__(self, filename): def __init__(self, filename):
super(Handler, self).__init__(filename) super(Handler, self).__init__(filename)
self.filetype = 'handlers' self.filetype = "handlers"
class Vars(Candidate): class Vars(Candidate):
@ -201,34 +222,34 @@ class Rolesfile(Unversioned):
def classify(filename): def classify(filename):
parentdir = os.path.basename(os.path.dirname(filename)) parentdir = os.path.basename(os.path.dirname(filename))
if parentdir in ['tasks']: if parentdir in ["tasks"]:
return Task(filename) return Task(filename)
if parentdir in ['handlers']: if parentdir in ["handlers"]:
return Handler(filename) return Handler(filename)
if parentdir in ['vars', 'defaults']: if parentdir in ["vars", "defaults"]:
return RoleVars(filename) return RoleVars(filename)
if 'group_vars' in filename.split(os.sep): if "group_vars" in filename.split(os.sep):
return GroupVars(filename) return GroupVars(filename)
if 'host_vars' in filename.split(os.sep): if "host_vars" in filename.split(os.sep):
return HostVars(filename) return HostVars(filename)
if parentdir in ['meta']: if parentdir in ["meta"]:
return Meta(filename) return Meta(filename)
if parentdir in ['library', 'lookup_plugins', 'callback_plugins', if parentdir in ["library", "lookup_plugins", "callback_plugins",
'filter_plugins'] or filename.endswith('.py'): "filter_plugins"] or filename.endswith(".py"):
return Code(filename) return Code(filename)
if 'inventory' in filename or 'hosts' in filename or parentdir in ['inventory']: if "inventory" in filename or "hosts" in filename or parentdir in ["inventory"]:
return Inventory(filename) return Inventory(filename)
if 'rolesfile' in filename or 'requirements' in filename: if "rolesfile" in filename or "requirements" in filename:
return Rolesfile(filename) return Rolesfile(filename)
if 'Makefile' in filename: if "Makefile" in filename:
return Makefile(filename) return Makefile(filename)
if 'templates' in filename.split(os.sep) or filename.endswith('.j2'): if "templates" in filename.split(os.sep) or filename.endswith(".j2"):
return Template(filename) return Template(filename)
if 'files' in filename.split(os.sep): if "files" in filename.split(os.sep):
return File(filename) return File(filename)
if filename.endswith('.yml') or filename.endswith('.yaml'): if filename.endswith(".yml") or filename.endswith(".yaml"):
return Playbook(filename) return Playbook(filename)
if 'README' in filename: if "README" in filename:
return Doc(filename) return Doc(filename)
return None return None
@ -236,13 +257,13 @@ def classify(filename):
def candidate_review(candidate, settings, lines=None): def candidate_review(candidate, settings, lines=None):
errors = 0 errors = 0
standards = read_standards(settings) standards = read_standards(settings)
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__):
raise SystemExit("Standards require ansible version %s (current version %s). " raise SystemExit("Standards require ansible version %s (current version %s). "
"Please upgrade ansible." % "Please upgrade ansible." %
(standards.ansible_min_version, ansible.__version__)) (standards.ansible_min_version, ansible.__version__))
if getattr(standards, 'ansible_review_min_version', None) and \ if getattr(standards, "ansible_review_min_version", None) and \
LooseVersion(standards.ansible_review_min_version) > LooseVersion( LooseVersion(standards.ansible_review_min_version) > LooseVersion(
get_property("__version__")): get_property("__version__")):
raise SystemExit("Standards require ansible-later version %s (current version %s). " raise SystemExit("Standards require ansible-later version %s (current version %s). "
@ -269,6 +290,7 @@ def candidate_review(candidate, settings, lines=None):
settings) settings)
for standard in standards.standards: for standard in standards.standards:
print(type(standard))
if type(candidate).__name__.lower() not in standard.types: if type(candidate).__name__.lower() not in standard.types:
continue continue
if settings.standards_filter and standard.name not in settings.standards_filter: if settings.standards_filter and standard.name not in settings.standards_filter:
@ -305,9 +327,10 @@ def candidate_review(candidate, settings, lines=None):
def find_version(filename, version_regex=r"^# Standards:\s*([\d.]+)"): def find_version(filename, version_regex=r"^# Standards:\s*([\d.]+)"):
version_re = re.compile(version_regex) version_re = re.compile(version_regex)
with codecs.open(filename, mode='rb', encoding='utf-8') as f: with codecs.open(filename, mode="rb", encoding="utf-8") as f:
for line in f: for line in f:
match = version_re.match(line) match = version_re.match(line)
if match: if match:
return match.group(1) return match.group(1)
return None return None

View File

@ -7,7 +7,8 @@ import sys
from appdirs import AppDirs from appdirs import AppDirs
from pkg_resources import resource_filename from pkg_resources import resource_filename
from ansiblelater import classify from ansiblelater import classify
from ansiblelater.utils import info, warn, read_config, get_property from ansiblelater import config
from ansiblelater.utils import info, warn, get_property
def main(): def main():
@ -28,52 +29,54 @@ def main():
const=logging.INFO, help="Show more verbose output") const=logging.INFO, help="Show more verbose output")
options, args = parser.parse_args(sys.argv[1:]) options, args = parser.parse_args(sys.argv[1:])
settings = read_config(options.configfile)
# Merge CLI options with config options. CLI options override config options. print(config.rulesdir)
for key, value in options.__dict__.items(): # settings = read_config(options.configfile)
if value:
setattr(settings, key, value)
if os.path.exists(settings.configfile): # # Merge CLI options with config options. CLI options override config options.
info("Using configuration file: %s" % settings.configfile, settings) # for key, value in options.__dict__.items():
else: # if value:
warn("No configuration file found at %s" % settings.configfile, settings, file=sys.stderr) # setattr(settings, key, value)
if not settings.rulesdir:
rules_dir = os.path.join(resource_filename('ansiblelater', 'examples'))
warn("Using example standards found at %s" % rules_dir, settings, file=sys.stderr)
settings.rulesdir = rules_dir
if len(args) == 0: # if os.path.exists(settings.configfile):
candidates = [] # info("Using configuration file: %s" % settings.configfile, settings)
for root, dirs, files in os.walk("."): # else:
for filename in files: # warn("No configuration file found at %s" % settings.configfile, settings, file=sys.stderr)
candidates.append(os.path.join(root, filename)) # if not settings.rulesdir:
else: # rules_dir = os.path.join(resource_filename('ansiblelater', 'examples'))
candidates = args # warn("Using example standards found at %s" % rules_dir, settings, file=sys.stderr)
# settings.rulesdir = rules_dir
errors = 0 # if len(args) == 0:
for filename in candidates: # candidates = []
if ':' in filename: # for root, dirs, files in os.walk("."):
(filename, lines) = filename.split(":") # for filename in files:
else: # candidates.append(os.path.join(root, filename))
lines = None # else:
candidate = classify(filename) # candidates = args
if candidate:
if candidate.binary: # errors = 0
info("Not reviewing binary file %s" % filename, settings) # for filename in candidates:
continue # if ":" in filename:
if candidate.vault: # (filename, lines) = filename.split(":")
info("Not reviewing vault file %s" % filename, settings) # else:
continue # lines = None
if lines: # candidate = classify(filename)
info("Reviewing %s lines %s" % (candidate, lines), settings) # if candidate:
else: # if candidate.binary:
info("Reviewing all of %s" % candidate, settings) # info("Not reviewing binary file %s" % filename, settings)
errors = errors + candidate.review(settings, lines) # continue
else: # if candidate.vault:
info("Couldn't classify file %s" % filename, settings) # info("Not reviewing vault file %s" % filename, settings)
return errors # continue
# if lines:
# info("Reviewing %s lines %s" % (candidate, lines), settings)
# else:
# info("Reviewing all of %s" % candidate, settings)
# errors = errors + candidate.review(settings, lines)
# else:
# info("Couldn't classify file %s" % filename, settings)
# return errors
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -11,6 +11,7 @@ from ansiblelater.utils.rulehelper import run_yamllint
def check_yaml_has_content(candidate, settings): def check_yaml_has_content(candidate, settings):
Testvar = "test"
lines, errors = get_normalized_yaml(candidate, settings) lines, errors = get_normalized_yaml(candidate, settings)
description = "the file appears to have no useful content" description = "the file appears to have no useful content"

20
ansiblelater/settings.py Normal file
View File

@ -0,0 +1,20 @@
try:
import ConfigParser as configparser
except ImportError:
import configparser
class Settings(object):
def __init__(self, config=configparser.ConfigParser(), config_file="asas"):
self.rulesdir = "ahjhsjahsjas"
self.custom_modules = []
self.log_level = None
self.standards_filter = None
if config.has_section('rules'):
self.rulesdir = config.get('rules', 'standards')
if config.has_section('ansible'):
modules = config.get('ansible', 'custom_modules')
self.custom_modules = [x.strip() for x in modules.split(',')]
self.configfile = config_file

View File

@ -112,18 +112,3 @@ def read_config(config_file):
return Settings(config, config_file) return Settings(config, config_file)
class Settings(object):
def __init__(self, config, config_file):
self.rulesdir = None
self.custom_modules = []
self.log_level = None
self.standards_filter = None
if config.has_section('rules'):
self.rulesdir = config.get('rules', 'standards')
if config.has_section('ansible'):
modules = config.get('ansible', 'custom_modules')
self.custom_modules = [x.strip() for x in modules.split(',')]
self.configfile = config_file

View File

@ -8,8 +8,18 @@ universal = 1
[flake8] [flake8]
ignore = E501, W503, F401, N813 ignore = E501, W503, F401, N813
max-line-length = 100 max-line-length = 100
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
application-import-names = ansiblelater
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
[isort]
default_section = THIRDPARTY
known_first_party = ansiblelater
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
[tool:pytest] [tool:pytest]
filterwarnings = filterwarnings =
ignore::FutureWarning
ignore:.*collections.*:DeprecationWarning ignore:.*collections.*:DeprecationWarning
ignore:.*pep8.*:FutureWarning

View File

@ -1,5 +1,13 @@
flake8 flake8
flake8-colors flake8-colors
flake8-blind-except
flake8-builtins
flake8-colors
flake8-docstrings
flake8-isort
flake8-logging-format
flake8-polyfill
flake8-quotes
pep8-naming pep8-naming
wheel wheel
pytest pytest