summaryrefslogtreecommitdiffstats
path: root/hacking/build_library/build_ansible/command_plugins/file_deprecated_issues.py
blob: 139ecc4d9474cb7a5e0d9e6080406fafdcca01f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# -*- coding: utf-8 -*-
# (c) 2017, Matt Martz <matt@sivel.net>
# (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