Forked from amarruedo/move-issues-from-jira-to-gitlab.py
Last active
March 6, 2020 21:53
-
-
Save ackermann/68704138c4871f8f7b1f9d9ed835eaf2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
from requests.auth import HTTPBasicAuth | |
import re | |
from StringIO import StringIO | |
import urllib | |
JIRA_URL = 'https://your-jira-url.tld/' | |
JIRA_ACCOUNT = ('jira-username', 'jira-password') | |
# the JIRA project ID (short) | |
JIRA_PROJECT = 'PRO' | |
GITLAB_URL = 'http://your-gitlab-url.tld/' | |
# this token will be used whenever the API is invoked and | |
# the script will be unable to match the jira's author of the comment / attachment / issue | |
# this identity will be used instead. use an admins PRIVATE token here to allow SUDOing. | |
GITLAB_TOKEN = 'get-this-token-from-your-profile' | |
# the project in gitlab that you are importing issues to. | |
GITLAB_PROJECT = 'namespaced/project/name' | |
# the numeric project ID. If you don't know it, the script will search for it | |
# based on the project name. | |
GITLAB_PROJECT_ID = None | |
# set this to false if JIRA / Gitlab is using self-signed certificate. | |
VERIFY_SSL_CERTIFICATE = True | |
# IMPORTANT !!! | |
# make sure that user (in gitlab) has access to the project you are trying to | |
# import into. Otherwise the API request will fail. | |
# jira user name as key, gitlab as value | |
# if you want dates and times to be correct, make sure every user is (temporarily) admin | |
# map the special user _nonexistent to a GitLab user that should be used as a replacement | |
GITLAB_USER_NAMES = { | |
'jira': 'gitlab', | |
'_nonexistent': 'jira_user' | |
} | |
jira_issues = requests.get( | |
JIRA_URL + 'rest/api/2/search?jql=project=%s&maxResults=10000' % JIRA_PROJECT, | |
auth=HTTPBasicAuth(*JIRA_ACCOUNT), | |
verify=VERIFY_SSL_CERTIFICATE, | |
headers={'Content-Type': 'application/json'}, | |
) | |
users = requests.get( | |
GITLAB_URL + 'api/v4/users', | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, | |
verify=VERIFY_SSL_CERTIFICATE, | |
).json() | |
usernames = [user['username'] for user in users] | |
def get_username(username): | |
username = GITLAB_USER_NAMES.get(username, username) | |
if user not in usernames: | |
return GITLAB_USER_NAMES['_nonexistent'] | |
else: | |
return username | |
if not GITLAB_PROJECT_ID: | |
# find out the ID of the project. | |
for project in requests.get( | |
GITLAB_URL + 'api/v4/projects', | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, | |
).json(): | |
if project['path_with_namespace'] == GITLAB_PROJECT: | |
GITLAB_PROJECT_ID = project['id'] | |
break | |
if not GITLAB_PROJECT_ID: | |
raise Exception("Unable to find %s in gitlab!" % GITLAB_PROJECT) | |
MILESTONES = {} | |
for issue in jira_issues.json()['issues']: | |
jiraKey = issue['key'] | |
reporter = issue['fields']['reporter']['name'] | |
milestone = '' | |
released = False | |
milestoneDescription = None | |
releaseDate = None | |
if issue['fields'].get('fixVersions'): | |
milestone = (issue['fields']['fixVersions'])[0].get('name',0) | |
released = (issue['fields']['fixVersions'])[0].get('released',0) | |
milestoneDescription = (issue['fields']['fixVersions'])[0].get('description',0) | |
releaseDate = (issue['fields']['fixVersions'])[0].get('releaseDate',0) | |
# create milestones | |
milestoneID = None | |
if milestone != '' and milestone not in MILESTONES: | |
milestoneID = requests.post( | |
GITLAB_URL + 'api/v4/projects/%s/milestones' % GITLAB_PROJECT_ID, | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, | |
verify=VERIFY_SSL_CERTIFICATE, | |
data={ | |
'title': milestone, | |
'description': milestoneDescription, | |
'due_date': releaseDate | |
} | |
).json()['id'] | |
MILESTONES[milestone] = milestoneID | |
if released: | |
res = requests.put( | |
GITLAB_URL + 'api/v4/projects/%s/milestones/%s?state_event=close' % (GITLAB_PROJECT_ID , milestoneID), | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, | |
verify=VERIFY_SSL_CERTIFICATE | |
).json() | |
assignee = '' | |
if issue['fields'].get('assignee'): | |
assignee = issue['fields']['assignee'].get('name',0) | |
assignee_id = None | |
if assignee != '': | |
for user in users: | |
if user['username'] == assignee: | |
assignee_id = user['id'] | |
break | |
labels = issue['fields']['issuetype']['name'] + "," + issue['fields']['priority']['name'] | |
if issue['fields']['status']['statusCategory']['name'] != 'Done': | |
labels = issue['fields']['status']['statusCategory']['name'] + "," + labels | |
title = issue['fields']['summary'].replace('#', 'FABRIC - ') | |
description = None | |
if issue['fields']['description'] != None: | |
description = issue['fields']['description'] + '\n' | |
else: | |
description = '' | |
gl_reporter = get_username(reporter) | |
if gl_reporter == GITLAB_USER_NAMES['_nonexistent']: | |
description = '**' + reporter + '**: ' + description | |
response = requests.post( | |
GITLAB_URL + 'api/v4/projects/%s/issues' % GITLAB_PROJECT_ID, | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': gl_reporter}, | |
verify=VERIFY_SSL_CERTIFICATE, | |
data={ | |
'title': title + ' [' + jiraKey + ']', | |
'description': description, | |
'created_at': issue['fields']['created'], | |
'assignee_id': assignee_id, | |
'milestone_id': MILESTONES.get(milestone, None), | |
'labels': labels | |
} | |
).json() | |
gl_issue = response['id'] | |
if issue['fields']['status']['statusCategory']['name'] == "Done": | |
res = requests.put( | |
GITLAB_URL + 'api/v4/projects/%s/issues/%s?state_event=close' % (GITLAB_PROJECT_ID , response['iid']), | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN}, | |
verify=VERIFY_SSL_CERTIFICATE | |
).json() | |
# get comments and attachments | |
issue_info = requests.get( | |
JIRA_URL + 'rest/api/2/issue/%s/?fields=attachment,comment' % issue['id'], | |
auth=HTTPBasicAuth(*JIRA_ACCOUNT), | |
verify=VERIFY_SSL_CERTIFICATE, | |
headers={'Content-Type': 'application/json'} | |
).json() | |
for comment in issue_info['fields']['comment']['comments']: | |
author = comment['author']['name'] | |
gl_author = get_username(author) | |
if gl_author == GITLAB_USER_NAMES['_nonexistent']: | |
comment['body'] = '**' + author + '**: ' + comment['body'] | |
note_add = requests.post( | |
GITLAB_URL + 'api/v3/projects/%s/issues/%s/notes' % (GITLAB_PROJECT_ID, gl_issue), | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': gl_author}, | |
verify=VERIFY_SSL_CERTIFICATE, | |
data={ | |
'body': comment['body'], | |
'created_at': comment['created'] | |
} | |
) | |
if len(issue_info['fields']['attachment']): | |
for attachment in issue_info['fields']['attachment']: | |
author = attachment['author']['name'] | |
_file = requests.get( | |
attachment['content'], | |
auth=HTTPBasicAuth(*JIRA_ACCOUNT), | |
verify=VERIFY_SSL_CERTIFICATE, | |
) | |
_content = StringIO(_file.content) | |
gl_author = get_username(author) | |
file_info = requests.post( | |
GITLAB_URL + 'api/v3/projects/%s/uploads' % GITLAB_PROJECT_ID, | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': gl_author}, | |
files={ | |
'file': ( | |
attachment['filename'], | |
_content | |
) | |
}, | |
verify=VERIFY_SSL_CERTIFICATE | |
) | |
del _content | |
markdown = file_info.json()['markdown'] | |
if gl_author == GITLAB_USER_NAMES['_nonexistent']: | |
markdown = '**' + author + '**: ' + markdown | |
# now we got the upload URL. Let's post the comment with an | |
# attachment | |
requests.post( | |
GITLAB_URL + 'api/v3/projects/%s/issues/%s/notes' % (GITLAB_PROJECT_ID, gl_issue), | |
headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': gl_author}, | |
verify=VERIFY_SSL_CERTIFICATE, | |
data={ | |
'body': markdown, | |
'created_at': attachment['created'] | |
} | |
) | |
print "created issue #%s" % gl_issue | |
print "imported %s issues from project %s" % (len(jira_issues.json()['issues']), JIRA_PROJECT) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ackermann I'm getting an error when running this code wondering if it needs to be run with a specific version of Python or something?