-
-
Save gerritwalther/1838bf315248c7f64f35 to your computer and use it in GitHub Desktop.
Helps to import issues from redmine
This file contains hidden or 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
#!/usr/bin/env ruby | |
require 'faraday' | |
require 'json' | |
require 'gitlab' | |
module Redmine | |
Host = nil | |
APIKey = nil | |
def self.connection | |
raise 'must define a Host' if Host.nil? | |
@connection ||= Faraday.new(:url => Host) do |faraday| | |
# faraday.response :logger | |
faraday.adapter Faraday.default_adapter | |
end | |
end | |
def self.get(path, attrs = {}) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.get(path, attrs) do |req| | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
JSON.parse result.body | |
end | |
def self.post(path, attrs = {}, body = nil) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.post(path, attrs) do |req| | |
req.body = body | |
req.headers['Content-Type'] = 'application/json' | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
JSON.parse result.body | |
end | |
def self.put(path, attrs = {}, body = nil) | |
raise 'must define an APIKey' if APIKey.nil? | |
result = connection.put(path, attrs) do |req| | |
req.body = body | |
req.headers['Content-Type'] = 'application/json' | |
req.headers['X-Redmine-API-Key'] = APIKey | |
end | |
end | |
class Base | |
attr_accessor :id, :attributes | |
def self.pluralized_resource_name | |
@pluralized_resource_name ||= "#{self.resource_name}s" | |
end | |
def self.resource_name | |
@resource_name ||= self.name.split('::').last.downcase | |
end | |
def self.list(options = {}) | |
list = Redmine.get "#{pluralized_resource_name}.json", options | |
raise "did not find any #{pluralized_resource_name} in #{list.inspect}" if list[pluralized_resource_name].nil? | |
list[pluralized_resource_name].collect do |attributes| | |
obj = new | |
obj.attributes = attributes | |
obj | |
end | |
end | |
def self.find(id) | |
@find ||= {} | |
return @find[id] if @find[id] | |
response = Redmine.get "#{pluralized_resource_name}/#{id}.json" | |
obj = new | |
obj.attributes = response[resource_name] | |
@find[id] = obj | |
end | |
def method_missing(sym) | |
self.attributes[sym.to_s] | |
end | |
def id | |
self.attributes['id'] | |
end | |
end | |
class Project < Base | |
def issues(options = {}) | |
@issues ||= Issue.list(options.merge(:status_id => '*', :project_id => self.id, :limit => 999)) | |
end | |
def categories | |
@categories ||= IssueCategory.list :project_id => self.id | |
end | |
def category_by_name(name) | |
@category_by_name ||= {} | |
@category_by_name[name] ||= categories.detect { |category| category.name == name } | |
end | |
def self.by_identifier(identifier) | |
self.list(:limit => 1000).detect { |project| project.identifier == identifier } | |
end | |
end | |
class User < Base | |
def self.by_email(email) | |
@by_email ||= {} | |
@by_email[email] ||= self.list.detect { |user| user.mail == email } | |
end | |
end | |
class Issue < Base | |
def self.create(project, subject, description, attributes = {}) | |
body = { | |
:issue => { | |
:project_id => project.id, | |
:subject => subject, | |
:description => description, | |
:tracker_id => Tracker.first.id, | |
:priority_id => 4 | |
}.merge(attributes) | |
}.to_json | |
response = Redmine.post 'issues.json', {}, body | |
end | |
def update(new_attributes = {}) | |
changes = {} | |
new_attributes.each do |key, value| | |
if key.match(/_id$/) | |
if self.attributes[key.to_s.gsub(/_id$/, '')] and self.attributes[key.to_s.gsub(/_id$/, '')]['id'].to_s != value.to_s | |
changes[key] = value | |
end | |
else | |
changes[key] = value if self.attributes[key.to_s].to_s != value.to_s | |
end | |
end | |
if changes.empty? | |
puts 'no changes !' | |
return | |
end | |
puts "changes: #{changes.inspect}" | |
response = Redmine.put "issues/#{self.id}.json", {}, { :issue => changes }.to_json | |
end | |
def author | |
Redmine::User.find self.attributes['author']['id'] | |
end | |
def assignee | |
Redmine::User.find self.attributes['assigned_to']['id'] rescue nil | |
end | |
end | |
class IssueStatus < Base | |
def self.pluralized_resource_name ; 'issue_statuses' ; end | |
def self.resource_name ; 'issue_status' ; end | |
def self.by_name(name) | |
@by_name ||= {} | |
@by_name[name] ||= list.detect { |status| status.name == name } | |
end | |
end | |
class IssueCategory < Base | |
def self.pluralized_resource_name ; 'issue_categories' ; end | |
def self.resource_name ; 'issue_category' ; end | |
def self.list(options = {}) | |
raise "must provide a project_id" if options[:project_id].nil? | |
list = Redmine.get "projects/#{options.delete :project_id}/issue_categories.json", options | |
raise "did not find any issue_categories in #{list.inspect}" if list['issue_categories'].nil? | |
list['issue_categories'].collect do |attributes| | |
obj = new | |
obj.attributes = attributes | |
obj | |
end | |
end | |
end | |
class Tracker < Base | |
def self.first | |
@first ||= self.list.first | |
end | |
end | |
end | |
# Example configuration: | |
# Redmine::Host = 'http://redmine.example.com' | |
# Redmine::APIKey = '<hex api key, found at the users "My Account", if the API is enabled>' | |
# config.endpoint = 'http://gitlab.example.com/api/v3' | |
# config.private_token = '<hex api key, found in the Account of the user>' | |
Redmine::Host = '' | |
Redmine::APIKey = '' | |
Gitlab.configure do |config| | |
config.endpoint = '' | |
config.private_token = '' | |
end | |
# puts Redmine::IssueStatus.list.inspect | |
# puts Redmine::IssueStatus.by_name('Assigned').inspect | |
# puts Redmine::Project.list.first.categories.inspect | |
# puts Redmine::Project.list.first.category_by_name('gitlab bug').inspect | |
# puts Redmine::Issue.create(Redmine::Project.list.first, 'testing creation from script', 'bleh', :assigned_to_id => 3, :status_id => Redmine::IssueStatus.by_name('Assigned').id, :category_id => Redmine::Project.list.first.category_by_name('gitlab task').id) | |
Gitlab.projects.each do |gitlab_project| | |
puts "iterating over project #{gitlab_project.name}" | |
# First, find a project matching the gitlab one | |
redmine_project = Redmine::Project.by_identifier(gitlab_project.name) | |
next if redmine_project.nil? | |
redmine_issues = redmine_project.issues | |
gitlab_issues = Gitlab.issues(gitlab_project.id) | |
processed_gitlab_issues = [] | |
puts "found #{gitlab_issues.count} issues on gitlab" | |
# Then, iterate through all redmine issues of the project | |
redmine_issues.each do |redmine_issue| | |
puts "processing redmine issue #{redmine_issue.id} #{redmine_issue.subject}" | |
# Skipping non gitlab issues | |
#Uncommented, because this prevents the import of issues. | |
#next if redmine_issue.category.nil? or redmine_issue.category['name'].match(/^gitlab/).nil? | |
# Find corresponding assignee in gitlab | |
gitlab_assignee = Gitlab.users.detect { |u| u.email == redmine_issue.assignee.mail } unless redmine_issue.assignee.nil? | |
gitlab_assignee_id = gitlab_assignee ? gitlab_assignee.id : nil | |
puts "gitlab assignee: #{gitlab_assignee.inspect}" | |
# Search for an existing issue | |
existing_issue = gitlab_issues.detect { |gitlab_issue| gitlab_issue.title == redmine_issue.subject} | |
puts "issue already existing on gitlab" if existing_issue | |
if existing_issue # Existing issue, updating status | |
if Time.parse(existing_issue.updated_at) < Time.parse(redmine_issue.updated_on) | |
puts "gitlab issue is older than redmine, updating gitlab issue" | |
processed_gitlab_issues << existing_issue unless existing_issue.nil? | |
Gitlab.edit_issue gitlab_project.id, | |
existing_issue.id, | |
:title => redmine_issue.subject, | |
:description => redmine_issue.description, | |
:assignee_id => gitlab_assignee_id, | |
:labels => redmine_issue.category['name'].gsub(/gitlab/, '').strip, | |
:closed => redmine_issue.status['name'] == 'Closed' | |
else | |
puts "gitlab issue is newer than redmine, skip the update" | |
end | |
else # No existing issue, creating it | |
puts "creating issue on gitlab" | |
created_issue = Gitlab.create_issue gitlab_project.id, | |
redmine_issue.subject, | |
:description => redmine_issue.description, | |
:assignee_id => gitlab_assignee_id#, | |
# Commented, because this breaks if there are no categories set on the redmine issues. | |
# :labels => redmine_issue.category['name'].gsub(/gitlab/, '').strip | |
processed_gitlab_issues << existing_issue unless existing_issue.nil? | |
end | |
end | |
(gitlab_issues - processed_gitlab_issues).each do |gitlab_issue| | |
puts "processing gitlab issue #{gitlab_issue.id} #{gitlab_issue.title}" | |
# Find corresponding assignee in redmine | |
redmine_assignee = Redmine::User.by_email(gitlab_issue.assignee.email) unless gitlab_issue.assignee.nil? | |
redmine_assignee_id = redmine_assignee ? redmine_assignee.id : nil | |
# Search for an existing issue | |
existing_issue = redmine_issues.detect { |redmine_issue| gitlab_issue.title == redmine_issue.subject } | |
puts "issue already existing on redmine" if existing_issue | |
status = case | |
when gitlab_issue.closed | |
'Closed' | |
when gitlab_issue.assignee | |
'Assigned' | |
else | |
'New' | |
end | |
status_id = Redmine::IssueStatus.by_name(status).id | |
if existing_issue # Existing issue, updating status | |
puts "updatig issue on redmine" | |
existing_issue.update :description => gitlab_issue.description, | |
:assigned_to_id => redmine_assignee_id, | |
:status_id => status_id, | |
:done_ratio => gitlab_issue.closed ? '100' : '0' | |
else # No existing issue, creating it | |
puts "creating issue on redmine" | |
Redmine::Issue.create( | |
redmine_project, | |
gitlab_issue.title, | |
gitlab_issue.description, | |
:assigned_to_id => redmine_assignee_id, | |
:status_id => status_id, | |
:category_id => Redmine::Project.list.first.category_by_name("gitlab").id, | |
:done_ratio => gitlab_issue.closed ? '100' : '0' | |
) | |
end | |
end | |
end | |
# puts Redmine::Issue.list.first.inspect | |
# puts Redmine::User.find(3).inspect | |
# puts Redmine::User.find(3).mail | |
# puts Gitlab.users.detect { |u| u.email == Redmine::User.find(3).mail }.inspect | |
# puts Gitlab.users.collect &:email | |
# puts Redmine::Issue.list.first.author.inspect | |
# puts Gitlab.projects.inspect | |
# puts Gitlab.issues.first.title | |
# puts Redmine::Project.list.first.id.inspect | |
# puts Redmine::Issue.list(:limit => 500).count | |
# | |
# puts '===' | |
# puts '===' | |
# puts '===' | |
# | |
# puts Redmine::Project.list.first.issues.inspect | |
# puts Redmine::Issue.find(778).inspect | |
# puts Redmine::Project.by_identifier('bureau-dr').inspect |
@maxxer: Sorry, I accidentally looked into this script and saw you wrote a comment. I guess you don't need this anymore, but I want to give an answer anyway in case someone stumbles onto this.
To me it sounds like you have the wrong configuration for your gitlab. Did you set the correct entpoint for the gitlab api? And did you use the correct api key for the Account of the user to create the issues?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hi. thanks for the script, but I'm getting this error when running:
any idea why? thanks