#!/usr/bin/env ruby # coding: utf-8 # TODO: add 'owner' to get/set_meta $:.unshift File.dirname(__FILE__) load 'git-mirror-backend.rb' load 'libremessages.rb' require 'net/http' require 'uri' require 'cgi' require 'json' class GitLabCE < GitMirrorBackend class Error < RuntimeError def initialize(obj) @obj = obj end def obj return @obj end def to_s return @obj.to_s end end def initialize() @connections = {} @projects = {} @config = {} end def connection(uri) key=URI(uri.scheme+":") key.host = uri.host key.port = uri.port @connections[key] ||= Net::HTTP::start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') return @connections[key] end def request(req) return self.connection(req.uri).request(req) end def finish @connections.each do |k,v| v.finish() end @connections = {} super end def config return @config end # Project def project(project_id) unless @projects.has_key?(project_id) @projects[project_id] = Project.new(self, project_id) end return @projects[project_id] end class Project def initialize(gl, project_id) @gl = gl @project_id = project_id @cache = {} end def info unless @cache.has_key?(:info) req = Net::HTTP::Get.new(@gl.config['apiurl'] + "projects/" + CGI::escape(@project_id)) req.add_field("PRIVATE-TOKEN", @gl.config['apikey']) res = @gl.request(req) case res.code when "200" @cache[:info] = JSON::parse(res.body) when "404" @cache[:info] = nil else raise Error.new([res, res.body]) end end return @cache[:info] end def info=(i) @cache[:info] = i end def get_meta return self.info.select{|k,v| @gl.vars.include?(k.to_sym)} end def set_meta(map) mirror = map["mirror"] map.delete("mirror") illegal = map.select{|k,v| not @gl.vars.include?(k.to_sym)} if illegal.count > 0 raise Error.new(illegal) end if self.info().nil? # create libremessages('msg2', 'Creating repo') namespace, path = @project_id.split('/', 2) namespace_id = namespace_path2id(namespace) req = Net::HTTP::Post.new(@gl.config['apiurl'] + "projects") req.add_field("PRIVATE-TOKEN", @gl.config['apikey']) req.add_field("Content-Type", "application/json") map["name"] ||= path map["namespace_id"] = namespace_id map["path"] = path if not mirror.nil? map["import_url"] = mirror end req.body = JSON::dump(map) res = @gl.request(req) if res.code != "201" raise Error.new([res, res.body]) end self.info = JSON::parse(res.body) else # update mismatch = false map.each do |key,val| if self.info[key].to_s != val.to_s if !mismatch libremessages('msg2', 'Updating general metadata') end libremessages('plain', '%q: %q -> %q', key, self.info[key].to_s, val.to_s) mismatch = true end end unless mismatch libremessages('msg2', 'General metadata ok') return nil end req = Net::HTTP::Put.new(@gl.config['apiurl'] + "projects/" + CGI::escape(self.info["id"].to_s)) req.add_field("PRIVATE-TOKEN", @gl.config['apikey']) req.add_field("Content-Type", "application/json") req.body = JSON::dump(map) res = @gl.request(req) if res.code != "200" raise Error.new([res, res.body]) end self.info = JSON::parse(res.body) end return nil end def namespace_path2id(path, pageno=1) req = Net::HTTP::Get.new(@gl.config['apiurl'] + "namespaces?page=#{pageno}&search=#{CGI::escape(path)}") req.add_field("PRIVATE-TOKEN", @gl.config['apikey']) res = @gl.request(req) if res.code != "200" raise Error.new([res, res.body]) end page = JSON::parse(res.body) page.each do |namespace| if namespace["path"] == path return namespace["id"].to_i end end if pageno < res['X-Total-Pages'].to_i return namespace_path2id(path, pageno+1) end return nil end def repo_mode return "passive" end end # commands def cmd_config(*args) args.each do |arg| key, val = arg.split('=', 2) key = key.gsub('-', '_') case key when "apiurl" val = URI(val) unless val.path.end_with?("/") val.path += "/" end end @config[key] = val end return nil end def cmd_get_meta(project_id) map = self.project(project_id).get_meta() ret = {} map.each do |key,val| key = key.gsub('_', '-') map[key] = val end return ret end def cmd_set_meta(project_id, *pairs) map = {} pairs.each do |pair| key, val = pair.split('=', 2) key = key.gsub('-', '_') if val.nil? map.delete(key) else map[key] = val end end self.project(project_id).set_meta(map) return nil end def cmd_push_url(project_id) return self.project(project_id).info["ssh_url_to_repo"] end def cmd_pull_url(project_id) return self.project(project_id).info["http_url_to_repo"] end def cmd_repo_mode(project_id) return self.project(project_id).repo_mode() end def vars # API docs suck, look at `lib/api/projects.rb` instead. return [ :builds_enabled, # create | create-user | edit :container_registry_enabled, # XXX # create | | edit :default_branch, # | create-user | edit :description, # create | create-user | edit #:import_url, # XXX # create | create-user | :issues_enabled, # create | create-user | edit :lfs_enabled, # create | create-user | edit :merge_requests_enabled, # create | create-user | edit :name, # create | create-user | edit #:namespace_id, # create | | :only_allow_merge_if_build_succeeds, # create | create-user | edit #:path, # create | | edit :public, # create | create-user | edit :public_builds, # create | create-user | edit :request_access_enabled, # create | create-user | edit :shared_runners_enabled, # create | create-user | edit :snippets_enabled, # create | create-user | edit :visibility_level, # create | create-user | edit :wiki_enabled, # create | create-user | edit :only_allow_merge_if_all_discussions_are_resolved # create | create-user | edit ] end end if __FILE__ == $0 if ARGV.length != 1 raise "Usage: $0 ACCOUNT_NAME" end GitLabCE.new().repl(ARGV[1]) end