gitgolem 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.yardopts +2 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +190 -0
- data/Rakefile +49 -0
- data/TODO +24 -0
- data/bin/golem +3 -0
- data/bin/golem-shell +4 -0
- data/lib/gitgolem.rb +1 -0
- data/lib/golem.rb +21 -0
- data/lib/golem/access.rb +45 -0
- data/lib/golem/command.rb +107 -0
- data/lib/golem/command/auth.rb +27 -0
- data/lib/golem/command/clear_repositories.rb +16 -0
- data/lib/golem/command/create_repository.rb +20 -0
- data/lib/golem/command/delete_repository.rb +14 -0
- data/lib/golem/command/environment.rb +11 -0
- data/lib/golem/command/save_config.rb +11 -0
- data/lib/golem/command/setup_db.rb +11 -0
- data/lib/golem/command/update_hooks.rb +14 -0
- data/lib/golem/command/update_keys_file.rb +39 -0
- data/lib/golem/config.rb +112 -0
- data/lib/golem/db.rb +46 -0
- data/lib/golem/db/pg.rb +69 -0
- data/lib/golem/db/postgres.sql +19 -0
- data/lib/golem/db/static.rb +66 -0
- data/lib/golem/parser.rb +47 -0
- data/lib/golem/version.rb +13 -0
- data/test/helper.rb +53 -0
- data/test/test_access.rb +31 -0
- data/test/test_config.rb +16 -0
- data/test/test_db.rb +12 -0
- metadata +192 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# Command for authorization. Checks authenticated (via sshd) user's access to given repository, if access granted creates repository if needed and calls git-shell.
|
2
|
+
class Golem::Command::Auth < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "user (defaults to GOLEM_USER env)\nused for authorizing users, called by sshd (via keys file), calls git-shell"
|
5
|
+
# Regexp to check for git commands.
|
6
|
+
RE_CMD = /\A\s*(git[ \-](upload-pack|upload-archive|receive-pack))\s+'([^.]+).git'/
|
7
|
+
|
8
|
+
# Run the command. Git command is read from ENV['SSH_ORIGINAL_COMMAND']. Set environment variables (for hooks) and call git-shell if access granted.
|
9
|
+
# @param [String] user the user to run as.
|
10
|
+
def run(user = ENV['GOLEM_USER'])
|
11
|
+
abort 'Please use git!' unless matches = (ENV['SSH_ORIGINAL_COMMAND'] || '').match(RE_CMD)
|
12
|
+
cmd, subcmd, repo = matches[1, 3]
|
13
|
+
abort 'You don\'t have permission!' unless Golem::Access.check(user, repo, subcmd)
|
14
|
+
command(:create_repository, repo) unless File.directory?(Golem::Config.repository_path(repo))
|
15
|
+
set_env(user, repo)
|
16
|
+
exec "git-shell", "-c", "#{cmd} '#{ENV['GOLEM_REPOSITORY_PATH']}'"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def set_env(user, repo)
|
21
|
+
{
|
22
|
+
'GOLEM_USER' => user,
|
23
|
+
'GOLEM_REPOSITORY_NAME' => repo,
|
24
|
+
'GOLEM_REPOSITORY_PATH' => Golem::Config.repository_path(repo),
|
25
|
+
}.each {|k, v| ENV[k] = v}
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Command for clearing repositories, suitable for cron.
|
2
|
+
class Golem::Command::ClearRepositories < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "\nclear .git directories not found in database"
|
5
|
+
|
6
|
+
# Run the command. Removes every '*.git' directory in {Golem::Config.repository_base_path}
|
7
|
+
# unless {Golem::Access.repositories} includes repository. Calls {Golem::Command::DeleteRepository}.
|
8
|
+
def run
|
9
|
+
repos = Golem::Access.repositories
|
10
|
+
Dir.glob(Golem::Config.repository_base_path + '/*.git').each do |repo_path|
|
11
|
+
repo = File.basename(repo_path)[0..-5]
|
12
|
+
next if repos.include?(repo)
|
13
|
+
command :delete_repository, repo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Command for creating a repository.
|
2
|
+
class Golem::Command::CreateRepository < Golem::Command::Base
|
3
|
+
include Golem::Command::ManageHooks
|
4
|
+
# @private
|
5
|
+
USAGE = "name\ncreate a repository and install hooks"
|
6
|
+
|
7
|
+
# Run the command. Installs hooks with {#install_hooks}.
|
8
|
+
# @param [String] name repository name.
|
9
|
+
def run(name)
|
10
|
+
path = Golem::Config.repository_path(name)
|
11
|
+
abort "Repository already exists!" if File.directory?(path)
|
12
|
+
pwd = Dir.pwd
|
13
|
+
Dir.mkdir(path, 0700)
|
14
|
+
Dir.chdir(path)
|
15
|
+
system('git --bare init ' + (verbose? ? '>&2' : '>/dev/null 2>&1'))
|
16
|
+
print "Repository #{path} created, installing hooks...\n" if verbose?
|
17
|
+
install_hooks(name)
|
18
|
+
Dir.chdir(pwd)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Command for deleting a repository.
|
2
|
+
class Golem::Command::DeleteRepository < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "name\ndelete a specific repository"
|
5
|
+
|
6
|
+
# Run the command.
|
7
|
+
# @param [String] name repository name.
|
8
|
+
def run(name)
|
9
|
+
repo_path = Golem::Config.repository_path(name)
|
10
|
+
abort 'Repository not found!' unless File.directory?(repo_path)
|
11
|
+
system("rm -rf #{repo_path}")
|
12
|
+
print "Removed repository #{repo_path}\n" if verbose?
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Command for listing configuration variables.
|
2
|
+
class Golem::Command::Environment < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "\nlist configuration values"
|
5
|
+
|
6
|
+
# List configuration variables that are set.
|
7
|
+
def run
|
8
|
+
print "Configuration values:\n"
|
9
|
+
print Golem::Config.config_hash.collect {|k, v| "\t " + k.to_s + ": " + v.to_s}.join("\n") + "\n"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Command to save configuration file.
|
2
|
+
class Golem::Command::SaveConfig < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "\nsave the configuration file\nPLEASE NOTE: this may destroy (overwrite) your old config (and thus destroy your static database)"
|
5
|
+
|
6
|
+
# Run the command. Calls {Golem::Config.save!}.
|
7
|
+
def run
|
8
|
+
Golem::Config.save!
|
9
|
+
print "Config was saved to #{Golem::Config.cfg_path.to_s}\n" if verbose?
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Command to setup the database schema (only useful for {Golem::DB::Pg}).
|
2
|
+
class Golem::Command::SetupDb < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "\nsetup database schema\nPLEASE NOTE: this is useful for postgres database only"
|
5
|
+
|
6
|
+
# Run the command. Calls {Golem::DB.setup}.
|
7
|
+
def run
|
8
|
+
Golem::DB.setup
|
9
|
+
print "Database schema is set up at #{Golem::Config.db.to_s}\n" if verbose?
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Command for updating hooks in repositories.
|
2
|
+
class Golem::Command::UpdateHooks < Golem::Command::Base
|
3
|
+
include Golem::Command::ManageHooks
|
4
|
+
# @private
|
5
|
+
USAGE = "\nupdate hooks in every repository (please note: deletes old hooks and symlinks new ones)"
|
6
|
+
|
7
|
+
# Run the command. It runs {#clear_hooks} and {#install_hooks} on every repository.
|
8
|
+
def run
|
9
|
+
Golem::Access.repositories.each do |repo|
|
10
|
+
clear_hooks(repo)
|
11
|
+
install_hooks(repo)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Command for updating the .ssh/authorized_keys file.
|
2
|
+
class Golem::Command::UpdateKeysFile < Golem::Command::Base
|
3
|
+
# @private
|
4
|
+
USAGE = "\nupdate authorized_keys file with values from database"
|
5
|
+
# Content mark to identify automatically updated part of file.
|
6
|
+
CONTENT_MARK = "# golem keys - do not place lines below, because the content gets rewritten (AND DO NOT EDIT THIS LINE!)"
|
7
|
+
# Default SSH(D) options to set if using command="" style keys file.
|
8
|
+
SSH_OPTS_COMMAND_DEFAULT = "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty"
|
9
|
+
|
10
|
+
# Run the command. Old content is preserved, searched for {CONTENT_MARK}, and only content after the mark gets replaced.
|
11
|
+
def run
|
12
|
+
orig_content = File.exists?(Golem::Config.keys_file_path) ? File.read(Golem::Config.keys_file_path) : ""
|
13
|
+
new_content = if orig_content.match(Regexp.new('^' + Regexp.escape(CONTENT_MARK) + '$'))
|
14
|
+
orig_content.sub(Regexp.new('^' + Regexp.escape(CONTENT_MARK) + '$.*\z', Regexp::MULTILINE), CONTENT_MARK + "\n" + keys_str)
|
15
|
+
else
|
16
|
+
orig_content + "\n" + CONTENT_MARK + "\n" + keys_str
|
17
|
+
end
|
18
|
+
File.open(Golem::Config.keys_file_path, "w") {|f| f.write(new_content)}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def keys_str
|
23
|
+
Golem::Access.ssh_keys.collect {|user, keys| keys.collect {|key| keys_file_line(user, key)}.join("\n")}.join("\n") + "\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def keys_file_line(user, key)
|
27
|
+
first_part = if Golem::Config.keys_file_use_command
|
28
|
+
"command=\"#{Golem::Config.bin_dir + '/golem'} auth '#{user}'\""
|
29
|
+
else
|
30
|
+
"environment=\"GOLEM_USER=#{user}\""
|
31
|
+
end
|
32
|
+
ssh_opts = if Golem::Config.keys_file_ssh_opts.nil?
|
33
|
+
Golem::Config.keys_file_use_command ? ",#{SSH_OPTS_COMMAND_DEFAULT}" : ""
|
34
|
+
else
|
35
|
+
"," + Golem::Config.keys_file_ssh_opts.to_s
|
36
|
+
end
|
37
|
+
"#{first_part}#{ssh_opts} #{key}"
|
38
|
+
end
|
39
|
+
end
|
data/lib/golem/config.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Configuration management.
|
2
|
+
module Golem::Config
|
3
|
+
# List of paths config file is searched for.
|
4
|
+
CFG_PATHS = ["/usr/local/etc/golem/golem.conf.rb", "/usr/local/etc/golem.conf.rb", "/etc/golem/golem.conf.rb", "/etc/golem.conf.rb", "~/golem.conf.rb"]
|
5
|
+
# List of available config variable names.
|
6
|
+
CFG_VARS = [:db, :user_home, :repository_dir, :cfg_path, :base_dir, :bin_dir, :hooks_dir, :keys_file_use_command, :keys_file_ssh_opts]
|
7
|
+
|
8
|
+
# Auto configure Golem. Tries to find config file, if one can be found executes it, otherwise calls {configure}.
|
9
|
+
# @param [String] path path to config file.
|
10
|
+
def self.auto_configure(path = nil, &block)
|
11
|
+
path = if ENV.key?('GOLEM_CONFIG') && File.exists?(ENV['GOLEM_CONFIG'])
|
12
|
+
ENV['GOLEM_CONFIG']
|
13
|
+
elsif ENV.key?('GOLEM_BASE') && File.exists?(ENV['GOLEM_BASE'].to_s + "/golem.conf.rb")
|
14
|
+
ENV['GOLEM_BASE'].to_s + "/golem.conf.rb"
|
15
|
+
else
|
16
|
+
CFG_PATHS.find {|try_path| File.exists?(try_path)}
|
17
|
+
end unless File.exists?(path.to_s)
|
18
|
+
if File.exists?(path.to_s)
|
19
|
+
@auto_configure_path = path.to_s
|
20
|
+
@auto_configure_block = block
|
21
|
+
require path.to_s
|
22
|
+
end
|
23
|
+
configure path unless @vars #configure was not called or there was no config file
|
24
|
+
end
|
25
|
+
|
26
|
+
# Configure Golem with options given as argument, yield self then setting defaults.
|
27
|
+
# @overload configure(path, &block)
|
28
|
+
# @param [String] path path to config file (interpreted as <i>:cfg_path => path</i>).
|
29
|
+
# @overload configure(opts, &block)
|
30
|
+
# @param [Hash] opts options or single path .
|
31
|
+
# @option opts [String] :db db configuration (postgres url or 'static'),
|
32
|
+
# @option opts [String] :user_home (ENV['HOME']) path to user's home directory (needed to place .ssh/authorized_keys),
|
33
|
+
# @option opts [String] :repository_dir (user_home + '/repositories') path to repositories, may be relative to +user_home+,
|
34
|
+
# @option opts [String] :cfg_path (base_dir + '/golem.conf.rb') path config file,
|
35
|
+
# @option opts [String] :base_dir path to base, defaults to in order ENV['GOLEM_BASE'], basedir of config file (if exists), basedir of library,
|
36
|
+
# @option opts [String] :bin_dir (base_dir + '/bin') path to directory containing the executables,
|
37
|
+
# @option opts [String] :hooks_dir (base_dir + '/bin') path to directory containing hooks,
|
38
|
+
# @option opts [Boolean] :keys_file_use_command controls (false) the .ssh/authorized_keys file syntax (<i>command=""_ or _environment=""</i>), see {file:README#keys_file authorized_keys},
|
39
|
+
# @option opts [String] :keys_file_ssh_opts (nil) the ssh options to set in .ssh/authorized_keys file, see {file:README#keys_file authorized_keys}.
|
40
|
+
# @return [Config] self.
|
41
|
+
def self.configure(opts_or_path = nil, &block)
|
42
|
+
opts = opts_or_path.is_a?(Hash) ? opts_or_path : {:cfg_path => opts_or_path}
|
43
|
+
opts[:cfg_path] = @auto_configure_path if @auto_configure_path
|
44
|
+
@vars = opts.reject {|k, v| ! CFG_VARS.include?(k)}
|
45
|
+
@auto_configure_block.call(self) if @auto_configure_block
|
46
|
+
yield self if block_given?
|
47
|
+
self.user_home = ENV['HOME'] if user_home.nil? && ENV.key?('HOME')
|
48
|
+
self.repository_dir = user_home + "/repositories" unless repository_dir
|
49
|
+
unless base_dir
|
50
|
+
self.base_dir = if ENV.key?('GOLEM_BASE')
|
51
|
+
ENV['GOLEM_BASE']
|
52
|
+
elsif File.exists?(cfg_path.to_s)
|
53
|
+
File.dirname(cfg_path.to_s)
|
54
|
+
else
|
55
|
+
File.expand_path(File.dirname(__FILE__) + '/../..')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
self.cfg_path = base_dir + '/golem.conf.rb' unless cfg_path
|
59
|
+
self.bin_dir = base_dir + '/bin' unless bin_dir
|
60
|
+
self.hooks_dir = base_dir + '/hooks' unless hooks_dir
|
61
|
+
self.keys_file_use_command = false unless keys_file_use_command
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Override +respond_to?+ to respond to +.config_var+ and +.config_var=+ (e.g. Golem::Config.db = 'static').
|
66
|
+
def self.respond_to?(sym)
|
67
|
+
CFG_VARS.include?(sym) || (sym.to_s.match(/=\z/) && CFG_VARS.include?(sym.to_s[0..-2].to_sym)) || super
|
68
|
+
end
|
69
|
+
|
70
|
+
# Override +method_missing+ to handle +.config_var+ and +.config_var=+ (e.g. Golem::Config.db = 'static').
|
71
|
+
def self.method_missing(sym, *args, &block)
|
72
|
+
auto_configure unless @vars
|
73
|
+
return @vars[sym] if CFG_VARS.include?(sym)
|
74
|
+
return @vars[sym.to_s[0..-2].to_sym] = args.first if sym.to_s.match(/=\z/) && CFG_VARS.include?(sym.to_s[0..-2].to_sym)
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get configuration variables that is set (e.g. not +nil+).
|
79
|
+
# @return [Hash] configuration variables.
|
80
|
+
def self.config_hash
|
81
|
+
auto_configure unless @vars
|
82
|
+
@vars.reject {|k, v| v.nil?}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Write configuration to file.
|
86
|
+
def self.save!
|
87
|
+
abort "No configuration path given!" unless cfg_path
|
88
|
+
File.open(cfg_path, 'w') {|f| f.write("Golem.configure do |cfg|\n" + config_hash.collect {|k, v| "\tcfg.#{k.to_s} = \"#{v.to_s}\""}.join("\n") + "\nend\n")}
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] path to +authorized_keys+ file.
|
92
|
+
def self.keys_file_path
|
93
|
+
user_home + "/.ssh/authorized_keys"
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] path to directory containing repositories.
|
97
|
+
def self.repository_base_path
|
98
|
+
(repository_dir[0..0] == "/" ? '' : user_home + '/') + repository_dir
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param [String] repo repository name.
|
102
|
+
# @return [String] path to given repository.
|
103
|
+
def self.repository_path(repo)
|
104
|
+
repository_base_path + '/' + repo.to_s + '.git'
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param [String] hook hook name.
|
108
|
+
# @return [String] path to given hook.
|
109
|
+
def self.hook_path(hook)
|
110
|
+
hooks_dir + "/" + hook.to_s
|
111
|
+
end
|
112
|
+
end
|
data/lib/golem/db.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Database handling. See {Golem::DB::Pg} and {Golem::DB::Static}.
|
2
|
+
#
|
3
|
+
# A +db+ should respond to 4 methods: +users+, +repositories+, +ssh_keys+, +setup+.
|
4
|
+
# The first 3 should take a single hash argument (options) and return an array/hash of results, +setup+
|
5
|
+
# takes no arguments (it may use a block). These options should be supported:
|
6
|
+
# * +:fields+: list of fields the results should include,
|
7
|
+
# * +:return+: type of return value, if is +:array+ then results should be an array, hash (attribute name => value pairs) otherwise,
|
8
|
+
# * any other key: should be interpreted as conditions (e.g. <i>:user => "name"</i> should return objects whose +user+ attribute is _name_).
|
9
|
+
module Golem::DB
|
10
|
+
autoload :Pg, "golem/db/pg"
|
11
|
+
autoload :Static, "golem/db/static"
|
12
|
+
|
13
|
+
# Proxy for the used db.
|
14
|
+
# @return [Pg, Static] the db currently used.
|
15
|
+
def self.db
|
16
|
+
@db ||= case Golem::Config.db
|
17
|
+
when /\Apostgres:\/\// then Pg.new(Golem::Config.db)
|
18
|
+
when "static" then Static.new
|
19
|
+
else abort "Unknown DB (#{Golem::Config.db.to_s})."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Forwards to proxy's users.
|
24
|
+
# @return [Array, Hash] results.
|
25
|
+
def self.users(opts = {})
|
26
|
+
db.users(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Forwards to proxy's repositories.
|
30
|
+
# @return [Array, Hash] results.
|
31
|
+
def self.repositories(opts = {})
|
32
|
+
db.repositories(opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Forwards to proxy's ssh_keys.
|
36
|
+
# @return [Array, Hash] results.
|
37
|
+
def self.ssh_keys(opts = {})
|
38
|
+
db.ssh_keys(opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Forwards to proxy's setup.
|
42
|
+
# @return [] depends on proxy.
|
43
|
+
def self.setup(&block)
|
44
|
+
db.setup(&block)
|
45
|
+
end
|
46
|
+
end
|
data/lib/golem/db/pg.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pg'
|
2
|
+
|
3
|
+
# Postgres functionality. Requires +pg+.
|
4
|
+
class Golem::DB::Pg
|
5
|
+
# Initializes +PGConn+ connection.
|
6
|
+
# @param [String] db_url postgres url to connect to (e.g. +postgres://user:pw@host/db+).
|
7
|
+
def initialize(db_url)
|
8
|
+
@connection ||= ::PGconn.connect(*(db_url.match(/\Apostgres:\/\/([^:]+):([^@]+)@([^\/]+)\/(.+)\z/) {|m| [m[3], 5432, nil, nil, m[4], m[1], m[2]]}))
|
9
|
+
end
|
10
|
+
|
11
|
+
# Retrieve users.
|
12
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
13
|
+
# @return [Array] list of users.
|
14
|
+
def users(opts = {})
|
15
|
+
opts[:table] = :users
|
16
|
+
get(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Retrieve repositories.
|
20
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
21
|
+
# @return [Array] list of repotitories.
|
22
|
+
def repositories(opts = {})
|
23
|
+
opts[:table] = :repositories
|
24
|
+
get(opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieve ssh keys.
|
28
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
29
|
+
# @return [Array] list of keys.
|
30
|
+
def ssh_keys(opts = {})
|
31
|
+
opts[:table] = "keys join users on keys.user_name=users.name"
|
32
|
+
get(opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Setup schema.
|
36
|
+
# @return [PGRes] result.
|
37
|
+
def setup
|
38
|
+
@connection.exec(File.read(File.expand_path(File.dirname(__FILE__) + '/postgres.sql')))
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def get(opts = {})
|
43
|
+
table = opts.delete(:table) || ''
|
44
|
+
fields = opts.delete(:fields) || ['*']
|
45
|
+
fields = [fields] unless fields.is_a?(Array)
|
46
|
+
order = opts.delete(:order)
|
47
|
+
limit = opts.delete(:limit)
|
48
|
+
ret_array = opts.delete(:return) == :array
|
49
|
+
sql = "SELECT #{fields.collect {|f| f.to_s}.join(', ')} FROM #{table.to_s}"
|
50
|
+
sql += " WHERE #{opts.keys.enum_for(:each_with_index).collect {|k, i| k.to_s + ' = $' + (i + 1).to_s}.join(' AND ')}" if opts.length > 0
|
51
|
+
sql += " ORDER BY #{order.to_s}" if order
|
52
|
+
sql += " LIMIT #{limit.to_s}" if limit
|
53
|
+
res = @connection.exec(sql, opts.values)
|
54
|
+
ret_fields = fields === ['*'] ? res.fields : fields
|
55
|
+
ret = res.collect do |row|
|
56
|
+
if ret_array
|
57
|
+
v = ret_fields.collect {|f| row[f.to_s]}
|
58
|
+
v.length == 1 ? v.first : v
|
59
|
+
else
|
60
|
+
ret_fields.inject({}) do |memo, field|
|
61
|
+
memo[field.to_sym] = row[field.to_s]
|
62
|
+
memo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
res.clear
|
67
|
+
ret
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
CREATE TABLE users (
|
2
|
+
name varchar(32) NOT NULL PRIMARY KEY,
|
3
|
+
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
4
|
+
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
5
|
+
);
|
6
|
+
|
7
|
+
CREATE TABLE keys (
|
8
|
+
user_name varchar(32) NOT NULL REFERENCES users (name) ON DELETE no action ON UPDATE no action,
|
9
|
+
key varchar(1024) NOT NULL UNIQUE,
|
10
|
+
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
11
|
+
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
12
|
+
PRIMARY KEY (user_name, key)
|
13
|
+
);
|
14
|
+
|
15
|
+
CREATE TABLE repositories (
|
16
|
+
name varchar(32) NOT NULL PRIMARY KEY,
|
17
|
+
user_name varchar(32) NOT NULL REFERENCES users (name) ON DELETE no action ON UPDATE no action,
|
18
|
+
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
19
|
+
);
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Static database for small installations. To use it, write:
|
2
|
+
# Golem.configure do |cfg|
|
3
|
+
# cfg.db = 'static'
|
4
|
+
# Golem::DB.setup do |db|
|
5
|
+
# db.add_user 'test_user'
|
6
|
+
# db.add_repository 'test_repository', 'test_user'
|
7
|
+
# db.add_key 'test_user', 'test_key'
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
class Golem::DB::Static
|
11
|
+
# Create database, initialize users, repositories and ssh_keys to [].
|
12
|
+
def initialize
|
13
|
+
@users, @repositories, @ssh_keys = [], [], []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Retrieve users.
|
17
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
18
|
+
# @return [Array] list of users.
|
19
|
+
def users(opts = {})
|
20
|
+
opts[:return] == :array ? @users.collect {|u| u[:name]} : @users
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieve repositories.
|
24
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
25
|
+
# @return [Array] list of repotitories.
|
26
|
+
def repositories(opts = {})
|
27
|
+
opts[:return] == :array ? @repositories.collect {|r| r[:name]} : @repositories
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieve ssh keys.
|
31
|
+
# @param [Hash] opts options, see {Golem::DB}.
|
32
|
+
# @return [Array] list of keys.
|
33
|
+
def ssh_keys(opts = {})
|
34
|
+
opts[:return] == :array ? @ssh_keys.collect {|k| [k[:user_name], k[:key]]} : @ssh_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add user to database.
|
38
|
+
# @param [String] name username,
|
39
|
+
# @return [Array] list of users.
|
40
|
+
def add_user(name)
|
41
|
+
@users << {:name => name}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add repository to database.
|
45
|
+
# @param [String] name repository name,
|
46
|
+
# @param [String] user_name username.
|
47
|
+
# @return [Array] list of repositories.
|
48
|
+
def add_repository(name, user_name)
|
49
|
+
abort "Cannot add repository, user not found!" unless users(:return => :array).include?(user_name)
|
50
|
+
@repositories << {:name => name, :user_name => user_name}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add key to database.
|
54
|
+
# @param [String] user_name username,
|
55
|
+
# @param [String] key ssh key (e.g. +cat id_rsa.pub+).
|
56
|
+
# @return [Array] list of keys.
|
57
|
+
def add_key(user_name, key)
|
58
|
+
abort "Cannot add key, user not found!" unless users(:return => :array).include?(user_name)
|
59
|
+
@ssh_keys << {:user_name => user_name, :key => key}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Setup database.
|
63
|
+
def setup(&block)
|
64
|
+
yield self
|
65
|
+
end
|
66
|
+
end
|