gitgolem 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|