-
Notifications
You must be signed in to change notification settings - Fork 0
/
jira-migration.py
232 lines (204 loc) · 7.98 KB
/
jira-migration.py
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import utils.ghutils as ghutils
import utils.jirautils as jirautils
import utils.migrationutils as migrationutils
import json
from pprint import pprint
import argparse
try:
config_file = open('config.json')
config_json = json.load(config_file)
config_file.close()
except:
print('* Error: config.json not found. Please populate the configuration file before continuing.')
exit(1)
user_map_json = None
try:
user_map_file = open('user_map.json')
user_map_json = json.load(user_map_file)
user_map_file.close()
except:
print('* Warning: user_map.json not found. This may be ignored if user_map is supplied in config.json or isn\'t used.')
user_map = {}
component_map = {}
if config_json:
if 'component_map' in config_json:
component_map = config_json['component_map']
if user_map_json:
user_map = user_map_json
elif 'user_map' in config_json:
user_map = config_json['user_map']
if 'default_jira_user' in config_json:
default_user = config_json['default_jira_user']
else:
print('Error finding default Jira user. This is required for creating Jira issues.')
exit(1)
else:
print('Error loading config.json.')
exit(1)
label_filter = ''
label_exclusions = ''
completion_label = ''
squad_completion_label = ''
component_name = ''
# Parse config file
if 'label_filter' in config_json:
label_filter = config_json['label_filter']
if 'label_exclusions' in config_json:
label_exclusions = config_json['label_exclusions']
if 'completion_label' in config_json:
completion_label = config_json['completion_label']
if 'squad_completion_label' in config_json:
squad_completion_label = config_json['squad_completion_label']
if 'component_name' in config_json:
component_name = config_json['component_name']
# Parse CLI arguments (these override the config file)
description = 'Utility to migrate issues from GitHub to Jira'
parser = argparse.ArgumentParser(description=description)
parser.add_argument(
'-l', '--label-filter',
help='Filter issues by GitHub label (comma separated list)')
parser.add_argument(
'-e', '--label-exclusions',
help='Exclude issues by GitHub label (comma separated list)')
parser.add_argument(
'-c', '--completion-label',
help='Label to filter/add for issues that have been migrated')
parser.add_argument(
'-s', '--squad-completion-label',
help='Label to filter/add for issues that have been migrated for non-closeable issues')
parser.add_argument(
'-m', '--component-name',
help='Name of the squad or component for messages')
parser.add_argument(
'-v', '--verbose',
default=False, action='store_true',
help='Print additional logs for debugging')
parser.add_argument(
'--dry-run',
default=False, action='store_true',
help='Only run get operations and don\'t update/create issues')
args = parser.parse_args()
if args.label_filter:
label_filter = args.label_filter
if args.label_exclusions:
label_exclusions = args.label_exclusions
if args.completion_label:
completion_label = args.completion_label
if args.squad_completion_label:
squad_completion_label = args.squad_completion_label
if args.component_name:
component_name = args.component_name
# Collect GitHub issues using query config or CLI
label_exclusions = f'{completion_label},{squad_completion_label},{label_exclusions}'
gh_issues = ghutils.get_issues_by_label(label_filter, label_exclusions)
jira_mappings = []
if len(gh_issues) == 0:
print('* No issues were returned from GitHub:')
print(f' Label filter: {label_filter}')
print(f' Label exclusions: {label_exclusions}')
# Iterate over GitHub issues and collect mapping objects
for gh_issue in gh_issues:
gh_url = gh_issue['html_url']
print(f'* Creating Jira mapping for {gh_url} ({gh_issue["title"]})')
jira_issue_input, can_close = migrationutils.issue_map(
gh_issue, component_map, user_map, default_user)
# Collect comments from the GitHub issue
gh_comments = ghutils.get_issue_comments(gh_issue)
jira_comment_input = []
for comment in gh_comments:
jira_comment_input.append(
migrationutils.comment_map(comment))
# Store issue mapping objects
mapping_obj = {
'gh_issue_number': gh_issue['number'],
'issue': jira_issue_input,
'comments': jira_comment_input,
'close_gh_issue': can_close
}
jira_mappings.append(mapping_obj)
if args.verbose:
pprint(mapping_obj)
# Iterate over Jira mappings to create issues with comments
issue_failures = []
duplicate_issues = {}
for jira_map in jira_mappings:
gh_issue_url = jira_map['issue'][jirautils.gh_issue_field]
gh_issue_title = jira_map['issue']['summary']
print(
'* Checking for issues already linked to GitHub issue ' +
f'{gh_issue_url} ({gh_issue_title})')
custom_field_index = jirautils.gh_issue_field.split('_')[1]
custom_field = f'cf[{custom_field_index}]'
duplicate_list = jirautils.search_issues(
f'{custom_field} = "{gh_issue_url}"')['issues']
if len(duplicate_list) > 0:
duplicate_issues[gh_issue_url] = list(
map(lambda issue: issue['key'], duplicate_list))
print(
f'* Creating Jira issue for {gh_issue_url} ({gh_issue_title})')
jira_api_url = ''
jira_key = ''
if not args.dry_run:
create_response = jirautils.create_issue(jira_map["issue"])
if args.verbose:
pprint(create_response)
if 'self' in create_response:
jira_api_url = create_response['self']
if 'key' in create_response:
jira_key = create_response['key']
if not args.dry_run and jira_key == '':
print('* Error: A Jira key was not returned in the creation response')
issue_failures.append(gh_issue_url)
continue
print(f'* Adding comments from GitHub to new Jira issue {jira_key}')
if not args.dry_run:
for comment_map in jira_map['comments']:
comment_response = jirautils.add_comment_from_url(
f'{jira_api_url}/comment', comment_map)
if args.verbose:
pprint(comment_response)
print(
f'* Adjusting status of Jira issue to match ZenHub pipeline {jira_key}')
if not args.dry_run:
if jira_map['issue']['status']:
transition_response = jirautils.do_transition(
jira_key, jira_map['issue']['status'])
if args.verbose:
pprint(transition_response)
# Add comment in GH issue with link to new Jira issue
gh_issue_number = jira_map['gh_issue_number']
jira_html_url = f'{jirautils.html_url}/{jira_key}'
gh_comment = 'This issue has been migrated to Jira'
if component_name != '':
gh_comment += f' for {component_name}'
gh_comment += f': {jira_html_url}'
if not args.dry_run:
comment_response = ghutils.add_issue_comment(
gh_issue_number, gh_comment)
if args.verbose:
pprint(comment_response)
# Add migration label and close GH issue if allowed
print('* Handling GitHub issue labels and closing issue if allowed')
if not args.dry_run:
if jira_map['close_gh_issue']:
label_response = ghutils.add_issue_label(
gh_issue_number, completion_label)
if args.verbose:
pprint(label_response)
close_response = ghutils.close_issue(gh_issue_number)
if args.verbose:
pprint(close_response)
else:
# We're not closing, so add squad-level migration label
label_response = ghutils.add_issue_label(
gh_issue_number, squad_completion_label)
if args.verbose:
pprint(label_response)
if len(issue_failures) > 0:
print('* Failed to create Jira issues for:')
for issue in issue_failures:
print(f' {issue}')
if len(duplicate_issues) > 0:
print('* Duplicate issues detected for review:')
for issue in duplicate_issues:
print(f' {issue}: {duplicate_issues[issue]}')