2 # Copyright (C) The Arvados Authors. All rights reserved.
4 # SPDX-License-Identifier: AGPL-3.0
13 # This script does the actual gitolite config management on disk.
15 # Ward Vandewege <ward@curii.com>
17 # Default is development
18 production = ARGV[0] == "production"
20 ENV["RAILS_ENV"] = "development"
21 ENV["RAILS_ENV"] = "production" if production
25 # load and merge in the environment-specific application config info
26 # if present, overriding base config parameters as specified
27 path = File.absolute_path('../../config/arvados-clients.yml', __FILE__)
28 if File.exist?(path) then
29 cp_config = File.open(path) do |f|
30 YAML.safe_load(f, filename: path)[ENV['RAILS_ENV']]
33 puts "Please create a\n #{path}\n file"
37 gitolite_url = cp_config['gitolite_url']
38 gitolite_arvados_git_user_key = cp_config['gitolite_arvados_git_user_key']
40 gitolite_tmpdir = cp_config['gitolite_tmp']
41 gitolite_admin = File.join(gitolite_tmpdir, 'gitolite-admin')
42 gitolite_admin_keydir = File.join(gitolite_admin, 'keydir')
43 gitolite_keydir = File.join(gitolite_admin, 'keydir', 'arvados')
45 ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
46 ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
47 if cp_config['arvados_api_host_insecure']
48 ENV['ARVADOS_API_HOST_INSECURE'] = 'true'
50 ENV.delete('ARVADOS_API_HOST_INSECURE')
53 def ensure_directory(path, mode)
60 def replace_file(path, contents)
62 dirname, basename = File.split(path)
63 FileUtils.mkpath(dirname)
64 new_file = Tempfile.new([basename, ".tmp"], dirname)
66 new_file.write(contents)
68 File.rename(new_file, path)
71 new_file.close(unlink_now)
75 def file_has_contents?(path, contents)
77 IO.read(path) == contents
83 module TrackCommitState
85 # Note that all classes that include TrackCommitState will have
86 # @@need_commit = true if any of them set it. Since this flag reports
87 # a boolean state of the underlying git repository, that's OK in the
88 # current implementation.
95 def ensure_in_git(path, contents)
96 unless file_has_contents?(path, contents)
97 replace_file(path, contents)
98 system("git", "add", path)
104 def ensure_in_git(path, contents)
105 self.class.ensure_in_git(path, contents)
108 def self.included(base)
109 base.extend(ClassMethods)
114 include TrackCommitState
116 def initialize(user_keys_map, key_dir)
117 @user_keys_map = user_keys_map
122 def install(filename, pubkey)
124 key_path = File.join(@key_dir, filename)
125 ensure_in_git(key_path, pubkey)
127 @installed[filename] = true
130 def ensure_keys_for_user(user_uuid)
131 return unless key_list = @user_keys_map.delete(user_uuid)
132 key_list.map { |k| k[:public_key] }.compact.each_with_index do |pubkey, ii|
133 # Handle putty-style ssh public keys
134 pubkey.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1')
135 pubkey.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1')
136 pubkey.gsub!(/\n/,'')
138 install("#{user_uuid}@#{ii}.pub", pubkey)
142 def installed?(filename)
148 include TrackCommitState
152 def initialize(arv_repo, user_keys)
154 @user_keys = user_keys
157 def self.ensure_system_config(conf_root)
158 ensure_in_git(File.join(conf_root, "conf", "gitolite.conf"),
159 %Q{include "auto/*.conf"\ninclude "admin/*.conf"\n})
160 ensure_in_git(File.join(conf_root, "arvadosaliases.pl"), alias_config)
162 conf_path = File.join(conf_root, "conf", "admin", "arvados.conf")
164 @arvados_git_user = arvados_git_user
167 RW = @arvados_git_user
170 ensure_directory(File.dirname(conf_path), 0755)
171 ensure_in_git(conf_path, conf_file)
174 def ensure_config(conf_root)
175 if name and (File.exist?(auto_conf_path(conf_root, name)))
176 # This gitolite installation knows the repository by name, rather than
177 # UUID. Leave it configured that way until a separate migration is run.
181 @@aliases[name] = uuid unless name.nil?
183 conf_file = "\nrepo #{basename}\n"
184 @arv_repo[:user_permissions].sort.each do |user_uuid, perm|
185 conf_file += "\t#{perm[:gitolite_permissions]}\t= #{user_uuid}\n"
186 @user_keys.ensure_keys_for_user(user_uuid)
188 ensure_in_git(auto_conf_path(conf_root, basename), conf_file)
193 def auto_conf_path(conf_root, basename)
194 File.join(conf_root, "conf", "auto", "#{basename}.conf")
202 if @arv_repo[:name].nil?
206 @arv_repo[:name].sub(/^[^A-Za-z]+/, "").gsub(/[^\w\.\/]/, "")
210 def self.alias_config
212 @@aliases.sort.each do |(repo_name, repo_uuid)|
213 conf_s += "\t'#{repo_name}' \t=> '#{repo_uuid}',\n"
221 # Get our local gitolite-admin repo up to snuff
222 if not File.exist?(gitolite_admin) then
223 ensure_directory(gitolite_tmpdir, 0700)
224 Dir.chdir(gitolite_tmpdir)
225 `git clone #{gitolite_url}`
226 Dir.chdir(gitolite_admin)
228 Dir.chdir(gitolite_admin)
233 permissions = arv.repository.get_all_permissions
235 ensure_directory(gitolite_keydir, 0700)
236 admin_user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_admin_keydir)
237 # Make sure the arvados_git_user key is installed; put it in gitolite_admin_keydir
238 # because that is where gitolite will try to put it if we do not.
239 admin_user_ssh_keys.install('arvados_git_user.pub', gitolite_arvados_git_user_key)
241 user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_keydir)
242 permissions[:repositories].each do |repo_record|
243 repo = Repository.new(repo_record, user_ssh_keys)
244 repo.ensure_config(gitolite_admin)
246 Repository.ensure_system_config(gitolite_admin)
248 # Clean up public key files that should not be present
249 Dir.chdir(gitolite_keydir)
250 stale_keys = Dir.glob('*.pub').reject do |key_file|
251 user_ssh_keys.installed?(key_file)
254 stale_keys.each { |key_file| puts "Extra file #{key_file}" }
255 system("git", "rm", "--quiet", *stale_keys)
258 if UserSSHKeys.changed? or Repository.changed? or stale_keys.any?
259 message = "#{Time.now().to_s}: update from API"
260 Dir.chdir(gitolite_admin)
262 `git commit -m '#{message}'`
267 puts "Error: " + bang.to_s
268 puts bang.backtrace.join("\n")