hubbard 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +27 -0
- data/README.md +61 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/bin/hubbard +159 -0
- data/commands/add-key.rb +23 -0
- data/commands/add-permission.rb +23 -0
- data/commands/create-project.rb +14 -0
- data/commands/create-repository.rb +8 -0
- data/commands/delete-project.rb +4 -0
- data/commands/git-receive-pack.rb +9 -0
- data/commands/git-upload-pack.rb +9 -0
- data/commands/help.rb +21 -0
- data/commands/list-keys.rb +5 -0
- data/commands/list-permissions.rb +15 -0
- data/commands/list-projects.rb +6 -0
- data/commands/list-repositories.rb +7 -0
- data/commands/remove-key.rb +21 -0
- data/commands/remove-permission.rb +16 -0
- data/lib/hubbard.rb +0 -0
- data/spec/gitssh +12 -0
- data/spec/hubbard_spec.rb +183 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +93 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2010 Matthew Foemmel
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* The names of the contributors may not be used to endorse or promote
|
15
|
+
products derived from this software without specific prior written
|
16
|
+
permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Description
|
2
|
+
===========
|
3
|
+
|
4
|
+
Hubbard is a command line tool for managing shared git repositories in a team environment. It provides a security model so that users can easily control who has access to the projects they create.
|
5
|
+
|
6
|
+
Hubbard uses public SSH keys to keep track of who is executing what commands. This means you only have to create a single account on the server, instead of one per user.
|
7
|
+
|
8
|
+
Hubbards was heavily inspired by gitosis, another tool for managing git repositories. However, the goal of Hubbard was to place less burden on the system administrator by allowing users to manage permissions for their own projects.
|
9
|
+
|
10
|
+
How It Works
|
11
|
+
============
|
12
|
+
|
13
|
+
All comminication between users the the Hubbard server happens over SSH. Users must register their public SSH keys with the server before they can connect to it.
|
14
|
+
|
15
|
+
When a user connects to the Hubbard server, the SSH daemon tries to find the user's public SSH key the "~/.ssh/authorized_keys" file on the server. That file also contains information about which user to associate with that SSH key. That information is automatically passed to the "hubbard" executable, so there is no way for users to run other programs on the server.
|
16
|
+
|
17
|
+
Installation
|
18
|
+
===========
|
19
|
+
|
20
|
+
### Server ###
|
21
|
+
|
22
|
+
The first step is to create a user account called "hub" on the server machine and log into that account. You'll also need to make sure that Ruby and Rubygems are installed on the machine. You can then install hubbard like this:
|
23
|
+
|
24
|
+
$ gem install hubbard
|
25
|
+
|
26
|
+
Unless you installed the gem using "sudo", the "hubbard" executable will be found somewhere under the "~/.gem" directory. You'll need to make sure this directory is included in the PATH whenever anyone uses SSH to connect. You can do this by adding the following line to the top of your "~/.bashrc" file (make sure the path matches):
|
27
|
+
|
28
|
+
export PATH=$PATH:~/.gem/ruby/1.8/bin
|
29
|
+
|
30
|
+
The next step is to create an SSH keypair to access the "admin" account on the Hubbard server. You should only use this key when performing tasks that require admin access. Run this on the machine that you'll be accessing Hubbard from (i.e. your local workstation, not the server):
|
31
|
+
|
32
|
+
$ ssh-keygen -f ~/.ssh/hubadmin
|
33
|
+
|
34
|
+
Now we'll need to copy the public key up to the server. Assuming that the hubbard server is named "example" :
|
35
|
+
|
36
|
+
$ cat ~/.ssh/hubadmin.pub | ssh hub@example hubbard admin add-key default
|
37
|
+
|
38
|
+
On your local workstation, you can now create an alias for executing admin commands:
|
39
|
+
|
40
|
+
$ alias hubadmin='ssh -i ~/.ssh/hubadmin hub@example.com'
|
41
|
+
|
42
|
+
Test the configuration:
|
43
|
+
|
44
|
+
$ hubadmin list-users
|
45
|
+
admin
|
46
|
+
|
47
|
+
You'll probably want to create a normal (i.e. non-admin) account as described in the next section.
|
48
|
+
|
49
|
+
### Client ###
|
50
|
+
|
51
|
+
There is no executable to install on the client, but you will have to generate an SSH keypair and send the public key (i.e. the one that ends with ".pub") to your administrator. Your administrator will have to run the following command:
|
52
|
+
|
53
|
+
$ cat <keyfile> | hubadmin run-as <username> add-key default
|
54
|
+
|
55
|
+
Assuming your SSH keys have been set up correcly, you can simply SSH into the server machine to executable commands. You'll probably want to set up an alias to make this easier, however. For example, if your server is running at "example", you can do this:
|
56
|
+
|
57
|
+
$ alias hub='ssh hub@example'
|
58
|
+
|
59
|
+
To test it, run:
|
60
|
+
|
61
|
+
$ hub help
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "hubbard"
|
8
|
+
gem.summary = %Q{Hubbard is a command line tool for managing git repositories.}
|
9
|
+
gem.description = %Q{Hubbard is a command line tool for managing git repositories.}
|
10
|
+
gem.email = "git@foemmel.com"
|
11
|
+
gem.homepage = "http://github.com/mfoemmel/hubbard"
|
12
|
+
gem.authors = ["Matthew Foemmel"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.files << Dir['commands/*.rb']
|
15
|
+
gem.bindir = 'bin'
|
16
|
+
gem.executables << 'hubbard'
|
17
|
+
gem.require_path = ''
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
33
|
+
spec.rcov = true
|
34
|
+
end
|
35
|
+
|
36
|
+
task :spec => :check_dependencies
|
37
|
+
|
38
|
+
task :default => :spec
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "hubbard #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/bin/hubbard
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
PROJECT_REGEX='[a-zA-Z0-9\-]{1,32}'
|
6
|
+
REPOSITORY_REGEX='[a-zA-Z0-9\-]{1,32}'
|
7
|
+
USERNAME_REGEX='[a-zA-Z0-9\-]{1,32}'
|
8
|
+
|
9
|
+
HUB_DATA=ENV['HUB_DATA'] || File.expand_path("~/.hubbard")
|
10
|
+
|
11
|
+
FileUtils.mkdir_p(File.join(HUB_DATA, "projects"))
|
12
|
+
FileUtils.mkdir_p(File.join(HUB_DATA, "accounts"))
|
13
|
+
|
14
|
+
def next_arg(msg)
|
15
|
+
if ARGV.length < 1
|
16
|
+
$stderr.puts msg
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
ARGV.shift
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_status(msg)
|
23
|
+
if $!.exitstatus != 0
|
24
|
+
$sderr.puts msg
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_project_name(name)
|
30
|
+
if name !~ /#{PROJECT_REGEX}/
|
31
|
+
$stderr.put "Project names can only contain letter, numbers, and hyphens"
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_repository_name(name)
|
37
|
+
if name !~ /#{REPOSITORY_REGEX}/
|
38
|
+
$stderr.put "Repository names can only contain letter, numbers, and hyphens"
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_user_name(name)
|
44
|
+
if name !~ /#{USERNAME_REGEX}/
|
45
|
+
$stderr.put "User names can only contain letter, numbers, and hyphens"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_action_name(name)
|
51
|
+
unless name == 'read' || name == 'write' || name == 'admin'
|
52
|
+
$stderr.put "Not a valid action (must be one of: read, write, admin)"
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
username = next_arg "Please specify the username to run as"
|
58
|
+
command = next_arg "Please specify a command to run"
|
59
|
+
|
60
|
+
if command == "run-as"
|
61
|
+
if username != "admin"
|
62
|
+
$stderr.puts "You don't have permission to do that"
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
username = next_arg "Please specify the username to run as"
|
66
|
+
command = next_arg "Please specify a command to run"
|
67
|
+
end
|
68
|
+
|
69
|
+
@username = username
|
70
|
+
|
71
|
+
def implies(a1, a2)
|
72
|
+
case a1
|
73
|
+
when 'admin'
|
74
|
+
true
|
75
|
+
when 'write'
|
76
|
+
a2 != 'admin'
|
77
|
+
when 'read'
|
78
|
+
a2 == 'read'
|
79
|
+
else
|
80
|
+
raise "Unknown action type: *#{a1}*"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def authorize(project_name, action)
|
85
|
+
unless is_authorized(project_name, action)
|
86
|
+
$stderr.puts "You don't have permission to do that"
|
87
|
+
exit 3
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def is_authorized(project_name, action)
|
92
|
+
Dir.chdir(find_project_dir(project_name)) do
|
93
|
+
if action == 'read' && File.read('.visibility').strip == 'public'
|
94
|
+
return true
|
95
|
+
end
|
96
|
+
File.read(".permissions").split("\n").each do |line|
|
97
|
+
permission = line.strip.split('=')
|
98
|
+
line_username = permission[0]
|
99
|
+
line_action = permission[1]
|
100
|
+
if line_username == @username && implies(line_action, action)
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_account_dir(user_name)
|
109
|
+
File.join(HUB_DATA, "accounts", user_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_project_dir(project_name)
|
113
|
+
File.join(HUB_DATA, "projects", project_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_repository_dir(project_name, repository_dir)
|
117
|
+
File.join(find_project_dir(project_name), repository_dir)
|
118
|
+
end
|
119
|
+
|
120
|
+
def read_project_name
|
121
|
+
project_name = next_arg("Please specify a project name")
|
122
|
+
validate_project_name(project_name)
|
123
|
+
project_name
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_repository_name
|
127
|
+
repository_name = next_arg("Please specify a repository name")
|
128
|
+
validate_repository_name(repository_name)
|
129
|
+
repository_name
|
130
|
+
end
|
131
|
+
|
132
|
+
def read_user_name
|
133
|
+
user_name = next_arg("Please specify a username")
|
134
|
+
validate_user_name(user_name)
|
135
|
+
user_name
|
136
|
+
end
|
137
|
+
|
138
|
+
def sync_keys
|
139
|
+
File.open(File.expand_path("~/.ssh/authorized_keys"), "w") do |file|
|
140
|
+
Dir.entries(File.join(HUB_DATA, "accounts")).each do |account|
|
141
|
+
next if account == '.' || account == '..'
|
142
|
+
key_dir = File.join(HUB_DATA, "accounts", account, "keys")
|
143
|
+
Dir.entries(key_dir).each do |name|
|
144
|
+
next if name == '.' || name == '..'
|
145
|
+
key = File.read(File.join(key_dir, name))
|
146
|
+
file << "command=\"hubbard #{@username}\" #{key} #{name}\n"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
command_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "commands", "#{command}.rb"))
|
153
|
+
|
154
|
+
if File.exist?(command_file)
|
155
|
+
load command_file
|
156
|
+
else
|
157
|
+
$stderr.puts "Unknown command: #{command}"
|
158
|
+
exit 1
|
159
|
+
end
|
data/commands/add-key.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
name = next_arg("Please specify the key name")
|
2
|
+
if name !~ /[a-zA-Z0-9]+/
|
3
|
+
$stderr.puts "Not a valid key name (letters and numbers only)"
|
4
|
+
exit 1
|
5
|
+
end
|
6
|
+
|
7
|
+
key = $stdin.read.strip
|
8
|
+
if key !~ /(ssh-rsa|ssh-dsa) ([a-zA-Z0-9\+\/]+[=]*)/
|
9
|
+
$stderr.puts "Not a valid key"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
type = $1
|
14
|
+
value = $2
|
15
|
+
|
16
|
+
dirname = File.join(find_account_dir(@username), "keys")
|
17
|
+
FileUtils.mkdir_p(dirname)
|
18
|
+
filename = File.join(dirname, name)
|
19
|
+
File.open(filename, "w") do |file|
|
20
|
+
file << type << " " << value
|
21
|
+
end
|
22
|
+
|
23
|
+
sync_keys
|
@@ -0,0 +1,23 @@
|
|
1
|
+
project_name = read_project_name
|
2
|
+
authorize(project_name, 'admin')
|
3
|
+
dir = find_project_dir(project_name)
|
4
|
+
username = ARGV.shift
|
5
|
+
action = ARGV.shift
|
6
|
+
unless ['admin','write','read'].member?(action)
|
7
|
+
$stderr.puts "Not a valid action (must be one of: read, write, admin)"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
File.open(File.join(dir, ".lock"), "w+") do |lock|
|
12
|
+
lock.flock(File::LOCK_EX)
|
13
|
+
begin
|
14
|
+
filename = File.join(dir, ".permissions")
|
15
|
+
permissions = File.read(filename).split("\n").map { |line| line.strip }.select { |line| line.split('=')[0] != username }
|
16
|
+
permissions << "#{username}=#{action}"
|
17
|
+
File.open(filename, "w") do |file|
|
18
|
+
permissions.each { |permission| file << permission << "\n" }
|
19
|
+
end
|
20
|
+
ensure
|
21
|
+
lock.flock(File::LOCK_UN)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
project_name = read_project_name
|
4
|
+
dir = find_project_dir(project_name)
|
5
|
+
if File.exist?(dir)
|
6
|
+
$stderr.puts "Project already exists with that name"
|
7
|
+
exit 4
|
8
|
+
end
|
9
|
+
unless Dir.mkdir(dir)
|
10
|
+
$stderr.puts "Unable to create directory: #{dir}"
|
11
|
+
end
|
12
|
+
visibility = ARGV.member?("--private") ? "private" : "public"
|
13
|
+
File.open(File.join(dir, ".permissions"), "w") { |f| f << "#{@username}=admin\n" }
|
14
|
+
File.open(File.join(dir, ".visibility"), "w") { |f| f << "#{visibility}\n" }
|
@@ -0,0 +1,9 @@
|
|
1
|
+
unless ARGV.shift =~ /(#{PROJECT_REGEX})\/(#{REPOSITORY_REGEX}).git/
|
2
|
+
$stderr.puts "Repository not found"
|
3
|
+
exit 1
|
4
|
+
end
|
5
|
+
project_name = $1
|
6
|
+
repository_name = $2
|
7
|
+
authorize(project_name, 'write')
|
8
|
+
dir = find_repository_dir(project_name, repository_name)
|
9
|
+
exec "git-receive-pack #{dir}"
|
@@ -0,0 +1,9 @@
|
|
1
|
+
unless ARGV.shift =~ /(#{PROJECT_REGEX})\/(#{REPOSITORY_REGEX}).git/
|
2
|
+
$stderr.puts "Repository not found"
|
3
|
+
exit 1
|
4
|
+
end
|
5
|
+
project_name = $1
|
6
|
+
repository_name = $2
|
7
|
+
authorize(project_name, 'read')
|
8
|
+
dir = find_repository_dir(project_name, repository_name)
|
9
|
+
exec "git-upload-pack #{dir}"
|
data/commands/help.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$stderr.puts <<-END
|
2
|
+
Usage: hub <command>
|
3
|
+
|
4
|
+
Projects:
|
5
|
+
|
6
|
+
list-projects
|
7
|
+
create-project <project>
|
8
|
+
delete-project <project>
|
9
|
+
|
10
|
+
Repositories:
|
11
|
+
|
12
|
+
create-repository <project> <repository>
|
13
|
+
delete-repository <project> <repository>
|
14
|
+
|
15
|
+
Permissions:
|
16
|
+
|
17
|
+
add-permission <project> <username> read|write|admin
|
18
|
+
remove-permission <project> <username>
|
19
|
+
|
20
|
+
END
|
21
|
+
exit 0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
project_name = read_project_name
|
2
|
+
authorize(project_name, 'admin')
|
3
|
+
dir = find_project_dir(project_name)
|
4
|
+
username = ARGV.shift
|
5
|
+
action = ARGV.shift
|
6
|
+
unless ['admin','write','read'].member?(action)
|
7
|
+
$stderr.puts "Not a valid action (must be one of: read, write, admin)"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
File.open(File.join(dir, ".permissions"), "r+") do |f|
|
11
|
+
f.flock(File::LOCK_EX)
|
12
|
+
contents = f.read
|
13
|
+
puts contents
|
14
|
+
f.flock(File::LOCK_UN)
|
15
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
project_name = read_project_name
|
2
|
+
authorize(project_name, 'read')
|
3
|
+
Dir.foreach(find_project_dir(project_name)) do |repository_name|
|
4
|
+
next if repository_name =~ /^\./
|
5
|
+
git_url = "#{ENV['USER']}@#{ENV['HUB_HOST']}:#{project_name}/#{repository_name}.git"
|
6
|
+
puts "#{repository_name}\t#{git_url}"
|
7
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name = next_arg("Please specify the key name")
|
2
|
+
if name !~ /[a-zA-Z0-9]+/
|
3
|
+
$stderr.puts "Not a valid key name (letters and numbers only)"
|
4
|
+
exit 1
|
5
|
+
end
|
6
|
+
|
7
|
+
dirname = File.join(find_account_dir(@username), "keys")
|
8
|
+
FileUtils.mkdir_p(dirname)
|
9
|
+
|
10
|
+
filename = File.join(dirname, name)
|
11
|
+
if !File.exist?(filename)
|
12
|
+
$stderr.puts "Key not found"
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
unless FileUtils.rm(filename)
|
17
|
+
$stderr.puts "Unable to delete key"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
sync_keys
|
@@ -0,0 +1,16 @@
|
|
1
|
+
project_name = read_project_name
|
2
|
+
authorize(project_name, 'admin')
|
3
|
+
dir = find_project_dir(project_name)
|
4
|
+
username = ARGV.shift
|
5
|
+
File.open(File.join(dir, ".lock"), "w+") do |lock|
|
6
|
+
lock.flock(File::LOCK_EX)
|
7
|
+
begin
|
8
|
+
filename = File.join(dir, ".permissions")
|
9
|
+
permissions = File.read(filename).split("\n").map { |line| line.strip }.select { |line| line.split('=')[0] != username }
|
10
|
+
File.open(filename, "w") do |file|
|
11
|
+
permissions.each { |permission| file << permission << "\n" }
|
12
|
+
end
|
13
|
+
ensure
|
14
|
+
lock.flock(File::LOCK_UN)
|
15
|
+
end
|
16
|
+
end
|
data/lib/hubbard.rb
ADDED
File without changes
|
data/spec/gitssh
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
HUB=File.expand_path(File.join(File.dirname(__FILE__), '..', 'bin', 'hubbard'))
|
4
|
+
|
5
|
+
HUB_USERNAME=ENV['HUB_USERNAME']
|
6
|
+
fail "Please define HUB_USERNAME" unless HUB_USERNAME
|
7
|
+
|
8
|
+
# Ignore hostname passed in
|
9
|
+
ARGV.shift
|
10
|
+
|
11
|
+
$stderr.puts "#{HUB} #{HUB_USERNAME} #{ARGV.join(' ')}"
|
12
|
+
exec "#{HUB} #{HUB_USERNAME} #{ARGV.join(' ')}"
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# Something in the Rakefile is generating
|
2
|
+
# a bunch of GIT_* environment variables,
|
3
|
+
# which mess everything up, so undo.
|
4
|
+
ENV.each do |key,value|
|
5
|
+
ENV[key] = nil if key =~ /^GIT_/
|
6
|
+
end
|
7
|
+
|
8
|
+
HUB=File.expand_path(File.join(File.dirname(__FILE__), "..", "bin", "hubbard"))
|
9
|
+
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
ENV['HUB_USER'] = HUB_USER = "hub"
|
13
|
+
ENV['HUB_HOST'] = HUB_HOST = "example.com"
|
14
|
+
ENV['HUB_DATA'] = HUB_DATA = File.expand_path(File.join(File.dirname(__FILE__), '..', "data"))
|
15
|
+
ENV['GIT_SSH'] = File.expand_path(File.join(File.dirname(__FILE__), "gitssh"))
|
16
|
+
|
17
|
+
def hub(username, command, input=nil)
|
18
|
+
if input
|
19
|
+
result = `echo #{input} | #{HUB} #{username} #{command}`
|
20
|
+
else
|
21
|
+
result = `#{HUB} #{username} #{command}`
|
22
|
+
end
|
23
|
+
if $?.exitstatus != 0
|
24
|
+
raise "Command failed: hub #{username} #{command}\n#{result}"
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def git(username, command)
|
30
|
+
ENV['HUB_USERNAME'] = username
|
31
|
+
result = `git #{command}`
|
32
|
+
if $?.exitstatus != 0
|
33
|
+
raise "Command failed: git #{command}:\n#{result}"
|
34
|
+
end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "Hubble" do
|
39
|
+
before(:each) do
|
40
|
+
FileUtils.rm_rf HUB_DATA
|
41
|
+
FileUtils.rm_rf "tmp"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should create project" do
|
45
|
+
hub("kipper", "create-project foo")
|
46
|
+
projects = hub("kipper", "list-projects").split("\n")
|
47
|
+
projects.should == ["foo"]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not allow multiple projects with same name" do
|
51
|
+
hub("kipper", "create-project foo")
|
52
|
+
lambda { hub("kipper", "create-project foo") }.should raise_error
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should delete project" do
|
56
|
+
hub("kipper", "create-project foo")
|
57
|
+
hub("kipper", "delete-project foo")
|
58
|
+
|
59
|
+
projects = hub("kipper", "list-projects").split("\n")
|
60
|
+
projects.should == []
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should default to public project" do
|
64
|
+
hub("kipper", "create-project foo")
|
65
|
+
|
66
|
+
# Other users can see...
|
67
|
+
projects = hub("tiger", "list-projects").split("\n")
|
68
|
+
projects.should == ["foo"]
|
69
|
+
|
70
|
+
# But not delete
|
71
|
+
lambda { hub("tiger", "delete-project foo") }.should raise_error
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should support private project" do
|
75
|
+
hub("kipper", "create-project foo --private")
|
76
|
+
|
77
|
+
# Other users can't see
|
78
|
+
projects = hub("tiger", "list-projects").split("\n")
|
79
|
+
projects.should == []
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should create repositories" do
|
83
|
+
hub("kipper", "create-project foo")
|
84
|
+
hub("kipper", "create-repository foo bar")
|
85
|
+
|
86
|
+
repositories = hub("kipper", "list-repositories foo").split("\n")
|
87
|
+
repositories.length.should == 1
|
88
|
+
name,url = repositories[0].split
|
89
|
+
name.should == "bar"
|
90
|
+
url.should == "#{ENV['USER']}@#{HUB_HOST}:foo/bar.git"
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_test_project
|
94
|
+
Dir.mkdir('tmp')
|
95
|
+
Dir.chdir('tmp') do
|
96
|
+
File.open("README", "w") { |f| f << "Hello, world\n" }
|
97
|
+
fail unless system "git init"
|
98
|
+
fail unless system "git add README"
|
99
|
+
fail unless system "git commit -m 'initial commit'"
|
100
|
+
yield
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should allow git push" do
|
105
|
+
hub("kipper", "create-project foo")
|
106
|
+
hub("kipper", "create-repository foo bar")
|
107
|
+
|
108
|
+
with_test_project do
|
109
|
+
git("kipper", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should allow git push with write permissions" do
|
114
|
+
hub("kipper", "create-project foo")
|
115
|
+
hub("kipper", "add-permission foo tiger write")
|
116
|
+
hub("kipper", "create-repository foo bar")
|
117
|
+
|
118
|
+
with_test_project do
|
119
|
+
git("tiger", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should not allow git push with read permissions" do
|
124
|
+
hub("kipper", "create-project foo")
|
125
|
+
hub("kipper", "add-permission foo tiger read")
|
126
|
+
hub("kipper", "create-repository foo bar")
|
127
|
+
|
128
|
+
with_test_project do
|
129
|
+
lambda { git("tiger", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master") }.should raise_error
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should allow git pull" do
|
134
|
+
hub("kipper", "create-project foo")
|
135
|
+
hub("kipper", "create-repository foo bar")
|
136
|
+
|
137
|
+
with_test_project do
|
138
|
+
git("kipper", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
139
|
+
git("kipper", "pull #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should not allow git pull with no permissions" do
|
144
|
+
hub("kipper", "create-project foo --private")
|
145
|
+
hub("kipper", "create-repository foo bar")
|
146
|
+
|
147
|
+
with_test_project do
|
148
|
+
git("kipper", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
149
|
+
lambda { git("tiger", "pull #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master") }.should raise_error
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should allow git pull with read permissions" do
|
154
|
+
hub("kipper", "create-project foo")
|
155
|
+
hub("kipper", "create-repository foo bar")
|
156
|
+
|
157
|
+
with_test_project do
|
158
|
+
git("kipper", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
159
|
+
git("tiger", "pull #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should remove permission" do
|
164
|
+
hub("kipper", "create-project foo")
|
165
|
+
hub("kipper", "create-repository foo bar")
|
166
|
+
hub("kipper", "add-permission foo tiger read")
|
167
|
+
hub("kipper", "remove-permission foo tiger")
|
168
|
+
|
169
|
+
with_test_project do
|
170
|
+
lambda { git("tiger", "push #{ENV['USER']}@#{HUB_HOST}:foo/bar.git master") }.should raise_error
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should add ssh key" do
|
175
|
+
hub("kipper", "add-key laptop", "ssh-rsa yabbadabba fdsa")
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should allow admin to run-as another user" do
|
179
|
+
hub("admin", "run-as kipper create-project foo")
|
180
|
+
projects = hub("kipper", "list-projects").split("\n")
|
181
|
+
projects.should == ["foo"]
|
182
|
+
end
|
183
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hubbard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Foemmel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-18 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Hubbard is a command line tool for managing git repositories.
|
26
|
+
email: git@foemmel.com
|
27
|
+
executables:
|
28
|
+
- hubbard
|
29
|
+
- hubbard~
|
30
|
+
- hubbard
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files:
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
files:
|
37
|
+
- .document
|
38
|
+
- .gitignore
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- VERSION
|
43
|
+
- bin/hubbard
|
44
|
+
- commands/add-key.rb
|
45
|
+
- commands/add-permission.rb
|
46
|
+
- commands/create-project.rb
|
47
|
+
- commands/create-repository.rb
|
48
|
+
- commands/delete-project.rb
|
49
|
+
- commands/git-receive-pack.rb
|
50
|
+
- commands/git-upload-pack.rb
|
51
|
+
- commands/help.rb
|
52
|
+
- commands/list-keys.rb
|
53
|
+
- commands/list-permissions.rb
|
54
|
+
- commands/list-projects.rb
|
55
|
+
- commands/list-repositories.rb
|
56
|
+
- commands/remove-key.rb
|
57
|
+
- commands/remove-permission.rb
|
58
|
+
- lib/hubbard.rb
|
59
|
+
- spec/gitssh
|
60
|
+
- spec/hubbard_spec.rb
|
61
|
+
- spec/spec.opts
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/mfoemmel/hubbard
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options:
|
69
|
+
- --charset=UTF-8
|
70
|
+
require_paths:
|
71
|
+
- ""
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Hubbard is a command line tool for managing git repositories.
|
91
|
+
test_files:
|
92
|
+
- spec/hubbard_spec.rb
|
93
|
+
- spec/spec_helper.rb
|