Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ackermann/68704138c4871f8f7b1f9d9ed835eaf2 to your computer and use it in GitHub Desktop.
Save ackermann/68704138c4871f8f7b1f9d9ed835eaf2 to your computer and use it in GitHub Desktop.
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)
@BrianGilbert
Copy link

@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?

MacBook-Pro:mhfa brian$ python move-issues-from-jira-to-gitlab.py 
Traceback (most recent call last):
  File "move-issues-from-jira-to-gitlab.py", line 62, in <module>
    for issue in jira_issues.json()['issues']:
  File "/usr/local/lib/python2.7/site-packages/requests/models.py", line 892, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/local/Cellar/python@2/2.7.16/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 339, in loads
    return _default_decoder.decode(s)
  File "/usr/local/Cellar/python@2/2.7.16/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 364, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/Cellar/python@2/2.7.16/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 382, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment