def check(user_configuration, repository_configuration, staging_area): """ Codevalidator check :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param staging_area: :type staging_area: git_hooks.models.staging.StagingArea :return: If check passed or not :rtype: turnstile.checks.CheckResult """ result = checks.CheckResult() logger = output.get_sub_logger('pre-commit', 'codevalidator') logger.debug('Starting Codevalidator check...') if not staging_area.changes: logger.debug('No files to check.') raise checks.CheckIgnore codevalidator_rc = staging_area.working_dir / '.codevalidatorrc' with staging_area: codevalidator_output = codevalidator(files_to_check=staging_area.files, temporary_dir=staging_area.temporary_directory, custom_config=codevalidator_rc) result.successful = not codevalidator_output if not result.successful: result.details.append(codevalidator_output) return result
def run_checks( hook_name, user_configuration, repository_configuration, check_object, ): """ Runs checks for hooks :param hook_name: Which hook is running the checks :type hook_name: str :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param check_object: Object to check, either a CommitMessage or a StagingArea :return: Number of failed checks :rtype: int """ logger = output.get_sub_logger(hook_name.replace('_', '-'), 'run_checks') failed_checks = 0 checklist = repository_configuration.get('checks', []) checklist = [x.replace('-', '_') for x in checklist] logger.debug('Configured Checks: %s', checklist) check_functions = list(get_checks(hook_name)) logger.debug('Available Checks: %s', [f[0] for f in check_functions]) checks_to_run = (check for check_name, check in check_functions if check_name in checklist) for check in checks_to_run: try: result = check(user_configuration, repository_configuration, check_object) except CheckIgnore: logger.debug('Check was ignored') continue if result.successful: logger.info('✔ %s', check.description) for detail in result.details: logger.info(' %s', detail) else: failed_checks += 1 logger.error('✘ %s', check.description) for detail in result.details: logger.error(' %s', detail) return failed_checks
def remove_hook(name, path): """ Remove a hook from path :type name: str :type path: Path """ logger = output.get_sub_logger('manager-remove', 'remove_hook') logger.debug('Removing %s hook.', name) if not path.exists(): logger.debug('%s Hook doesn\'t exist.', name) elif click.confirm('Are you sure you want to remove {} hook?'.format(name)): path.unlink() logger.info('Removed %s hook', name) else: logger.info('Kept %s hook.', name)
def install_hook(name, path, wrapper_command): """ Installs a hook in path :type name: str :type path: Path :type wrapper_command: str """ logger = output.get_sub_logger('manager-install', 'install_hook') logger.debug('Installing %s hook.', name) if not path.exists() or click.confirm('{} hook already exists. Do you want to overwrite it?'.format(name)): with path.open('wb+') as pre_commit_hook: pre_commit_hook.write(wrapper_command.encode('utf-8')) path.chmod(0o755) # -rwxr-xr-x logger.info('Installed %s hook', name) else: logger.info('Skipped %s hook installation.', name)
def get_checks(hook_name): """ Load all the checks for the hook :param hook_name: Which hook is fetching the checks :type hook_name: str :rtype: [str, FunctionType] """ logger = output.get_sub_logger(hook_name.replace('_', '-'), 'get_checks') group_name = 'turnstile.{}'.format(hook_name) for entry_point in pkg_resources.iter_entry_points(group_name): try: module = entry_point.load() except ImportError: # pragma: no cover logger.error('%s not found', entry_point.name) continue check = module.check # type: FunctionType yield entry_point.name, check
def check(user_configuration, repository_configuration, commit_message): """ Check if the specification is valid. :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'specification') logger.debug('Starting specification check...') logger.debug('Commit Message: %s', commit_message.message) if commit_message.message.startswith('Merge'): logger.debug("Commit is a merge, ignoring.") raise checks.CheckIgnore check_options = repository_configuration.get('specification', {}) allowed_schemes = check_options.get('allowed_schemes', ['https', 'offline']) allowed_formats = check_options.get('allowed_formats', {'uri'}) logger.debug("Allowed schemes: %s", allowed_schemes) result = checks.CheckResult() specification = specifications.get_specification(commit_message.message, allowed_formats, allowed_schemes) is_valid_uri = specification.valid logger.debug('Specification: %s', specification) logger.debug("Specification is valid: %s", is_valid_uri) result.successful = is_valid_uri if not is_valid_uri: result.add_detail( '{spec} is not a valid specification.'.format(spec=specification)) return result
def run_checks(hook_name, user_configuration, repository_configuration, check_object, ): """ Runs checks for hooks :param hook_name: Which hook is running the checks :type hook_name: str :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param check_object: Object to check, either a CommitMessage or a StagingArea :return: Number of failed checks :rtype: int """ logger = output.get_sub_logger(hook_name.replace('_', '-'), 'run_checks') failed_checks = 0 checklist = repository_configuration.get('checks', []) checklist = [x.replace('-', '_') for x in checklist] logger.debug('Configured Checks: %s', checklist) check_functions = list(get_checks(hook_name)) logger.debug('Available Checks: %s', [f[0] for f in check_functions]) checks_to_run = (check for check_name, check in check_functions if check_name in checklist) for check in checks_to_run: try: result = check(user_configuration, repository_configuration, check_object) except CheckIgnore: logger.debug('Check was ignored') continue if result.successful: logger.info('✔ %s', check.description) for detail in result.details: logger.info(' %s', detail) else: failed_checks += 1 logger.error('✘ %s', check.description) for detail in result.details: logger.error(' %s', detail) return failed_checks
def check(user_configuration, repository_configuration, commit_message): """ Check if the specification is valid. :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger("commit-msg", "specification") logger.debug("Starting specification check...") logger.debug("Commit Message: %s", commit_message.message) if commit_message.message.startswith("Merge"): logger.debug("Commit is a merge, ignoring.") raise checks.CheckIgnore check_options = repository_configuration.get("specification", {}) allowed_schemes = check_options.get("allowed_schemes", ["https", "offline"]) allowed_formats = check_options.get("allowed_formats", {"uri"}) logger.debug("Allowed schemes: %s", allowed_schemes) result = checks.CheckResult() specification = specifications.get_specification(commit_message.message, allowed_formats, allowed_schemes) is_valid_uri = specification.valid logger.debug("Specification: %s", specification) logger.debug("Specification is valid: %s", is_valid_uri) result.successful = is_valid_uri if not is_valid_uri: result.add_detail("{spec} is not a valid specification.".format(spec=specification)) return result
def codevalidator(files_to_check, temporary_dir=None, custom_config=None, fix=False): """ Wrapper around codevalidator :param files_to_check: list of files to check :type files_to_check: list :param temporary_dir: temporary dir used, this is used to remove it from the output :param custom_config: path to custom codevalidatorrc if any :type custom_config: pathlib.Path :param fix: whether to try to fix the files or not :type fix: bool :return: codevalidator output """ logger = output.get_sub_logger('pre-commit', 'codevalidator') arguments = ['codevalidator', '-v'] arguments.extend(str(path) for path in files_to_check) if custom_config.is_file(): arguments.extend(['-c', str(custom_config.resolve())]) if fix: arguments.extend(['-f', '--no-backup']) logger.debug('Command Arguments: %s', arguments) process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) stdout, stderr = process.communicate() logger.debug('Standard Output: %s', stdout) logger.debug('Standard Error: %s', stderr) codevalidator_output = stdout + stderr if temporary_dir: codevalidator_output = remove_temporary_path(codevalidator_output, temporary_dir) return codevalidator_output
def check(user_configuration, repository_configuration, commit_message): """ Prevents commits to master >>> import turnstile.models.message as message >>> commit = message.CommitMessage('master', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, None, commit) >>> result.successful False >>> result.details ['Master branch is protected.'] :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'protect-master') logger.debug('Starting protect-master check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) is_allowed = branch != 'master' result.successful = is_allowed if not is_allowed: result.add_detail("Master branch is protected.") return result
def check(user_configuration, repository_configuration, commit_message): """ Prevents commits to master >>> import turnstile.models.message as message >>> commit = message.CommitMessage('master', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, None, commit) >>> result.successful False >>> result.details ['Master branch is protected.'] :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger("commit-msg", "protect-master") logger.debug("Starting protect-master check...") result = checks.CheckResult() branch = commit_message.branch logger.debug("Branch: %s", branch) is_allowed = branch != "master" result.successful = is_allowed if not is_allowed: result.add_detail("Master branch is protected.") return result
def check(user_configuration, repository_configuration, commit_message): """ Check if the release of release branches matches a pattern. By default this pattern is ^R(?:\d|\_|\.)+$ but it's configurable :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'branch-release') logger.debug('Starting branch-release check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) if not branch.startswith('release/'): logger.debug("%s isn't a release branch, ignoring.", branch) raise checks.CheckIgnore check_options = repository_configuration.get('branch-release', {}) pattern = check_options.get('pattern', '^R(?:\d|\_|\.)+$') branch_type, release = branch.split('/', 1) matches_pattern = bool(re.match(pattern, release)) result.successful = matches_pattern if not matches_pattern: template = "'{release}' doesn't match '{pattern}'." result.add_detail(template.format(release=release, pattern=pattern)) return result
def check(user_configuration, repository_configuration, commit_message): """ Check if the branch type is allowed. The branch type is the prefix of the branch name, for example feature/CD-100 is a feature branch. By default only master is allowed >>> import turnstile.models.message as message >>> commit_1 = message.CommitMessage('master', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result_1 = check(None, {}, commit_1) >>> result_1.successful, result_1.details (True, []) >>> commit_2 = message.CommitMessage('feature/ABC', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result_2 = check(None, {}, commit_2) >>> result_2.successful, result_2.details (False, ["'feature' type is not allowed. Allowed types are: master."]) But you can configure it >>> allow_feature_release = {'branch-type': {'allowed': ['feature', 'release']}} >>> commit_3 = message.CommitMessage('feature/ABC', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result_3 = check(None, allow_feature_release, commit_3) >>> result_3.successful, result_3.details (True, []) >>> allow_feature_release = {'branch-type': {'allowed': ['feature', 'release']}} >>> commit_4 = message.CommitMessage('other/ABC', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result_4 = check(None, allow_feature_release, commit_4) >>> result_4.successful, result_4.details (False, ["'other' type is not allowed. Allowed types are: feature, release, master."]) :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'branch-type') logger.debug('Starting branch-type check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) check_options = repository_configuration.get('branch-type', {}) allowed = check_options.get('allowed', []) logger.debug('Allowed Patterns: %s', allowed) # add a / after type name because branches should be TYPE/* and master is always allowed is_allowed = branch == 'master' or any( branch.startswith(branch_type + '/') for branch_type in allowed) result.successful = is_allowed if not is_allowed: branch_type = branch.split('/').pop(0) allowed.append('master') # make it clear it can also be master template = "'{branch_type}' type is not allowed. Allowed types are: {allowed}." result.add_detail( template.format(branch_type=branch_type, allowed=', '.join(allowed))) return result
def check(user_configuration, repository_configuration, commit_message): """ Check if the branch name matches the allowed pattern. Master is always allowed By default this check only allows master >>> import turnstile.models.message as message >>> commit = message.CommitMessage('master', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, {}, commit) >>> result.successful, result.details (True, []) >>> commit = message.CommitMessage('feature/42', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, {}, commit) >>> result.successful, result.details (False, ["feature/42 doesn't match any allowed pattern."]) But you can add more allowed patterns >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/R10', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (True, []) >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/R10', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (True, []) >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/broken', 'https://github.com/jmcs/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (False, ["release/broken doesn't match any allowed pattern."]) :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'branch-pattern') logger.debug('Starting branch-pattern check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) check_options = repository_configuration.get('branch-pattern', {}) allowed = check_options.get('allowed', []) allowed.append('master') # master is always allowed logger.debug('Allowed Patterns: %s', allowed) is_allowed = any(re.match(pattern, branch) for pattern in allowed) result.successful = is_allowed if not is_allowed: template = "{branch} doesn't match any allowed pattern." result.add_detail(template.format(branch=branch)) return result
def check(user_configuration, repository_configuration, commit_message): """ Check if the branch type is allowed. The branch type is the prefix of the branch name, for example feature/CD-100 is a feature branch. By default only master is allowed >>> import turnstile.models.message as message >>> commit_1 = message.CommitMessage('master', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result_1 = check(None, {}, commit_1) >>> result_1.successful, result_1.details (True, []) >>> commit_2 = message.CommitMessage('feature/ABC', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result_2 = check(None, {}, commit_2) >>> result_2.successful, result_2.details (False, ["'feature' type is not allowed. Allowed types are: master."]) But you can configure it >>> allow_feature_release = {'branch-type': {'allowed': ['feature', 'release']}} >>> commit_3 = message.CommitMessage('feature/ABC', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result_3 = check(None, allow_feature_release, commit_3) >>> result_3.successful, result_3.details (True, []) >>> allow_feature_release = {'branch-type': {'allowed': ['feature', 'release']}} >>> commit_4 = message.CommitMessage('other/ABC', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result_4 = check(None, allow_feature_release, commit_4) >>> result_4.successful, result_4.details (False, ["'other' type is not allowed. Allowed types are: feature, release, master."]) :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'branch-type') logger.debug('Starting branch-type check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) check_options = repository_configuration.get('branch-type', {}) allowed = check_options.get('allowed', []) logger.debug('Allowed Patterns: %s', allowed) # add a / after type name because branches should be TYPE/* and master is always allowed is_allowed = branch == 'master' or any(branch.startswith(branch_type + '/') for branch_type in allowed) result.successful = is_allowed if not is_allowed: branch_type = branch.split('/').pop(0) allowed.append('master') # make it clear it can also be master template = "'{branch_type}' type is not allowed. Allowed types are: {allowed}." result.add_detail(template.format(branch_type=branch_type, allowed=', '.join(allowed))) return result
def check(user_configuration, repository_configuration, commit_message): """ Check if the branch name matches the allowed pattern. Master is always allowed By default this check only allows master >>> import turnstile.models.message as message >>> commit = message.CommitMessage('master', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, {}, commit) >>> result.successful, result.details (True, []) >>> commit = message.CommitMessage('feature/42', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, {}, commit) >>> result.successful, result.details (False, ["feature/42 doesn't match any allowed pattern."]) But you can add more allowed patterns >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/R10', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (True, []) >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/R10', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (True, []) >>> allow_feature_release = {'branch-pattern': {'allowed': ['^feature/', '^release/R']}} >>> commit = message.CommitMessage('release/broken', 'https://github.com/zalando-bus/turnstile/issues/42 message') >>> result = check(None, allow_feature_release, commit) >>> result.successful, result.details (False, ["release/broken doesn't match any allowed pattern."]) :param user_configuration: User specific configuration :type user_configuration: git_hooks.common.config.UserConfiguration :param repository_configuration: Repository specific configuration :type repository_configuration: dict :param commit_message: :type commit_message: git_hooks.models.message.CommitMessage :return: If check passed or not :rtype: git_hooks.checks.CheckResult """ logger = output.get_sub_logger('commit-msg', 'branch-pattern') logger.debug('Starting branch-pattern check...') result = checks.CheckResult() branch = commit_message.branch logger.debug('Branch: %s', branch) check_options = repository_configuration.get('branch-pattern', {}) allowed = check_options.get('allowed', []) allowed.append('master') # master is always allowed logger.debug('Allowed Patterns: %s', allowed) is_allowed = any(re.match(pattern, branch) for pattern in allowed) result.successful = is_allowed if not is_allowed: template = "{branch} doesn't match any allowed pattern." result.add_detail(template.format(branch=branch)) return result