1# Copyright (c) 2019 Red Hat, Inc. 2# 3# Permission is hereby granted, free of charge, to any person obtaining a copy 4# of this software and associated documentation files (the "Software"), to 5# deal in the Software without restriction, including without limitation the 6# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7# sell copies of the Software, and to permit persons to whom the Software is 8# furnished to do so, subject to the following conditions: 9# 10# The above copyright notice and this permission notice shall be included in 11# all copies or substantial portions of the Software. 12# 13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19# DEALINGS IN THE SOFTWARE. 20"""Linter module for pre-commit related code.""" 21 22import os 23 24import sh 25 26from molecule import logger 27from molecule import util 28from molecule.verifier.lint import base 29 30LOG = logger.get_logger(__name__) 31 32 33class PreCommit(base.Base): 34 """ 35 Pre-commit tool verifier wrapper. 36 37 This class is used to lint files by executing the pre-commit 38 command line tool for files in the test folder with a prefix 39 of ``test_``. 40 41 `Pre-Commit`_ is not the default verifier linter. 42 43 `Pre-Commit`_ is a linter for python files and more. 44 45 Additional options can be passed to ``pre-commit`` through the options 46 dict. Any option set in this section will override the defaults. 47 48 .. code-block:: yaml 49 50 verifier: 51 name: testinfra 52 lint: 53 name: pre-commit 54 options: 55 remove-tabs: 56 57 Test file linting can be disabled by setting ``enabled`` to False. 58 59 .. code-block:: yaml 60 61 verifier: 62 name: testinfra 63 lint: 64 name: pre-commit 65 enabled: False 66 67 Environment variables can be passed to lint. 68 69 .. code-block:: yaml 70 71 verifier: 72 name: testinfra 73 lint: 74 name: pre-commit 75 env: 76 FOO: bar 77 78 Example pre-commit configuration file (``.pre-commit-config.yaml``) to run 79 flake8. 80 81 .. code-block:: yaml 82 83 repos: 84 - repo: local 85 hooks: 86 - id: flake8 87 name: flake8 88 entry: python -m flake8 --max-line-length=120 89 language: system 90 types: [python] 91 92 .. _`Pre-Commit`: https://pre-commit.com/ 93 """ 94 95 def __init__(self, config): 96 """ 97 Set up the requirements to execute ``pre-commit`` tool. 98 99 :param config: An instance of a Molecule config. 100 """ 101 super(PreCommit, self).__init__(config) 102 self._precommit_command = None 103 if config: 104 self._tests = self._get_tests() 105 106 @property 107 def default_options(self): 108 """Default options for pre-commit tool runtime.""" 109 return {} 110 111 @property 112 def default_env(self): 113 """Default environment variables for pre-commit tool runtime.""" 114 return util.merge_dicts(os.environ.copy(), self._config.env) 115 116 def bake(self): 117 """Bake a ready to execute ``pre-commit`` command.""" 118 self._precommit_command = sh.Command('pre-commit').bake( 119 'run', 120 self.options, 121 '--files', 122 self._tests, 123 _env=self.env, 124 _out=LOG.out, 125 _err=LOG.error, 126 ) 127 128 def execute(self): 129 """Execute the pre-commit command.""" 130 if not self.enabled: 131 msg = 'Skipping, verifier_lint is disabled.' 132 LOG.warn(msg) 133 return 134 135 if not self._tests: 136 msg = 'Skipping, no tests found.' 137 LOG.warn(msg) 138 return 139 140 if self._precommit_command is None: 141 self.bake() 142 143 msg = 'Executing pre-commit on files found in {}/...'.format( 144 self._config.verifier.directory 145 ) 146 LOG.info(msg) 147 148 try: 149 util.run_command(self._precommit_command, debug=self._config.debug) 150 msg = 'Lint completed successfully.' 151 LOG.success(msg) 152 except sh.ErrorReturnCode as e: 153 util.sysexit(e.exit_code) 154 155 def _get_tests(self): 156 """ 157 Get a list of test files from the verifier's directory. 158 159 :return: list 160 """ 161 return list(util.os_walk(self._config.verifier.directory, 'test_*.py')) 162