From b437a19f6d8f26b07a64b5002bbea8f0eb992a51 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 9 Sep 2019 17:28:52 -0700 Subject: [PATCH] create-deprecated-issues script can now add to a specified project (#61901) * create-deprecated-issues script can now add to a specified project * Migrate the create deprecated issues script into a subcommand of build-ansible * Remove deprecated-issue script from ignore list --- hacking/build-ansible.py | 6 +- .../command_plugins/file_deprecated_issues.py | 153 ++++++++++++++++++ hacking/build_library/build_ansible/errors.py | 11 +- hacking/create_deprecated_issues.py | 105 ------------ test/sanity/ignore.txt | 2 - 5 files changed, 167 insertions(+), 110 deletions(-) create mode 100644 hacking/build_library/build_ansible/command_plugins/file_deprecated_issues.py delete mode 100644 hacking/create_deprecated_issues.py diff --git a/hacking/build-ansible.py b/hacking/build-ansible.py index 8043e947813..8b151d9d23c 100755 --- a/hacking/build-ansible.py +++ b/hacking/build-ansible.py @@ -54,6 +54,8 @@ def main(): subcommands = load('build_ansible.command_plugins', subclasses=commands.Command) arg_parser = create_arg_parser(os.path.basename(sys.argv[0])) + arg_parser.add_argument('--debug', dest='debug', required=False, default=False, action='store_true', + help='Show tracebacks and other debugging information') subparsers = arg_parser.add_subparsers(title='Subcommands', dest='command', help='for help use build-ansible.py SUBCOMMANDS -h') subcommands.pipe('init_parser', subparsers.add_parser) @@ -77,8 +79,10 @@ def main(): try: retval = command.main(args) - except errors.DependencyError as e: + except (errors.DependencyError, errors.MissingUserInput, errors.InvalidUserInput) as e: print(e) + if args.debug: + raise sys.exit(2) sys.exit(retval) diff --git a/hacking/build_library/build_ansible/command_plugins/file_deprecated_issues.py b/hacking/build_library/build_ansible/command_plugins/file_deprecated_issues.py new file mode 100644 index 00000000000..139ecc4d947 --- /dev/null +++ b/hacking/build_library/build_ansible/command_plugins/file_deprecated_issues.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +# (c) 2017, Matt Martz +# (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +import argparse +import os +import time + +from collections import defaultdict + +from ansible.release import __version__ as ansible_version + +# Pylint doesn't understand Python3 namespace modules. +from ..commands import Command # pylint: disable=relative-beyond-top-level +from .. import errors # pylint: disable=relative-beyond-top-level + +ANSIBLE_MAJOR_VERSION = '.'.join(ansible_version.split('.')[:2]) + + +def get_token(token_file): + if token_file: + return token_file.read().strip() + + token = os.getenv('GITHUB_TOKEN').strip() + if not token: + raise errors.MissingUserInput( + 'Please provide a file containing a github oauth token with public_repo scope' + ' via the --github-token argument or set the GITHUB_TOKEN env var with your' + ' github oauth token' + ) + return token + + +def parse_deprecations(problems_file_handle): + deprecated = defaultdict(list) + deprecation_errors = problems_file_handle.read() + for line in deprecation_errors.splitlines(): + path = line.split(':')[0] + if path.endswith('__init__.py'): + component = os.path.basename(os.path.dirname(path)) + else: + component, dummy = os.path.splitext(os.path.basename(path).lstrip('_')) + + title = ( + '%s contains deprecated call to be removed in %s' % + (component, ANSIBLE_MAJOR_VERSION) + ) + deprecated[component].append( + dict(title=title, path=path, line=line) + ) + return deprecated + + +def find_project_todo_column(repo, project_name): + project = None + for project in repo.projects(): + if project.name.lower() == project_name: + break + else: + raise errors.InvalidUserInput('%s was an invalid project name' % project_name) + + for project_column in project.columns(): + column_name = project_column.name.lower() + if 'todo' in column_name or 'backlog' in column_name or 'to do' in column_name: + return project_column + + raise Exception('Unable to determine the todo column in' + ' project %s' % project_name) + + +def create_issues(deprecated, body_tmpl, repo): + issues = [] + + for component, items in deprecated.items(): + title = items[0]['title'] + path = '\n'.join(set((i['path']) for i in items)) + line = '\n'.join(i['line'] for i in items) + body = body_tmpl % dict(component=component, path=path, + line=line, + version=ANSIBLE_MAJOR_VERSION) + + issue = repo.create_issue(title, body=body, labels=['deprecated']) + print(issue) + issues.append(issue) + + # Sleep a little, so that the API doesn't block us + time.sleep(0.5) + + return issues + + +class FileDeprecationTickets(Command): + name = 'file-deprecation-tickets' + + @classmethod + def init_parser(cls, add_parser): + parser = add_parser(cls.name, description='File tickets to cleanup deprecated features for' + ' the next release') + parser.add_argument('--template', default='deprecated_issue_template.md', + type=argparse.FileType('r'), + help='Path to markdown file template to be used for issue ' + 'body. Default: %(default)s') + parser.add_argument('--project-name', default='', type=str, + help='Name of a github project to assign all issues to') + parser.add_argument('--github-token', type=argparse.FileType('r'), + help='Path to file containing a github token with public_repo scope.' + ' This token in this file will be used to open the deprcation' + ' tickets and add them to the github project. If not given,' + ' the GITHUB_TOKEN environment variable will be tried') + parser.add_argument('problems', type=argparse.FileType('r'), + help='Path to file containing pylint output for the ' + 'ansible-deprecated-version check') + + @staticmethod + def main(args): + try: + from github3 import GitHub + except ImportError: + raise errors.DependencyError( + 'This command needs the github3.py library installed to work' + ) + + token = get_token(args.github_token) + args.github_token.close() + + deprecated = parse_deprecations(args.problems) + args.problems.close() + + body_tmpl = args.template.read() + args.template.close() + + project_name = args.project_name.strip().lower() + + gh_conn = GitHub(token=token) + repo = gh_conn.repository('abadger', 'ansible') + + if project_name: + project_column = find_project_todo_column(repo, project_name) + + issues = create_issues(deprecated, body_tmpl, repo) + + if project_column: + for issue in issues: + project_column.create_card_with_issue(issue) + time.sleep(0.5) + + return 0 diff --git a/hacking/build_library/build_ansible/errors.py b/hacking/build_library/build_ansible/errors.py index b1c8df5fb95..a53d1fb1c8e 100644 --- a/hacking/build_library/build_ansible/errors.py +++ b/hacking/build_library/build_ansible/errors.py @@ -8,5 +8,12 @@ __metaclass__ = type class DependencyError(Exception): - """Used when a dependency is unmet""" - pass + """A dependency was unmet""" + + +class MissingUserInput(Exception): + """The user failed to provide input (via cli arg or interactively""" + + +class InvalidUserInput(Exception): + """The user provided invalid input""" diff --git a/hacking/create_deprecated_issues.py b/hacking/create_deprecated_issues.py deleted file mode 100644 index 9a6022988bd..00000000000 --- a/hacking/create_deprecated_issues.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# (c) 2017, Matt Martz -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -import argparse -import os -import time - -from collections import defaultdict - -from ansible.release import __version__ as ansible_version - -ansible_major_version = '.'.join(ansible_version.split('.')[:2]) - -try: - from github3 import GitHub -except ImportError: - raise SystemExit( - 'This script needs the github3.py library installed to work' - ) - -if not os.getenv('GITHUB_TOKEN'): - raise SystemExit( - 'Please set the GITHUB_TOKEN env var with your github oauth token' - ) - -deprecated = defaultdict(list) - - -parser = argparse.ArgumentParser() -parser.add_argument('--template', default='deprecated_issue_template.md', - type=argparse.FileType('r'), - help='Path to markdown file template to be used for issue ' - 'body. Default: %(default)s') -parser.add_argument('problems', nargs=1, type=argparse.FileType('r'), - help='Path to file containing pylint output for the ' - 'ansible-deprecated-version check') -args = parser.parse_args() - - -body_tmpl = args.template.read() -args.template.close() - -text = args.problems[0].read() -args.problems[0].close() - - -for line in text.splitlines(): - path = line.split(':')[0] - if path.endswith('__init__.py'): - component = os.path.basename(os.path.dirname(path)) - else: - component, ext_ = os.path.splitext(os.path.basename(path).lstrip('_')) - - title = ( - '%s contains deprecated call to be removed in %s' % - (component, ansible_major_version) - ) - deprecated[component].append( - dict(title=title, path=path, line=line) - ) - - -g = GitHub(token=os.getenv('GITHUB_TOKEN')) -repo = g.repository('ansible', 'ansible') - -# Not enabled by default, this fetches the column of a project, -# so that we can later add the issue to a project column -# You will need the project and column IDs for this to work -# and then update the below lines -# project = repo.project(2141803) -# project_column = project.column(4348504) - -for component, items in deprecated.items(): - title = items[0]['title'] - path = '\n'.join(set((i['path']) for i in items)) - line = '\n'.join(i['line'] for i in items) - body = body_tmpl % dict(component=component, path=path, - line=line, - version=ansible_major_version) - - issue = repo.create_issue(title, body=body, labels=['deprecated']) - print(issue) - # Sleep a little, so that the API doesn't block us - time.sleep(0.5) - # Uncomment the next 2 lines if you want to add issues to a project - # Needs to be done in combination with the above code for selecting - # the project/column - # project_column.create_card_with_issue(issue) - # time.sleep(0.5) diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 517c74efb51..0b3466d69b9 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -133,8 +133,6 @@ hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-3.5 hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-2.6!skip # release process only, 3.6+ required hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-2.7!skip # release process only, 3.6+ required hacking/build_library/build_ansible/command_plugins/release_announcement.py compile-3.5!skip # release process only, 3.6+ required -hacking/create_deprecated_issues.py future-import-boilerplate -hacking/create_deprecated_issues.py metaclass-boilerplate hacking/fix_test_syntax.py future-import-boilerplate hacking/fix_test_syntax.py metaclass-boilerplate hacking/get_library.py future-import-boilerplate