Skip to content

Instantly share code, notes, and snippets.

Revisions

  1. @amarruedo amarruedo revised this gist Apr 27, 2017. 1 changed file with 44 additions and 11 deletions.
    55 changes: 44 additions & 11 deletions move-issues-from-jira-to-gitlab.py
    Original file line number Diff line number Diff line change
    @@ -38,8 +38,6 @@
    headers={'Content-Type': 'application/json'},
    )

    # get gitlab users to match issue asignees
    # since in my case users in Gitlab and Jira are the same (LDAP) the match is straight forward
    users = requests.get(
    GITLAB_URL + 'api/v4/users',
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
    @@ -59,43 +57,78 @@
    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)

    # match asignee
    # all asginees must have proper acces rigths in the gitlab repository, otherwise it will fail
    assignee_id = None
    if assignee != '':
    for user in users:
    if user['username'] == assignee:
    assignee_id = user['id']
    break

    # jira issue status, type and priority as labels in gitlab issue
    # this is usefull in the gitlab issue board
    labels = issue['fields']['status']['statusCategory']['name'] + "," + issue['fields']['issuetype']['name'] + "," + issue['fields']['priority']['name']
    title = issue['fields']['summary'].replace('#', 'FABRIC - ')
    description = None
    if issue['fields']['description'] != None:
    description = '```\n' + issue['fields']['description'] + '\n```'
    else:
    description = issue['fields']['description']

    response = requests.post(
    GITLAB_URL + 'api/v4/projects/%s/issues' % GITLAB_PROJECT_ID,
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': GITLAB_USER_NAMES.get(reporter, reporter)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'title': issue['fields']['summary'],
    'description': issue['fields']['description'],
    'title': jiraKey + ' ' + title,
    'description': description,
    'created_at': issue['fields']['created'],
    'assignee_id': assignee_id,
    'milestone_id': MILESTONES.get(milestone, None),
    'labels': labels
    }
    ).json()

    # close all gitlab issues marked as Done in Jira
    # I use gitlab root user token so every issue appears closed by the admin user
    # once every Done issue is closed, I remove "Done" label in gitlab project since it is not usefull anymore.
    gl_issue = response['id']
    if issue['fields']['status']['statusCategory']['name'] == "Done":
    res = requests.put(
  2. @amarruedo amarruedo revised this gist Apr 26, 2017. 1 changed file with 47 additions and 8 deletions.
    55 changes: 47 additions & 8 deletions move-issues-from-jira-to-gitlab.py
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,7 @@
    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')
    @@ -31,16 +32,24 @@
    }

    jira_issues = requests.get(
    JIRA_URL + 'rest/api/2/search?jql=project=%s+&maxResults=10000' % JIRA_PROJECT,
    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'}
    headers={'Content-Type': 'application/json'},
    )

    # get gitlab users to match issue asignees
    # since in my case users in Gitlab and Jira are the same (LDAP) the match is straight forward
    users = requests.get(
    GITLAB_URL + 'api/v4/users',
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
    verify=VERIFY_SSL_CERTIFICATE,
    ).json()

    if not GITLAB_PROJECT_ID:
    # find out the ID of the project.
    for project in requests.get(
    GITLAB_URL + 'api/v3/projects',
    GITLAB_URL + 'api/v4/projects',
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
    ).json():
    if project['path_with_namespace'] == GITLAB_PROJECT:
    @@ -51,19 +60,49 @@
    raise Exception("Unable to find %s in gitlab!" % GITLAB_PROJECT)

    for issue in jira_issues.json()['issues']:

    reporter = issue['fields']['reporter']['name']

    gl_issue = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/issues' % GITLAB_PROJECT_ID,
    assignee = ''
    if issue['fields'].get('assignee'):
    assignee = issue['fields']['assignee'].get('name',0)

    # match asignee
    # all asginees must have proper acces rigths in the gitlab repository, otherwise it will fail
    assignee_id = None
    if assignee != '':
    for user in users:
    if user['username'] == assignee:
    assignee_id = user['id']
    break

    # jira issue status, type and priority as labels in gitlab issue
    # this is usefull in the gitlab issue board
    labels = issue['fields']['status']['statusCategory']['name'] + "," + issue['fields']['issuetype']['name'] + "," + issue['fields']['priority']['name']

    response = requests.post(
    GITLAB_URL + 'api/v4/projects/%s/issues' % GITLAB_PROJECT_ID,
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': GITLAB_USER_NAMES.get(reporter, reporter)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'title': issue['fields']['summary'],
    'description': issue['fields']['description'],
    'created_at': issue['fields']['created']
    'created_at': issue['fields']['created'],
    'assignee_id': assignee_id,
    'labels': labels
    }
    ).json()['id']
    ).json()

    # close all gitlab issues marked as Done in Jira
    # I use gitlab root user token so every issue appears closed by the admin user
    # once every Done issue is closed, I remove "Done" label in gitlab project since it is not usefull anymore.
    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(
  3. @florisb florisb revised this gist Sep 23, 2016. 1 changed file with 48 additions and 83 deletions.
    131 changes: 48 additions & 83 deletions move-issues-from-jira-to-gitlab.py
    Original file line number Diff line number Diff line change
    @@ -8,8 +8,6 @@
    # the JIRA project ID (short)
    JIRA_PROJECT = 'PRO'
    GITLAB_URL = 'http://your-gitlab-url.tld/'
    # this is needed for importing attachments. The script will login to gitlab under the hood.
    GITLAB_ACCOUNT = ('gitlab-username', 'gitlab-password')
    # 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.
    @@ -20,31 +18,30 @@
    # based on the project name.
    GITLAB_PROJECT_ID = None
    # set this to false if JIRA / Gitlab is using self-signed certificate.
    VERIFY_SSL_CERTIFICATE = False
    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.
    GITLAB_USER_TOKENS = {
    'jira-username': 'gitlab-private-token-for-this-user',
    }

    RE_TOKEN = "<meta content=\"(?P<token>.*)\" name=\"csrf-token\""
    # jira user name as key, gitlab as value
    # if you want dates and times to be correct, make sure every user is (temporarily) admin
    GITLAB_USER_NAMES = {
    'jira': 'gitlab',
    }

    jira_issues = requests.get(
    JIRA_URL + 'rest/api/2/search?jql=project=%s+AND+resolution=Unresolved+ORDER+BY+priority+DESC&maxResults=10000' % JIRA_PROJECT,
    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'}
    )


    if not GITLAB_PROJECT_ID:
    # find out the ID of the project.
    for project in requests.get(
    GITLAB_URL + 'api/v3/projects',
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
    verify=VERIFY_SSL_CERTIFICATE
    ).json():
    if project['path_with_namespace'] == GITLAB_PROJECT:
    GITLAB_PROJECT_ID = project['id']
    @@ -54,16 +51,17 @@
    raise Exception("Unable to find %s in gitlab!" % GITLAB_PROJECT)

    for issue in jira_issues.json()['issues']:

    reporter = issue['fields']['reporter']['name']

    gl_issue = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/issues' % GITLAB_PROJECT_ID,
    headers={'PRIVATE-TOKEN': GITLAB_USER_TOKENS.get(reporter, GITLAB_TOKEN)},
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': GITLAB_USER_NAMES.get(reporter, reporter)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'title': issue['fields']['summary'],
    'description': issue['fields']['description']
    'description': issue['fields']['description'],
    'created_at': issue['fields']['created']
    }
    ).json()['id']

    @@ -80,85 +78,52 @@

    note_add = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/issues/%s/notes' % (GITLAB_PROJECT_ID, gl_issue),
    headers={'PRIVATE-TOKEN': GITLAB_USER_TOKENS.get(author, GITLAB_TOKEN)},
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': GITLAB_USER_NAMES.get(author, author)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'body': comment['body']
    'body': comment['body'],
    'created_at': comment['created']
    }
    )

    if len(issue_info['fields']['attachment']):
    # !!! HACK !!! obtain a session to gitlab in order to get a secret csrftoken
    with requests.Session() as s:
    token = re.search(
    RE_TOKEN,
    s.get(
    GITLAB_URL + 'users/sign_in',
    verify=VERIFY_SSL_CERTIFICATE
    ).content
    ).group('token')

    signin = s.post(
    GITLAB_URL + 'users/sign_in',
    headers={
    "Referer": GITLAB_URL
    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)

    file_info = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/uploads' % GITLAB_PROJECT_ID,
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN,'SUDO': GITLAB_USER_NAMES.get(author, author)},
    files={
    'file': (
    attachment['filename'],
    _content
    )
    },
    verify=VERIFY_SSL_CERTIFICATE
    )

    del _content

    # 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': GITLAB_USER_NAMES.get(author, author)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'authenticity_token': token,
    'user[login]': GITLAB_ACCOUNT[0],
    'user[password]': GITLAB_ACCOUNT[1],
    'user[remember_me]': 0
    'body': file_info.json()['markdown'],
    'created_at': attachment['created']
    }
    )

    html = s.get(
    GITLAB_URL + '%s/issues/%s' % (GITLAB_PROJECT, gl_issue),
    verify=VERIFY_SSL_CERTIFICATE
    ).content

    token = re.search(RE_TOKEN, html).group('token')

    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)

    file_info = s.post(
    GITLAB_URL + '%s/uploads' % GITLAB_PROJECT,
    headers={
    'X-CSRF-Token': token,
    },
    files={
    'file': (
    attachment['filename'],
    _content
    )
    },
    verify=VERIFY_SSL_CERTIFICATE
    )

    del _content

    # 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_USER_TOKENS.get(author, GITLAB_TOKEN)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'body': '[%s](%s)' % (
    attachment['filename'],
    file_info.json()['link']['url']
    )
    }
    )

    s.get(GITLAB_URL + 'users/sign_out')
    print "created issue #%s" % gl_issue

    print "imported %s issues from project %s" % (len(jira_issues.json()['issues']), JIRA_PROJECT)
  4. @toudi toudi revised this gist Oct 2, 2015. 1 changed file with 10 additions and 3 deletions.
    13 changes: 10 additions & 3 deletions move-issues-from-jira-to-gitlab.py
    Original file line number Diff line number Diff line change
    @@ -92,7 +92,10 @@
    with requests.Session() as s:
    token = re.search(
    RE_TOKEN,
    s.get(GITLAB_URL + 'users/sign_in').content
    s.get(
    GITLAB_URL + 'users/sign_in',
    verify=VERIFY_SSL_CERTIFICATE
    ).content
    ).group('token')

    signin = s.post(
    @@ -109,7 +112,10 @@
    }
    )

    html = s.get(GITLAB_URL + '%s/issues/%s' % (GITLAB_PROJECT, gl_issue)).content
    html = s.get(
    GITLAB_URL + '%s/issues/%s' % (GITLAB_PROJECT, gl_issue),
    verify=VERIFY_SSL_CERTIFICATE
    ).content

    token = re.search(RE_TOKEN, html).group('token')

    @@ -134,7 +140,8 @@
    attachment['filename'],
    _content
    )
    }
    },
    verify=VERIFY_SSL_CERTIFICATE
    )

    del _content
  5. @toudi toudi created this gist Oct 1, 2015.
    157 changes: 157 additions & 0 deletions move-issues-from-jira-to-gitlab.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,157 @@
    import requests
    from requests.auth import HTTPBasicAuth
    import re
    from StringIO import StringIO

    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 is needed for importing attachments. The script will login to gitlab under the hood.
    GITLAB_ACCOUNT = ('gitlab-username', 'gitlab-password')
    # 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.
    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 = False

    # IMPORTANT !!!
    # make sure that user (in gitlab) has access to the project you are trying to
    # import into. Otherwise the API request will fail.
    GITLAB_USER_TOKENS = {
    'jira-username': 'gitlab-private-token-for-this-user',
    }

    RE_TOKEN = "<meta content=\"(?P<token>.*)\" name=\"csrf-token\""

    jira_issues = requests.get(
    JIRA_URL + 'rest/api/2/search?jql=project=%s+AND+resolution=Unresolved+ORDER+BY+priority+DESC&maxResults=10000' % JIRA_PROJECT,
    auth=HTTPBasicAuth(*JIRA_ACCOUNT),
    verify=VERIFY_SSL_CERTIFICATE,
    headers={'Content-Type': 'application/json'}
    )


    if not GITLAB_PROJECT_ID:
    # find out the ID of the project.
    for project in requests.get(
    GITLAB_URL + 'api/v3/projects',
    headers={'PRIVATE-TOKEN': GITLAB_TOKEN},
    verify=VERIFY_SSL_CERTIFICATE
    ).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)

    for issue in jira_issues.json()['issues']:

    reporter = issue['fields']['reporter']['name']

    gl_issue = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/issues' % GITLAB_PROJECT_ID,
    headers={'PRIVATE-TOKEN': GITLAB_USER_TOKENS.get(reporter, GITLAB_TOKEN)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'title': issue['fields']['summary'],
    'description': issue['fields']['description']
    }
    ).json()['id']

    # 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']

    note_add = requests.post(
    GITLAB_URL + 'api/v3/projects/%s/issues/%s/notes' % (GITLAB_PROJECT_ID, gl_issue),
    headers={'PRIVATE-TOKEN': GITLAB_USER_TOKENS.get(author, GITLAB_TOKEN)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'body': comment['body']
    }
    )

    if len(issue_info['fields']['attachment']):
    # !!! HACK !!! obtain a session to gitlab in order to get a secret csrftoken
    with requests.Session() as s:
    token = re.search(
    RE_TOKEN,
    s.get(GITLAB_URL + 'users/sign_in').content
    ).group('token')

    signin = s.post(
    GITLAB_URL + 'users/sign_in',
    headers={
    "Referer": GITLAB_URL
    },
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'authenticity_token': token,
    'user[login]': GITLAB_ACCOUNT[0],
    'user[password]': GITLAB_ACCOUNT[1],
    'user[remember_me]': 0
    }
    )

    html = s.get(GITLAB_URL + '%s/issues/%s' % (GITLAB_PROJECT, gl_issue)).content

    token = re.search(RE_TOKEN, html).group('token')

    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)

    file_info = s.post(
    GITLAB_URL + '%s/uploads' % GITLAB_PROJECT,
    headers={
    'X-CSRF-Token': token,
    },
    files={
    'file': (
    attachment['filename'],
    _content
    )
    }
    )

    del _content

    # 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_USER_TOKENS.get(author, GITLAB_TOKEN)},
    verify=VERIFY_SSL_CERTIFICATE,
    data={
    'body': '[%s](%s)' % (
    attachment['filename'],
    file_info.json()['link']['url']
    )
    }
    )

    s.get(GITLAB_URL + 'users/sign_out')