Commit 79021e674bab1d34609228343fbf3403d9bd9bc6

Authored by Dmitriy Zaporozhets
1 parent 137594dd

Split gitolite backend. Use gitolite_config methods moved to separate class

app/controllers/application_controller.rb
... ... @@ -14,10 +14,6 @@ class ApplicationController < ActionController::Base
14 14 render "errors/gitolite", layout: "error"
15 15 end
16 16  
17   - rescue_from Gitlab::Gitolite::InvalidKey do |exception|
18   - render "errors/invalid_ssh_key", layout: "error"
19   - end
20   -
21 17 rescue_from Encoding::CompatibilityError do |exception|
22 18 render "errors/encoding", layout: "error", status: 404
23 19 end
... ...
app/views/errors/invalid_ssh_key.html.haml
... ... @@ -1,3 +0,0 @@
1   -%h1 Git Error
2   -%hr
3   -%p Seems like SSH Key you provided is not a valid SSH key.
lib/gitlab/backend/gitolite.rb
1   -require 'gitolite'
2   -require 'timeout'
3   -require 'fileutils'
  1 +require_relative 'gitolite_config'
4 2  
5   -# TODO: refactor & cleanup
6 3 module Gitlab
7 4 class Gitolite
8 5 class AccessDenied < StandardError; end
9   - class InvalidKey < StandardError; end
  6 +
  7 + def config
  8 + @config ||= Gitlab::GitoliteConfig.new
  9 + end
10 10  
11 11 def set_key key_id, key_content, projects
12   - configure do |c|
13   - c.update_keys(key_id, key_content)
14   - c.update_projects(projects)
  12 + config.apply do |config|
  13 + config.write_key(key_id, key_content)
  14 + config.update_projects(projects)
15 15 end
16 16 end
17 17  
18 18 def remove_key key_id, projects
19   - configure do |c|
20   - c.delete_key(key_id)
21   - c.update_projects(projects)
  19 + config.apply do |config|
  20 + config.rm_key(key_id)
  21 + config.update_projects(projects)
22 22 end
23 23 end
24 24  
25 25 def update_repository project
26   - configure do |c|
27   - c.update_project(project.path, project)
28   - end
  26 + config.update_project!(project.path, project)
29 27 end
30 28  
31   - alias_method :create_repository, :update_repository
32   -
33 29 def remove_repository project
34   - configure do |c|
35   - c.destroy_project(project)
36   - end
  30 + config.destroy_project!(project)
37 31 end
38 32  
39 33 def url_to_repo path
40 34 Gitlab.config.ssh_path + "#{path}.git"
41 35 end
42 36  
43   - def initialize
44   - # create tmp dir
45   - @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
46   - end
47   -
48 37 def enable_automerge
49   - configure do |git|
50   - git.admin_all_repo
51   - end
52   - end
53   -
54   - protected
55   -
56   - def destroy_project(project)
57   - FileUtils.rm_rf(project.path_to_repo)
58   -
59   - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
60   - conf = ga_repo.config
61   - conf.rm_repo(project.path)
62   - ga_repo.save
63   - end
64   -
65   - #update or create
66   - def update_keys(user, key)
67   - File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
68   - end
69   -
70   - def delete_key(user)
71   - File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"))
72   - `cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub`
73   - end
74   -
75   - # update or create
76   - def update_project(repo_name, project)
77   - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
78   - conf = ga_repo.config
79   - repo = update_project_config(project, conf)
80   - conf.add_repo(repo, true)
81   -
82   - ga_repo.save
83   - end
84   -
85   - # Updates many projects and uses project.path as the repo path
86   - # An order of magnitude faster than update_project
87   - def update_projects(projects)
88   - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
89   - conf = ga_repo.config
90   -
91   - projects.each do |project|
92   - repo = update_project_config(project, conf)
93   - conf.add_repo(repo, true)
94   - end
95   -
96   - ga_repo.save
97   - end
98   -
99   - def update_project_config(project, conf)
100   - repo_name = project.path
101   -
102   - repo = if conf.has_repo?(repo_name)
103   - conf.get_repo(repo_name)
104   - else
105   - ::Gitolite::Config::Repo.new(repo_name)
106   - end
107   -
108   - name_readers = project.repository_readers
109   - name_writers = project.repository_writers
110   - name_masters = project.repository_masters
111   -
112   - pr_br = project.protected_branches.map(&:name).join("$ ")
113   -
114   - repo.clean_permissions
115   -
116   - # Deny access to protected branches for writers
117   - unless name_writers.blank? || pr_br.blank?
118   - repo.add_permission("-", pr_br.strip + "$ ", name_writers)
119   - end
120   -
121   - # Add read permissions
122   - repo.add_permission("R", "", name_readers) unless name_readers.blank?
123   -
124   - # Add write permissions
125   - repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
126   - repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
127   -
128   - repo
129   - end
130   -
131   - def admin_all_repo
132   - ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
133   - conf = ga_repo.config
134   - owner_name = ""
135   -
136   - # Read gitolite-admin user
137   - #
138   - begin
139   - repo = conf.get_repo("gitolite-admin")
140   - owner_name = repo.permissions[0]["RW+"][""][0]
141   - raise StandardError if owner_name.blank?
142   - rescue => ex
143   - puts "Can't determine gitolite-admin owner".red
144   - raise StandardError
145   - end
146   -
147   - # @ALL repos premission for gitolite owner
148   - repo_name = "@all"
149   - repo = if conf.has_repo?(repo_name)
150   - conf.get_repo(repo_name)
151   - else
152   - ::Gitolite::Config::Repo.new(repo_name)
153   - end
154   -
155   - repo.add_permission("RW+", "", owner_name)
156   - conf.add_repo(repo, true)
157   - ga_repo.save
  38 + config.admin_all_repo!(project)
158 39 end
159 40  
160   - private
161   -
162   - def pull
163   - # create tmp dir
164   - @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
165   - Dir.mkdir @local_dir
166   -
167   - `git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite`
168   - end
169   -
170   - def push
171   - Dir.chdir(File.join(@local_dir, "gitolite"))
172   - `git add -A`
173   - `git commit -am "GitLab"`
174   - `git push`
175   - Dir.chdir(Rails.root)
176   -
177   - FileUtils.rm_rf(@local_dir)
178   - end
179   -
180   - def configure
181   - Timeout::timeout(30) do
182   - File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
183   - begin
184   - f.flock(File::LOCK_EX)
185   - pull
186   - yield(self)
187   - push
188   - ensure
189   - f.flock(File::LOCK_UN)
190   - end
191   - end
192   - end
193   - rescue Exception => ex
194   - if ex.message =~ /is not a valid SSH key string/
195   - raise Gitolite::InvalidKey.new("ssh key is not valid")
196   - else
197   - Gitlab::Logger.error(ex.message)
198   - raise Gitolite::AccessDenied.new("gitolite timeout")
199   - end
200   - end
  41 + alias_method :create_repository, :update_repository
201 42 end
202 43 end
... ...
lib/gitlab/backend/gitolite_config.rb 0 → 100644
... ... @@ -0,0 +1,168 @@
  1 +require 'gitolite'
  2 +require 'timeout'
  3 +require 'fileutils'
  4 +
  5 +module Gitlab
  6 + class GitoliteConfig
  7 + def config_tmp_dir
  8 + @config_tmp_dir ||= File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
  9 + end
  10 +
  11 + def apply
  12 + Timeout::timeout(30) do
  13 + File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
  14 + begin
  15 + f.flock(File::LOCK_EX)
  16 + pull
  17 + yield(self)
  18 + push
  19 + ensure
  20 + f.flock(File::LOCK_UN)
  21 + end
  22 + end
  23 + end
  24 + rescue Exception => ex
  25 + Gitlab::Logger.error(ex.message)
  26 + raise Gitolite::AccessDenied.new("gitolite timeout")
  27 + end
  28 +
  29 + def destroy_project(project)
  30 + FileUtils.rm_rf(project.path_to_repo)
  31 +
  32 + ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
  33 + conf = ga_repo.config
  34 + conf.rm_repo(project.path)
  35 + ga_repo.save
  36 + end
  37 +
  38 + def destroy_project!(project)
  39 + apply do |config|
  40 + config.destroy_project(project)
  41 + end
  42 + end
  43 +
  44 + def write_key(id, key)
  45 + File.open(File.join(config_tmp_dir, 'gitolite/keydir',"#{id}.pub"), 'w') do |f|
  46 + f.write(key.gsub(/\n/,''))
  47 + end
  48 + end
  49 +
  50 + def rm_key(user)
  51 + File.unlink(File.join(config_tmp_dir, 'gitolite/keydir',"#{user}.pub"))
  52 + `cd #{File.join(config_tmp_dir,'gitolite')} ; git rm keydir/#{user}.pub`
  53 + end
  54 +
  55 + # update or create
  56 + def update_project(repo_name, project)
  57 + ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
  58 + conf = ga_repo.config
  59 + repo = update_project_config(project, conf)
  60 + conf.add_repo(repo, true)
  61 +
  62 + ga_repo.save
  63 + end
  64 +
  65 + def update_project!(repo_name, project)
  66 + apply do |config|
  67 + config.update_project(repo_name, project)
  68 + end
  69 + end
  70 +
  71 + # Updates many projects and uses project.path as the repo path
  72 + # An order of magnitude faster than update_project
  73 + def update_projects(projects)
  74 + ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
  75 + conf = ga_repo.config
  76 +
  77 + projects.each do |project|
  78 + repo = update_project_config(project, conf)
  79 + conf.add_repo(repo, true)
  80 + end
  81 +
  82 + ga_repo.save
  83 + end
  84 +
  85 + def update_project_config(project, conf)
  86 + repo_name = project.path
  87 +
  88 + repo = if conf.has_repo?(repo_name)
  89 + conf.get_repo(repo_name)
  90 + else
  91 + ::Gitolite::Config::Repo.new(repo_name)
  92 + end
  93 +
  94 + name_readers = project.repository_readers
  95 + name_writers = project.repository_writers
  96 + name_masters = project.repository_masters
  97 +
  98 + pr_br = project.protected_branches.map(&:name).join("$ ")
  99 +
  100 + repo.clean_permissions
  101 +
  102 + # Deny access to protected branches for writers
  103 + unless name_writers.blank? || pr_br.blank?
  104 + repo.add_permission("-", pr_br.strip + "$ ", name_writers)
  105 + end
  106 +
  107 + # Add read permissions
  108 + repo.add_permission("R", "", name_readers) unless name_readers.blank?
  109 +
  110 + # Add write permissions
  111 + repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
  112 + repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
  113 +
  114 + repo
  115 + end
  116 +
  117 + def admin_all_repo
  118 + ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
  119 + conf = ga_repo.config
  120 + owner_name = ""
  121 +
  122 + # Read gitolite-admin user
  123 + #
  124 + begin
  125 + repo = conf.get_repo("gitolite-admin")
  126 + owner_name = repo.permissions[0]["RW+"][""][0]
  127 + raise StandardError if owner_name.blank?
  128 + rescue => ex
  129 + puts "Can't determine gitolite-admin owner".red
  130 + raise StandardError
  131 + end
  132 +
  133 + # @ALL repos premission for gitolite owner
  134 + repo_name = "@all"
  135 + repo = if conf.has_repo?(repo_name)
  136 + conf.get_repo(repo_name)
  137 + else
  138 + ::Gitolite::Config::Repo.new(repo_name)
  139 + end
  140 +
  141 + repo.add_permission("RW+", "", owner_name)
  142 + conf.add_repo(repo, true)
  143 + ga_repo.save
  144 + end
  145 +
  146 + def admin_all_repo!
  147 + apply { |config| config.admin_all_repo }
  148 + end
  149 +
  150 + private
  151 +
  152 + def pull
  153 + Dir.mkdir config_tmp_dir
  154 + `git clone #{Gitlab.config.gitolite_admin_uri} #{config_tmp_dir}/gitolite`
  155 + end
  156 +
  157 + def push
  158 + Dir.chdir(File.join(config_tmp_dir, "gitolite"))
  159 + `git add -A`
  160 + `git commit -am "GitLab"`
  161 + `git push`
  162 + Dir.chdir(Rails.root)
  163 +
  164 + FileUtils.rm_rf(config_tmp_dir)
  165 + end
  166 + end
  167 +end
  168 +
... ...
spec/lib/gitolite_config_spec.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::GitoliteConfig do
  4 + let(:gitolite) { Gitlab::GitoliteConfig.new }
  5 +
  6 + it { should respond_to :write_key }
  7 + it { should respond_to :rm_key }
  8 + it { should respond_to :update_project }
  9 + it { should respond_to :update_project! }
  10 + it { should respond_to :update_projects }
  11 + it { should respond_to :destroy_project }
  12 + it { should respond_to :destroy_project! }
  13 + it { should respond_to :apply }
  14 + it { should respond_to :admin_all_repo }
  15 + it { should respond_to :admin_all_repo! }
  16 +end
... ...
spec/lib/gitolite_spec.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::Gitolite do
  4 + let(:project) { double('Project', path: 'diaspora') }
  5 + let(:gitolite_config) { double('Gitlab::GitoliteConfig') }
  6 + let(:gitolite) { Gitlab::Gitolite.new }
  7 +
  8 + before do
  9 + gitolite.stub(config: gitolite_config)
  10 + end
  11 +
  12 + it { should respond_to :set_key }
  13 + it { should respond_to :remove_key }
  14 +
  15 + it { should respond_to :update_repository }
  16 + it { should respond_to :create_repository }
  17 + it { should respond_to :remove_repository }
  18 +
  19 + it { gitolite.url_to_repo('diaspora').should == Gitlab.config.ssh_path + "diaspora.git" }
  20 +
  21 + it "should call config update" do
  22 + gitolite_config.should_receive(:update_project!)
  23 + gitolite.update_repository project
  24 + end
  25 +end
... ...
spec/support/gitolite_stub.rb
... ... @@ -17,7 +17,7 @@ module GitoliteStub
17 17 )
18 18  
19 19 gitolite_admin = double(
20   - 'Gitolite::GitoliteAdmin',
  20 + 'Gitolite::GitoliteAdmin',
21 21 config: gitolite_config,
22 22 save: true,
23 23 )
... ... @@ -27,9 +27,21 @@ module GitoliteStub
27 27 end
28 28  
29 29 def stub_gitlab_gitolite
30   - gitlab_gitolite = Gitlab::Gitolite.new
31   - Gitlab::Gitolite.stub(new: gitlab_gitolite)
32   - gitlab_gitolite.stub(configure: ->() { yield(self) })
33   - gitlab_gitolite.stub(update_keys: true)
  30 + gitolite_config = double('Gitlab::GitoliteConfig')
  31 + gitolite_config.stub(
  32 + apply: ->() { yield(self) },
  33 + write_key: true,
  34 + rm_key: true,
  35 + update_projects: true,
  36 + update_project: true,
  37 + update_project!: true,
  38 + destroy_project: true,
  39 + destroy_project!: true,
  40 + admin_all_repo: true,
  41 + admin_all_repo!: true,
  42 +
  43 + )
  44 +
  45 + Gitlab::GitoliteConfig.stub(new: gitolite_config)
34 46 end
35 47 end
... ...