summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--.hound.yml3
-rw-r--r--.travis.yml13
-rw-r--r--CHANGELOG88
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock28
-rw-r--r--README.md72
-rw-r--r--VERSION2
-rwxr-xr-xbin/check11
-rwxr-xr-xbin/create-hooks16
-rwxr-xr-xbin/gitlab-keys6
-rwxr-xr-xbin/gitlab-projects2
-rwxr-xr-xbin/install22
-rw-r--r--config.yml.example19
-rwxr-xr-xhooks/post-receive17
-rwxr-xr-xhooks/pre-receive (renamed from hooks/update)12
-rw-r--r--lib/gitlab_access.rb35
-rw-r--r--lib/gitlab_config.rb21
-rw-r--r--lib/gitlab_keys.rb84
-rw-r--r--lib/gitlab_net.rb78
-rw-r--r--lib/gitlab_post_receive.rb31
-rw-r--r--lib/gitlab_projects.rb108
-rw-r--r--lib/gitlab_shell.rb43
-rw-r--r--lib/gitlab_update.rb59
-rw-r--r--lib/names_helper.rb13
-rw-r--r--spec/gitlab_access_spec.rb19
-rw-r--r--spec/gitlab_config_spec.rb81
-rw-r--r--spec/gitlab_keys_spec.rb190
-rw-r--r--spec/gitlab_logger_spec.rb11
-rw-r--r--spec/gitlab_net_spec.rb108
-rw-r--r--spec/gitlab_post_receive_spec.rb18
-rw-r--r--spec/gitlab_projects_spec.rb134
-rw-r--r--spec/gitlab_shell_spec.rb37
-rw-r--r--spec/names_helper_spec.rb18
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--spec/vcr_cassettes/allowed-pull.yml28
-rw-r--r--spec/vcr_cassettes/allowed-push.yml28
-rw-r--r--spec/vcr_cassettes/check-ok.yml26
-rw-r--r--spec/vcr_cassettes/denied-pull.yml28
-rw-r--r--spec/vcr_cassettes/denied-push-with-user.yml48
-rw-r--r--spec/vcr_cassettes/denied-push.yml28
-rw-r--r--spec/vcr_cassettes/dev_gitlab_org.yml48
-rw-r--r--spec/vcr_cassettes/discover-ok.yml28
-rwxr-xr-xsupport/rewrite-hooks.sh29
44 files changed, 1282 insertions, 422 deletions
diff --git a/.gitignore b/.gitignore
index 0f0683e..62e2cd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,10 @@
config.yml
tmp/*
*.log
+/*.log*
+authorized_keys.lock
+coverage/
+.gitlab_shell_secret
+.bundle
+tags
+.bundle/
diff --git a/.hound.yml b/.hound.yml
new file mode 100644
index 0000000..4799e9d
--- /dev/null
+++ b/.hound.yml
@@ -0,0 +1,3 @@
+StringLiterals:
+ EnforcedStyle: single_quotes
+ Enabled: true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0b677af..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: ruby
-env:
- - TRAVIS=true
-branches:
- only:
- - 'master'
-rvm:
- - 1.9.3-p327
- - 2.0.0
-before_script:
- - "cp config.yml.example config.yml"
-script: "bundle exec rspec spec"
-
diff --git a/CHANGELOG b/CHANGELOG
index 988879c..64829b4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,91 @@
+v2.1.0
+ - Use secret token with GitLab internal API. Requires GitLab 7.5 or higher
+
+v2.0.1
+ - Send post-receive changes to redis as a string instead of array
+
+v2.0.0
+ - Works with GitLab v7.3+
+ - Replace raise with abort when checking path to prevent path exposure
+ - Handle invalid number of arguments on remote commands
+ - Replace update hook with pre-receive and post-receive hooks.
+ - Symlink the whole hooks directory
+ - Ignore missing repositories in create-hooks
+ - Connect to Redis via sockets by default
+
+v1.9.7
+ - Increased test coverage
+ - By default use direct unicorn connection (localhost:8080)
+ - Fix wrong repo path send to GitLab by GitlabUpdate hook
+
+v1.9.6
+ - Explicitly require 'timeout' from the standard library
+
+v1.9.5
+ - Put authorized_keys.lock in the same directory as authorized_keys
+ - Use lock file when add new entries to authorized_keys
+
+v1.9.4
+ - Use lock file when modify authorized_keys
+
+v1.9.3
+ - Ignore force push detection for new branch or branch remove push
+
+v1.9.2
+ - Add support for force push detection
+
+v1.9.1
+ - Update hook sends branch and tag name
+
+v1.9.0
+ - Call api in update hook for both ssdh and http push. Requires GitLab 6.7+
+ - Pass oldrev and newrev to api.allowed?
+
+v1.8.5
+ - Add `gitlab-keys batch-add-keys` subcommand for authorized_keys rebuilds
+
+v1.8.4
+ - Dont do import if repository exists
+
+v1.8.3
+ - Add timeout option for repository import
+
+v1.8.2
+ - Fix broken 1.8.1
+
+v1.8.1
+ - Restrict Environment Variables
+ - Add bin/create-hooks command
+ - More safe shell execution
+
+v1.8.0
+ - Fix return values in GitlabKeys
+
+v1.7.9
+ - Fix escape of repository path for custom ssh port
+
+v1.7.8
+ - Escape repository path to prevent relative links (CVE-2013-4583)
+
+v1.7.7
+ - Separate options from arguments with -- (CVE-2013-4582)
+ - Bypass shell and use stdlib JSON for GitlabUpdate (CVE-2013-4581)
+
+v1.7.6
+ - Fix gitlab-projects update-head for improted repo when branch exists but not listed in refs/head
+
+v1.7.5
+ - Remove keys from authorized_keys using ruby instead of shell
+
+v1.7.4
+ - More protection against shell injection (CVE-2013-4546)
+
+v1.7.3
+ - Use Kernel#open to append lines to authorized_keys (CVE-2013-4490)
+
+v1.7.2
+ - More safe command execution
+
v1.7.1
- Fixed issue when developers are able to push to protected branches that contain a '/' in the branch name.
diff --git a/Gemfile b/Gemfile
index c7516d0..5f5fb9b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source "http://rubygems.org"
-group :development do
+group :development, :test do
gem 'coveralls', require: false
gem 'rspec'
gem 'webmock'
diff --git a/Gemfile.lock b/Gemfile.lock
index 338ee07..661d576 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,15 +3,15 @@ GEM
specs:
addressable (2.3.2)
coderay (1.0.8)
- colorize (0.5.8)
- coveralls (0.6.2)
- colorize
+ coveralls (0.7.1)
multi_json (~> 1.3)
rest-client
simplecov (>= 0.7)
+ term-ansicolor
thor
crack (0.3.1)
diff-lcs (1.1.3)
+ docile (1.1.5)
guard (1.5.4)
listen (>= 0.4.2)
lumberjack (>= 1.0.2)
@@ -23,14 +23,16 @@ GEM
listen (0.5.3)
lumberjack (1.0.2)
method_source (0.8.1)
- mime-types (1.21)
- multi_json (1.6.1)
+ mime-types (2.3)
+ multi_json (1.10.1)
+ netrc (0.7.7)
pry (0.9.10)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.3.1)
- rest-client (1.6.7)
- mime-types (>= 1.16)
+ rest-client (1.7.2)
+ mime-types (>= 1.16, < 3.0)
+ netrc (~> 0.7)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
@@ -39,12 +41,16 @@ GEM
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.12.2)
- simplecov (0.7.1)
+ simplecov (0.9.1)
+ docile (~> 1.1.0)
multi_json (~> 1.0)
- simplecov-html (~> 0.7.1)
- simplecov-html (0.7.1)
+ simplecov-html (~> 0.8.0)
+ simplecov-html (0.8.0)
slop (3.3.3)
- thor (0.16.0)
+ term-ansicolor (1.3.0)
+ tins (~> 1.0)
+ thor (0.19.1)
+ tins (1.3.3)
vcr (2.4.0)
webmock (1.9.0)
addressable (>= 2.2.7)
diff --git a/README.md b/README.md
index eb215fa..3f04de9 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,88 @@
-### gitlab-shell: ssh access and repository management
+# gitlab-shell: ssh access and repository management
-#### Code status
+GitLab Shell is an application that allows you to execute git commands
+and provide ssh access to git repositories.
+It is not a Unix shell nor a replacement for Bash or Zsh.
-* [![CI](http://ci.gitlab.org/projects/4/status.png?ref=master)](http://ci.gitlab.org/projects/4?ref=master)
-* [![Build Status](https://travis-ci.org/gitlabhq/gitlab-shell.png?branch=master)](https://travis-ci.org/gitlabhq/gitlab-shell)
-* [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlab-shell.png)](https://codeclimate.com/github/gitlabhq/gitlab-shell)
-* [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlab-shell/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlab-shell)
+## Code status
+[![CI](http://ci.gitlab.org/projects/4/status.png?ref=master)](http://ci.gitlab.org/projects/4?ref=master)
+[![Build Status](https://semaphoreapp.com/api/v1/projects/a71ddd46-a9cc-4062-875e-7ade19a44927/243336/badge.png)](https://semaphoreapp.com/gitlabhq/gitlab-shell)
+[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlab-shell.png)](https://codeclimate.com/github/gitlabhq/gitlab-shell)
+[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlab-shell/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlab-shell)
-__Requires ruby 1.9+__
+**Requires ruby 1.9+**
-
-### Setup
+## Setup
./bin/install
-
-### Check
+## Check
./bin/check
+## Repos
-### Repos:
-
-
-Add repo
+Add repo:
./bin/gitlab-projects add-project gitlab/gitlab-ci.git
-Remove repo
+Remove repo:
./bin/gitlab-projects rm-project gitlab/gitlab-ci.git
-Import repo
+List repos:
+
+ ./bin/gitlab-projects list-projects
+
+Import repo:
+
+ # Default timeout is 2 minutes
+ ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git
- ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git
+ # Override timeout in seconds
+ ./bin/gitlab-projects import-project randx/six.git https://github.com/randx/six.git 90
-Fork repo
+Fork repo:
./bin/gitlab-projects fork-project gitlab/gitlab-ci.git randx
-Update HEAD
+Update HEAD:
./bin/gitlab-projects update-head gitlab/gitlab-ci.git 3-2-stable
-Create branch
+Create branch:
./bin/gitlab-projects create-branch gitlab/gitlab-ci.git 3-2-stable master
-Remove branch
+Remove branch:
./bin/gitlab-projects rm-branch gitlab/gitlab-ci.git 3-0-stable
-Create tag
+Create tag (lightweight & annotated):
./bin/gitlab-projects create-tag gitlab/gitlab-ci.git v3.0.0 3-0-stable
+ ./bin/gitlab-projects create-tag gitlab/gitlab-ci.git v3.0.0 3-0-stable 'annotated message goes here'
-Remove tag
+Remove tag:
./bin/gitlab-projects rm-tag gitlab/gitlab-ci.git v3.0.0
+## Keys
-### Keys:
-
-
-Add key
+Add key:
./bin/gitlab-keys add-key key-782 "ssh-rsa AAAAx321..."
-Remove key
+Remove key:
./bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..."
-Remove all keys from authorized_keys file
+List all keys:
- ./bin/gitlab-keys clear
+ ./bin/gitlab-keys list-keys
+
+Remove all keys from authorized_keys file:
+
+ ./bin/gitlab-keys clear
diff --git a/VERSION b/VERSION
index 943f9cb..7ec1d6d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.7.1
+2.1.0
diff --git a/bin/check b/bin/check
index e0c34ee..f4588a2 100755
--- a/bin/check
+++ b/bin/check
@@ -12,7 +12,7 @@ resp = GitlabNet.new.check
if resp.code == "200"
print 'OK'
else
- puts "FAILED. code: #{resp.code}"
+ abort "FAILED. code: #{resp.code}"
end
puts "\nCheck directories and files: "
@@ -21,11 +21,18 @@ config = GitlabConfig.new
dirs = [config.repos_path, config.auth_file]
dirs.each do |dir|
+ abort("ERROR: missing option in config.yml") unless dir
print "\t#{dir}: "
if File.exists?(dir)
print 'OK'
else
- puts "FAILED"
+ abort "FAILED"
end
puts "\n"
end
+
+print "Test redis-cli executable: "
+abort('FAILED') unless system(*config.redis_command, '--version')
+
+print "Send ping to redis server: "
+abort unless system(*config.redis_command, 'ping')
diff --git a/bin/create-hooks b/bin/create-hooks
new file mode 100755
index 0000000..4efa650
--- /dev/null
+++ b/bin/create-hooks
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+# Recreate GitLab hooks in the Git repositories managed by GitLab.
+#
+# This script is used when restoring a GitLab backup.
+
+require_relative '../lib/gitlab_init'
+require File.join(ROOT_PATH, 'lib', 'gitlab_projects')
+
+Dir["#{GitlabConfig.new.repos_path}/*/*.git"].each do |repo|
+ begin
+ GitlabProjects.create_hooks(repo)
+ rescue Errno::ENOENT
+ # The user must have deleted their repository. Ignore.
+ end
+end
diff --git a/bin/gitlab-keys b/bin/gitlab-keys
index d402d1e..9eb1950 100755
--- a/bin/gitlab-keys
+++ b/bin/gitlab-keys
@@ -8,9 +8,13 @@ require_relative '../lib/gitlab_init'
# Ex.
# /bin/gitlab-keys add-key key-782 "ssh-rsa AAAAx321..."
#
+# printf "key-782\tssh-rsa AAAAx321...\n" | /bin/gitlab-keys batch-add-keys
+#
# /bin/gitlab-keys rm-key key-23 "ssh-rsa AAAAx321..."
#
-# /bin/gitlab-keys clear"
+# /bin/gitlab-keys list-keys
+#
+# /bin/gitlab-keys clear
#
require File.join(ROOT_PATH, 'lib', 'gitlab_keys')
diff --git a/bin/gitlab-projects b/bin/gitlab-projects
index 01de20b..c354fe5 100755
--- a/bin/gitlab-projects
+++ b/bin/gitlab-projects
@@ -11,6 +11,8 @@ require_relative '../lib/gitlab_init'
#
# /bin/gitlab-projects rm-project gitlab/gitlab-ci.git
#
+# /bin/gitlab-projects list-projects
+#
# /bin/gitlab-projects mv-project gitlab/gitlab-ci.git randx/fork.git
#
# /bin/gitlab-projects fork-project gitlab/gitlab-ci.git randx
diff --git a/bin/install b/bin/install
index f8c12f8..64ae726 100755
--- a/bin/install
+++ b/bin/install
@@ -10,17 +10,23 @@ config = GitlabConfig.new
key_dir = File.dirname("#{config.auth_file}")
commands = [
- "mkdir -p #{config.repos_path}",
- "mkdir -p #{key_dir}",
- "chmod 700 #{key_dir}",
- "touch #{config.auth_file}",
- "chmod 600 #{config.auth_file}",
- "chmod -R ug+rwX,o-rwx #{config.repos_path}",
- "find #{config.repos_path} -type d -print0 | xargs -0 chmod g+s"
+ %W(mkdir -p #{config.repos_path}),
+ %W(mkdir -p #{key_dir}),
+ %W(chmod 700 #{key_dir}),
+ %W(touch #{config.auth_file}),
+ %W(chmod 600 #{config.auth_file}),
+ %W(chmod -R ug+rwX,o-rwx #{config.repos_path}),
+ %W(find #{config.repos_path} -type d -exec chmod g+s {} ;)
]
commands.each do |cmd|
- puts "#{cmd}: #{system(cmd)}"
+ print "#{cmd.join(' ')}: "
+ if system(*cmd)
+ puts 'OK'
+ else
+ puts 'Failed'
+ abort "#{$PROGRAM_NAME} failed"
+ end
end
exit
diff --git a/config.yml.example b/config.yml.example
index 6922b48..97b5006 100644
--- a/config.yml.example
+++ b/config.yml.example
@@ -2,8 +2,13 @@
user: git
# Url to gitlab instance. Used for api calls. Should end with a slash.
-gitlab_url: "http://localhost/"
+# Default: http://localhost:8080/
+# You only have to change the default if you have configured Unicorn
+# to listen on a custom port, or if you have configured Unicorn to
+# only listen on a Unix domain socket.
+gitlab_url: "http://localhost:8080/"
+# See installation.md#using-https for additional HTTPS configuration details.
http_settings:
# user: someone
# password: somepass
@@ -12,7 +17,9 @@ http_settings:
self_signed_cert: false
# Repositories path
-# REPOS_PATH MUST NOT BE A SYMLINK!!!
+# Give the canonicalized absolute pathname,
+# REPOS_PATH MUST NOT CONTAIN ANY SYMLINK!!!
+# Check twice that none of the components is a symlink, including "/home".
repos_path: "/home/git/repositories"
# File used as authorized_keys for gitlab user
@@ -21,9 +28,11 @@ auth_file: "/home/git/.ssh/authorized_keys"
# Redis settings used for pushing commit notices to gitlab
redis:
bin: /usr/bin/redis-cli
- host: 127.0.0.1
- port: 6379
- # socket: /tmp/redis.socket # Only define this if you want to use sockets
+ # host: 127.0.0.1
+ # port: 6379
+ # pass: redispass # Allows you to specify the password for Redis
+ database: 0
+ socket: /var/run/redis/redis.sock # Comment out this line if you want to use TCP
namespace: resque:gitlab
# Log file.
diff --git a/hooks/post-receive b/hooks/post-receive
new file mode 100755
index 0000000..d85ad42
--- /dev/null
+++ b/hooks/post-receive
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+# This file was placed here by GitLab. It makes sure that your pushed commits
+# will be processed properly.
+# You can add your own hooks to this file, but be careful when updating gitlab-shell!
+
+changes = ARGF.read
+key_id = ENV['GL_ID']
+repo_path = Dir.pwd
+
+require_relative '../lib/gitlab_post_receive'
+
+if GitlabPostReceive.new(repo_path, key_id, changes).exec
+ exit 0
+else
+ exit 1
+end
diff --git a/hooks/update b/hooks/pre-receive
index 549afeb..2b979fa 100755
--- a/hooks/update
+++ b/hooks/pre-receive
@@ -4,10 +4,14 @@
# will be processed properly.
# You can add your own hooks to this file, but be careful when updating gitlab-shell!
-refname = ARGV[0]
+refs = ARGF.read
key_id = ENV['GL_ID']
-repo_path = `pwd`
+repo_path = Dir.pwd
-require_relative '../lib/gitlab_update'
+require_relative '../lib/gitlab_access'
-GitlabUpdate.new(repo_path, key_id, refname).exec
+if GitlabAccess.new(repo_path, key_id, refs).exec
+ exit 0
+else
+ exit 1
+end
diff --git a/lib/gitlab_access.rb b/lib/gitlab_access.rb
new file mode 100644
index 0000000..80c050c
--- /dev/null
+++ b/lib/gitlab_access.rb
@@ -0,0 +1,35 @@
+require_relative 'gitlab_init'
+require_relative 'gitlab_net'
+require_relative 'names_helper'
+require 'json'
+
+class GitlabAccess
+ include NamesHelper
+
+ attr_reader :config, :repo_path, :repo_name, :changes
+
+ def initialize(repo_path, actor, changes)
+ @config = GitlabConfig.new
+ @repo_path = repo_path.strip
+ @actor = actor
+ @repo_name = extract_repo_name(@repo_path.dup, config.repos_path.to_s)
+ @changes = changes.lines
+ end
+
+ def exec
+ if api.allowed?('git-receive-pack', @repo_name, @actor, @changes)
+ exit 0
+ else
+ # reset GL_ID env since we stop git push here
+ ENV['GL_ID'] = nil
+ puts "GitLab: You are not allowed to access some of the refs!"
+ exit 1
+ end
+ end
+
+ protected
+
+ def api
+ GitlabNet.new
+ end
+end
diff --git a/lib/gitlab_config.rb b/lib/gitlab_config.rb
index 9dc5c66..c97743b 100644
--- a/lib/gitlab_config.rb
+++ b/lib/gitlab_config.rb
@@ -7,12 +7,16 @@ class GitlabConfig
@config = YAML.load_file(File.join(ROOT_PATH, 'config.yml'))
end
+ def home
+ ENV['HOME']
+ end
+
def repos_path
- @config['repos_path'] ||= "/home/git/repositories"
+ @config['repos_path'] ||= File.join(home, "repositories")
end
def auth_file
- @config['auth_file'] ||= "/home/git/.ssh/authorized_keys"
+ @config['auth_file'] ||= File.join(home, ".ssh/authorized_keys")
end
def gitlab_url
@@ -48,12 +52,19 @@ class GitlabConfig
if redis.empty?
# Default to old method of connecting to redis
# for users that haven't updated their configuration
- "env -i redis-cli"
+ %W(env -i redis-cli)
else
+ redis['database'] ||= 0
+ redis['host'] ||= '127.0.0.1'
+ redis['port'] ||= '6379'
if redis.has_key?("socket")
- "#{redis['bin']} -s #{redis['socket']}"
+ %W(#{redis['bin']} -s #{redis['socket']} -n #{redis['database']})
else
- "#{redis['bin']} -h #{redis['host']} -p #{redis['port']}"
+ if redis.has_key?("pass")
+ %W(#{redis['bin']} -h #{redis['host']} -p #{redis['port']} -n #{redis['database']} -a #{redis['pass']})
+ else
+ %W(#{redis['bin']} -h #{redis['host']} -p #{redis['port']} -n #{redis['database']})
+ end
end
end
end
diff --git a/lib/gitlab_keys.rb b/lib/gitlab_keys.rb
index dc54740..100e164 100644
--- a/lib/gitlab_keys.rb
+++ b/lib/gitlab_keys.rb
@@ -1,4 +1,5 @@
require 'tempfile'
+require 'timeout'
require_relative 'gitlab_config'
require_relative 'gitlab_logger'
@@ -16,7 +17,9 @@ class GitlabKeys
def exec
case @command
when 'add-key'; add_key
+ when 'batch-add-keys'; batch_add_keys
when 'rm-key'; rm_key
+ when 'list-keys'; puts list_keys
when 'clear'; clear
else
$logger.warn "Attempt to execute invalid gitlab-keys command #{@command.inspect}."
@@ -28,21 +31,84 @@ class GitlabKeys
protected
def add_key
- $logger.info "Adding key #{@key_id} => #{@key.inspect}"
- cmd = "command=\"#{ROOT_PATH}/bin/gitlab-shell #{@key_id}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{@key}"
- cmd = "echo \'#{cmd}\' >> #{auth_file}"
- system(cmd)
+ lock do
+ $logger.info "Adding key #{@key_id} => #{@key.inspect}"
+ auth_line = key_line(@key_id, @key)
+ open(auth_file, 'a') { |file| file.puts(auth_line) }
+ end
+ true
+ end
+
+ def list_keys
+ $logger.info 'Listing all keys'
+ keys = ''
+ File.readlines(auth_file).each do |line|
+ # key_id & public_key
+ # command=".../bin/gitlab-shell key-741" ... ssh-rsa AAAAB3NzaDAxx2E\n
+ # ^^^^^^^ ^^^^^^^^^^^^^^^
+ matches = /^command=\".+?\s+(.+?)\".+?ssh-rsa\s(.+)\s*.*\n*$/.match(line)
+ keys << "#{matches[1]} #{matches[2]}\n" unless matches.nil?
+ end
+ keys
+ end
+
+ def batch_add_keys
+ lock do
+ open(auth_file, 'a') do |file|
+ stdin.each_line do |input|
+ tokens = input.strip.split("\t")
+ abort("#{$0}: invalid input #{input.inspect}") unless tokens.count == 2
+ key_id, public_key = tokens
+ $logger.info "Adding key #{key_id} => #{public_key.inspect}"
+ file.puts(key_line(key_id, public_key))
+ end
+ end
+ end
+ true
+ end
+
+ def stdin
+ $stdin
+ end
+
+ def key_line(key_id, public_key)
+ auth_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell #{key_id}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}"
end
def rm_key
- $logger.info "Removing key #{@key_id}"
- Tempfile.open('authorized_keys') do |temp|
- cmd = "sed '/shell #{@key_id}\"/d' #{auth_file} > #{temp.path} && mv #{temp.path} #{auth_file}"
- system(cmd)
+ lock do
+ $logger.info "Removing key #{@key_id}"
+ Tempfile.open('authorized_keys') do |temp|
+ open(auth_file, 'r+') do |current|
+ current.each do |line|
+ temp.puts(line) unless line.include?("/bin/gitlab-shell #{@key_id}\"")
+ end
+ end
+ temp.close
+ FileUtils.cp(temp.path, auth_file)
+ end
end
+ true
end
def clear
- system("echo '# Managed by gitlab-shell' > #{auth_file}")
+ open(auth_file, 'w') { |file| file.puts '# Managed by gitlab-shell' }
+ true
+ end
+
+
+ def lock(timeout = 10)
+ File.open(lock_file, "w+") do |f|
+ begin
+ f.flock File::LOCK_EX
+ Timeout::timeout(timeout) { yield }
+ ensure
+ f.flock File::LOCK_UN
+ end
+ end
+ end
+
+ def lock_file
+ @lock_file ||= auth_file + '.lock'
end
end
diff --git a/lib/gitlab_net.rb b/lib/gitlab_net.rb
index 99d0044..e6478ef 100644
--- a/lib/gitlab_net.rb
+++ b/lib/gitlab_net.rb
@@ -6,15 +6,25 @@ require_relative 'gitlab_config'
require_relative 'gitlab_logger'
class GitlabNet
- def allowed?(cmd, repo, key, ref)
+ def allowed?(cmd, repo, actor, changes)
project_name = repo.gsub("'", "")
project_name = project_name.gsub(/\.git\Z/, "")
project_name = project_name.gsub(/\A\//, "")
- key_id = key.gsub("key-", "")
+ params = {
+ action: cmd,
+ changes: changes,
+ project: project_name,
+ }
+
+ if actor =~ /\Akey\-\d+\Z/
+ params.merge!(key_id: actor.gsub("key-", ""))
+ elsif actor =~ /\Auser\-\d+\Z/
+ params.merge!(user_id: actor.gsub("user-", ""))
+ end
- url = "#{host}/allowed?key_id=#{key_id}&action=#{cmd}&ref=#{ref}&project=#{project_name}"
- resp = get(url)
+ url = "#{host}/allowed"
+ resp = post(url, params)
!!(resp.code == '200' && resp.body == 'true')
end
@@ -39,37 +49,63 @@ class GitlabNet
"#{config.gitlab_url}/api/v3/internal"
end
+ def http_client_for(url)
+ Net::HTTP.new(url.host, url.port).tap do |http|
+ if URI::HTTPS === url
+ http.use_ssl = true
+ http.cert_store = cert_store
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.http_settings['self_signed_cert']
+ end
+ end
+ end
+
+ def http_request_for(url, method = :get)
+ user = config.http_settings['user']
+ password = config.http_settings['password']
+
+ if method == :get
+ Net::HTTP::Get.new(url.request_uri).tap { |r| r.basic_auth(user, password) if user && password }
+ else
+ Net::HTTP::Post.new(url.request_uri).tap { |r| r.basic_auth(user, password) if user && password }
+ end
+ end
+
def get(url)
$logger.debug "Performing GET #{url}"
url = URI.parse(url)
- http = Net::HTTP.new(url.host, url.port)
-
- if URI::HTTPS === url
- http.use_ssl = true
- http.cert_store = cert_store
+ http = http_client_for url
+ request = http_request_for url
+ request.set_form_data(secret_token: secret_token)
- if config.http_settings['self_signed_cert']
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.start { |http| http.request(request) }.tap do |resp|
+ if resp.code == "200"
+ $logger.debug { "Received response #{resp.code} => <#{resp.body}>." }
+ else
+ $logger.error { "API call <GET #{url}> failed: #{resp.code} => <#{resp.body}>." }
end
end
+ end
- request = Net::HTTP::Get.new(url.request_uri)
- if config.http_settings['user'] && config.http_settings['password']
- request.basic_auth config.http_settings['user'], config.http_settings['password']
- end
+ def post(url, params)
+ $logger.debug "Performing POST #{url}"
- http.start {|http| http.request(request) }.tap do |resp|
+ url = URI.parse(url)
+ http = http_client_for(url)
+ request = http_request_for(url, :post)
+ request.set_form_data(params.merge(secret_token: secret_token))
+
+ http.start { |http| http.request(request) }.tap do |resp|
if resp.code == "200"
$logger.debug { "Received response #{resp.code} => <#{resp.body}>." }
else
- $logger.error { "API call <GET #{url}> failed: #{resp.code} => <#{resp.body}>." }
+ $logger.error { "API call <POST #{url}> failed: #{resp.code} => <#{resp.body}>." }
end
end
end
def cert_store
- @cert_store ||= OpenSSL::X509::Store.new.tap { |store|
+ @cert_store ||= OpenSSL::X509::Store.new.tap do |store|
store.set_default_paths
if ca_file = config.http_settings['ca_file']
@@ -79,6 +115,10 @@ class GitlabNet
if ca_path = config.http_settings['ca_path']
store.add_path(ca_path)
end
- }
+ end
+ end
+
+ def secret_token
+ @secret_token ||= File.read File.join(ROOT_PATH, '.gitlab_shell_secret')
end
end
diff --git a/lib/gitlab_post_receive.rb b/lib/gitlab_post_receive.rb
new file mode 100644
index 0000000..bd80408
--- /dev/null
+++ b/lib/gitlab_post_receive.rb
@@ -0,0 +1,31 @@
+require_relative 'gitlab_init'
+require 'json'
+
+class GitlabPostReceive
+ attr_reader :config, :repo_path, :changes
+
+ def initialize(repo_path, actor, changes)
+ @config = GitlabConfig.new
+ @repo_path, @actor = repo_path.strip, actor
+ @changes = changes
+ end
+
+ def exec
+ # reset GL_ID env since we already
+ # get value from it
+ ENV['GL_ID'] = nil
+
+ update_redis
+ end
+
+ protected
+
+ def update_redis
+ queue = "#{config.redis_namespace}:queue:post_receive"
+ msg = JSON.dump({'class' => 'PostReceive', 'args' => [@repo_path, @actor, @changes]})
+ unless system(*config.redis_command, 'rpush', queue, msg, err: '/dev/null', out: '/dev/null')
+ puts "GitLab: An unexpected error occurred (redis-cli returned #{$?.exitstatus})."
+ exit 1
+ end
+ end
+end
diff --git a/lib/gitlab_projects.rb b/lib/gitlab_projects.rb
index bea5686..82ae519 100644
--- a/lib/gitlab_projects.rb
+++ b/lib/gitlab_projects.rb
@@ -1,10 +1,12 @@
-require 'open3'
require 'fileutils'
+require 'timeout'
require_relative 'gitlab_config'
require_relative 'gitlab_logger'
class GitlabProjects
+ GLOBAL_HOOKS_DIRECTORY = File.join(ROOT_PATH, 'hooks')
+
# Project name is a directory name for repository with .git at the end
# It may be namespaced or not. Like repo.git or gitlab/repo.git
attr_reader :project_name
@@ -17,11 +19,19 @@ class GitlabProjects
# Ex /home/git/repositories/test.git
attr_reader :full_path
+ def self.create_hooks(path)
+ local_hooks_directory = File.join(path, 'hooks')
+ unless File.realpath(local_hooks_directory) == File.realpath(GLOBAL_HOOKS_DIRECTORY)
+ FileUtils.mv(local_hooks_directory, "#{local_hooks_directory}.old.#{Time.now.to_i}")
+ FileUtils.ln_s(GLOBAL_HOOKS_DIRECTORY, local_hooks_directory)
+ end
+ end
+
def initialize
@command = ARGV.shift
@project_name = ARGV.shift
@repos_path = GitlabConfig.new.repos_path
- @full_path = File.join(@repos_path, @project_name)
+ @full_path = File.join(@repos_path, @project_name) unless @project_name.nil?
end
def exec
@@ -31,6 +41,7 @@ class GitlabProjects
when 'create-tag'; create_tag
when 'rm-tag'; rm_tag
when 'add-project'; add_project
+ when 'list-projects'; puts list_projects
when 'rm-project'; rm_project
when 'mv-project'; mv_project
when 'import-project'; import_project
@@ -48,38 +59,46 @@ class GitlabProjects
def create_branch
branch_name = ARGV.shift
ref = ARGV.shift || "HEAD"
- cmd = "cd #{full_path} && git branch #{branch_name} #{ref}"
- system(cmd)
+ cmd = %W(git --git-dir=#{full_path} branch -- #{branch_name} #{ref})
+ system(*cmd)
end
def rm_branch
branch_name = ARGV.shift
- cmd = "cd #{full_path} && git branch -D #{branch_name}"
- system(cmd)
+ cmd = %W(git --git-dir=#{full_path} branch -D #{branch_name})
+ system(*cmd)
end
def create_tag
tag_name = ARGV.shift
ref = ARGV.shift || "HEAD"
- cmd = "cd #{full_path} && git tag #{tag_name} #{ref}"
- system(cmd)
+ cmd = %W(git --git-dir=#{full_path} tag)
+ if ARGV.size > 0
+ msg = ARGV.shift
+ cmd += %W(-a -m #{msg})
+ end
+ cmd += %W(-- #{tag_name} #{ref})
+ system(*cmd)
end
def rm_tag
tag_name = ARGV.shift
- cmd = "cd #{full_path} && git tag -d #{tag_name}"
- system(cmd)
+ cmd = %W(git --git-dir=#{full_path} tag -d #{tag_name})
+ system(*cmd)
end
def add_project
$logger.info "Adding project #{@project_name} at <#{full_path}>."
FileUtils.mkdir_p(full_path, mode: 0770)
- cmd = "cd #{full_path} && git init --bare && #{create_hooks_cmd}"
- system(cmd)
+ cmd = %W(git --git-dir=#{full_path} init --bare)
+ system(*cmd) && self.class.create_hooks(full_path)
end
- def create_hooks_cmd
- create_hooks_to(full_path)
+ def list_projects
+ $logger.info 'Listing projects'
+ Dir.chdir(repos_path) do
+ next Dir.glob('**/*.git')
+ end
end
def rm_project
@@ -87,13 +106,53 @@ class GitlabProjects
FileUtils.rm_rf(full_path)
end
+ def mask_password_in_url(url)
+ result = URI(url)
+ result.password = "*****" unless result.password.nil?
+ result
+ rescue
+ url
+ end
+
+ def remove_origin_in_repo
+ cmd = %W(git --git-dir=#{full_path} remote rm origin)
+ pid = Process.spawn(*cmd)
+ Process.wait(pid)
+ end
+
# Import project via git clone --bare
# URL must be publicly cloneable
def import_project
+ # Skip import if repo already exists
+ return false if File.exists?(full_path)
+
@source = ARGV.shift
- $logger.info "Importing project #{@project_name} from <#{@source}> to <#{full_path}>."
- cmd = "cd #{repos_path} && git clone --bare #{@source} #{project_name} && #{create_hooks_cmd}"
- system(cmd)
+ masked_source = mask_password_in_url(@source)
+
+ # timeout for clone
+ timeout = (ARGV.shift || 120).to_i
+ $logger.info "Importing project #{@project_name} from <#{masked_source}> to <#{full_path}>."
+ cmd = %W(git clone --bare -- #{@source} #{full_path})
+
+ pid = Process.spawn(*cmd)
+
+ begin
+ Timeout.timeout(timeout) do
+ Process.wait(pid)
+ end
+ rescue Timeout::Error
+ $logger.error "Importing project #{@project_name} from <#{masked_source}> failed due to timeout."
+
+ Process.kill('KILL', pid)
+ Process.wait
+ FileUtils.rm_rf(full_path)
+ false
+ else
+ self.class.create_hooks(full_path)
+ # The project was imported successfully.
+ # Remove the origin URL since it may contain password.
+ remove_origin_in_repo
+ end
end
# Move repository from one directory to another
@@ -154,8 +213,8 @@ class GitlabProjects
end
$logger.info "Forking project from <#{full_path}> to <#{full_destination_path}>."
- cmd = "cd #{namespaced_path} && git clone --bare #{full_path} && #{create_hooks_to(full_destination_path)}"
- system(cmd)
+ cmd = %W(git clone --bare -- #{full_path} #{full_destination_path})
+ system(*cmd) && self.class.create_hooks(full_destination_path)
end
def update_head
@@ -166,11 +225,6 @@ class GitlabProjects
return false
end
- unless File.exists?(File.join(full_path, 'refs/heads', new_head))
- $logger.error "update-head failed: specified branch does not exist in ref/heads."
- return false
- end
-
File.open(File.join(full_path, 'HEAD'), 'w') do |f|
f.write("ref: refs/heads/#{new_head}")
end
@@ -178,10 +232,4 @@ class GitlabProjects
$logger.info "Update head in project #{project_name} to <#{new_head}>."
true
end
-
- private
-
- def create_hooks_to(dest_path)
- "ln -s #{File.join(ROOT_PATH, 'hooks', 'update')} #{dest_path}/hooks/update"
- end
end
diff --git a/lib/gitlab_shell.rb b/lib/gitlab_shell.rb
index 01ef4a1..e2cb2cc 100644
--- a/lib/gitlab_shell.rb
+++ b/lib/gitlab_shell.rb
@@ -1,8 +1,10 @@
-require 'open3'
+require 'shellwords'
require_relative 'gitlab_net'
class GitlabShell
+ class DisallowedCommandError < StandardError; end
+
attr_accessor :key_id, :repo_name, :git_cmd, :repos_path, :repo_name
def initialize
@@ -10,7 +12,6 @@ class GitlabShell
@origin_cmd = ENV['SSH_ORIGINAL_COMMAND']
@config = GitlabConfig.new
@repos_path = @config.repos_path
- @user_tried = false
end
def exec
@@ -28,21 +29,24 @@ class GitlabShell
$stderr.puts "Access denied."
end
else
- message = "gitlab-shell: Attempt to execute disallowed command <#{@origin_cmd}> by #{log_username}."
- $logger.warn message
- puts 'Not allowed command'
+ raise DisallowedCommandError
end
else
puts "Welcome to GitLab, #{username}!"
end
+ rescue DisallowedCommandError => ex
+ message = "gitlab-shell: Attempt to execute disallowed command <#{@origin_cmd}> by #{log_username}."
+ $logger.warn message
+ puts 'Not allowed command'
end
protected
def parse_cmd
- args = @origin_cmd.split(' ')
- @git_cmd = args.shift
- @repo_name = args.shift
+ args = Shellwords.shellwords(@origin_cmd)
+ raise DisallowedCommandError unless args.count == 2
+ @git_cmd = args[0]
+ @repo_name = escape_path(args[1])
end
def git_cmds
@@ -51,17 +55,17 @@ class GitlabShell
def process_cmd
repo_full_path = File.join(repos_path, repo_name)
- cmd = "#{@git_cmd} #{repo_full_path}"
- $logger.info "gitlab-shell: executing git command <#{cmd}> for #{log_username}."
- exec_cmd(cmd)
+ $logger.info "gitlab-shell: executing git command <#{@git_cmd} #{repo_full_path}> for #{log_username}."
+ exec_cmd(@git_cmd, repo_full_path)
end
def validate_access
api.allowed?(@git_cmd, @repo_name, @key_id, '_any')
end
- def exec_cmd args
- Kernel::exec args
+ # This method is not covered by Rspec because it ends the current Ruby process.
+ def exec_cmd(*args)
+ Kernel::exec({'PATH' => ENV['PATH'], 'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'], 'GL_ID' => ENV['GL_ID']}, *args, unsetenv_others: true)
end
def api
@@ -70,10 +74,9 @@ class GitlabShell
def user
# Can't use "@user ||=" because that will keep hitting the API when @user is really nil!
- if @user_tried
+ if instance_variable_defined?('@user')
@user
else
- @user_tried = true
@user = api.discover(@key_id)
end
end
@@ -86,4 +89,14 @@ class GitlabShell
def log_username
@config.audit_usernames ? username : "user with key #{@key_id}"
end
+
+ def escape_path(path)
+ full_repo_path = File.join(repos_path, path)
+
+ if File.absolute_path(full_repo_path) == full_repo_path
+ path
+ else
+ abort "Wrong repository path"
+ end
+ end
end
diff --git a/lib/gitlab_update.rb b/lib/gitlab_update.rb
deleted file mode 100644
index 40ddc6e..0000000
--- a/lib/gitlab_update.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require_relative 'gitlab_init'
-require_relative 'gitlab_net'
-
-class GitlabUpdate
- attr_reader :config
-
- def initialize(repo_path, key_id, refname)
- @config = GitlabConfig.new
-
- @repo_path = repo_path.strip
- @repo_name = repo_path
- @repo_name.gsub!(config.repos_path.to_s, "")
- @repo_name.gsub!(/\.git$/, "")
- @repo_name.gsub!(/^\//, "")
-
- @key_id = key_id
- @refname = refname
- @branch_name = /refs\/heads\/([\/\w\.-]+)/.match(refname).to_a.last
-
- @oldrev = ARGV[1]
- @newrev = ARGV[2]
- end
-
- def exec
- # reset GL_ID env since we already
- # get value from it
- ENV['GL_ID'] = nil
-
- # If its push over ssh
- # we need to check user permission per branch first
- if ssh?
- if api.allowed?('git-receive-pack', @repo_name, @key_id, @branch_name)
- update_redis
- exit 0
- else
- puts "GitLab: You are not allowed to access #{@branch_name}! "
- exit 1
- end
- else
- update_redis
- exit 0
- end
- end
-
- protected
-
- def api
- GitlabNet.new
- end
-
- def ssh?
- @key_id =~ /\Akey\-\d+\Z/
- end
-
- def update_redis
- command = "#{config.redis_command} rpush '#{config.redis_namespace}:queue:post_receive' '{\"class\":\"PostReceive\",\"args\":[\"#{@repo_path}\",\"#{@oldrev}\",\"#{@newrev}\",\"#{@refname}\",\"#{@key_id}\"]}' > /dev/null 2>&1"
- system(command)
- end
-end
diff --git a/lib/names_helper.rb b/lib/names_helper.rb
new file mode 100644
index 0000000..efad56f
--- /dev/null
+++ b/lib/names_helper.rb
@@ -0,0 +1,13 @@
+module NamesHelper
+ def extract_repo_name(path, base)
+ repo_name = path.strip
+ repo_name.gsub!(base, "")
+ repo_name.gsub!(/\.git$/, "")
+ repo_name.gsub!(/^\//, "")
+ repo_name
+ end
+
+ def extract_ref_name(ref)
+ ref.gsub(/\Arefs\/(tags|heads)\//, '')
+ end
+end
diff --git a/spec/gitlab_access_spec.rb b/spec/gitlab_access_spec.rb
new file mode 100644
index 0000000..13db4bd
--- /dev/null
+++ b/spec/gitlab_access_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'gitlab_access'
+
+describe GitlabAccess do
+ let(:repository_path) { "/home/git/repositories" }
+ let(:repo_name) { 'dzaporozhets/gitlab-ci' }
+ let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
+ let(:gitlab_access) { GitlabAccess.new(repo_path, 'key-123', 'wow') }
+
+ before do
+ GitlabConfig.any_instance.stub(repos_path: repository_path)
+ end
+
+ describe :initialize do
+ it { gitlab_access.repo_name.should == repo_name }
+ it { gitlab_access.repo_path.should == repo_path }
+ it { gitlab_access.changes.should == ['wow'] }
+ end
+end
diff --git a/spec/gitlab_config_spec.rb b/spec/gitlab_config_spec.rb
new file mode 100644
index 0000000..52fb182
--- /dev/null
+++ b/spec/gitlab_config_spec.rb
@@ -0,0 +1,81 @@
+require_relative 'spec_helper'
+require_relative '../lib/gitlab_config'
+
+describe GitlabConfig do
+ let(:config) { GitlabConfig.new }
+
+ describe :redis do
+ before do
+ config.instance_variable_set(:@config, YAML.load(<<eos
+redis:
+ bin: /usr/bin/redis-cli
+ host: 127.0.1.1
+ port: 6378
+ pass: secure
+ database: 1
+ socket: /var/run/redis/redis.sock
+ namespace: my:gitlab
+eos
+ ))
+ end
+
+ it { config.redis['bin'].should eq('/usr/bin/redis-cli') }
+ it { config.redis['host'].should eq('127.0.1.1') }
+ it { config.redis['port'].should eq(6378) }
+ it { config.redis['database'].should eq(1) }
+ it { config.redis['namespace'].should eq('my:gitlab') }
+ it { config.redis['socket'].should eq('/var/run/redis/redis.sock') }
+ it { config.redis['pass'].should eq('secure') }
+ end
+
+ describe :gitlab_url do
+ let(:url) { 'http://test.com' }
+ subject { config.gitlab_url }
+ before { config.send(:config)['gitlab_url'] = url }
+
+ it { should_not be_empty }
+ it { should eq(url) }
+ end
+
+ describe :audit_usernames do
+ subject { config.audit_usernames }
+
+ it("returns false by default") { should eq(false) }
+ end
+
+ describe :redis_command do
+ subject { config.redis_command }
+
+ context "with empty redis config" do
+ before do
+ config.stub(:redis) { {} }
+ end
+
+ it { should be_an(Array) }
+ it { should include('redis-cli') }
+ end
+
+ context "with host and port" do
+ before do
+ config.stub(:redis) { {'host' => 'localhost', 'port' => 1123, 'bin' => '/usr/bin/redis-cli'} }
+ end
+
+ it { should be_an(Array) }
+ it { should include(config.redis['host']) }
+ it { should include(config.redis['bin']) }
+ it { should include(config.redis['port'].to_s) }
+ end
+
+ context "with redis socket" do
+ let(:socket) { '/tmp/redis.socket' }
+ before do
+ config.stub(:redis) { {'bin' => '', 'socket' => socket } }
+ end
+
+ it { should be_an(Array) }
+ it { should include(socket) }
+ it { should_not include('-p') }
+ it { should_not include('-h') }
+ end
+ end
+end
diff --git a/spec/gitlab_keys_spec.rb b/spec/gitlab_keys_spec.rb
index c888b8e..bcce628 100644
--- a/spec/gitlab_keys_spec.rb
+++ b/spec/gitlab_keys_spec.rb
@@ -1,5 +1,6 @@
require_relative 'spec_helper'
require_relative '../lib/gitlab_keys'
+require 'stringio'
describe GitlabKeys do
before do
@@ -14,36 +15,131 @@ describe GitlabKeys do
it { gitlab_keys.instance_variable_get(:@key_id).should == 'key-741' }
end
+
describe :add_key do
let(:gitlab_keys) { build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') }
- it "should receive valid cmd" do
- valid_cmd = "echo 'command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E' >> #{GitlabConfig.new.auth_file}"
- gitlab_keys.should_receive(:system).with(valid_cmd)
+ it "adds a line at the end of the file" do
+ create_authorized_keys_fixture
gitlab_keys.send :add_key
+ auth_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
+ File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line}\n"
+ end
+
+ context "without file writing" do
+ before { gitlab_keys.stub(:open) }
+ before { create_authorized_keys_fixture }
+
+ it "should log an add-key event" do
+ $logger.should_receive(:info).with('Adding key key-741 => "ssh-rsa AAAAB3NzaDAxx2E"')
+ gitlab_keys.send :add_key
+ end
+
+ it "should return true" do
+ gitlab_keys.send(:add_key).should be_true
+ end
+ end
+ end
+
+ describe :list_keys do
+ let(:gitlab_keys) do
+ build_gitlab_keys('add-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E')
end
- it "should log an add-key event" do
- $logger.should_receive(:info).with('Adding key key-741 => "ssh-rsa AAAAB3NzaDAxx2E"')
+ it 'adds a key and lists it' do
+ create_authorized_keys_fixture
gitlab_keys.send :add_key
+ auth_line1 = 'key-741 AAAAB3NzaDAxx2E'
+ gitlab_keys.send(:list_keys).should == "#{auth_line1}\n"
end
end
+ describe :batch_add_keys do
+ let(:gitlab_keys) { build_gitlab_keys('batch-add-keys') }
+ let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\n", 'r') }
+ before do
+ create_authorized_keys_fixture
+ gitlab_keys.stub(stdin: fake_stdin)
+ end
+
+ it "adds lines at the end of the file" do
+ gitlab_keys.send :batch_add_keys
+ auth_line1 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG"
+ auth_line2 = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG"
+ File.read(tmp_authorized_keys_path).should == "existing content\n#{auth_line1}\n#{auth_line2}\n"
+ end
+
+ context "with invalid input" do
+ let(:fake_stdin) { StringIO.new("key-12\tssh-dsa ASDFASGADG\nkey-123\tssh-rsa GFDGDFSGSDFG\nfoo\tbar\tbaz\n", 'r') }
+
+ it "aborts" do
+ gitlab_keys.should_receive(:abort)
+ gitlab_keys.send :batch_add_keys
+ end
+ end
+
+ context "without file writing" do
+ before do
+ gitlab_keys.should_receive(:open).and_yield(mock(:file, puts: nil))
+ end
+
+ it "should log an add-key event" do
+ $logger.should_receive(:info).with('Adding key key-12 => "ssh-dsa ASDFASGADG"')
+ $logger.should_receive(:info).with('Adding key key-123 => "ssh-rsa GFDGDFSGSDFG"')
+ gitlab_keys.send :batch_add_keys
+ end
+
+ it "should return true" do
+ gitlab_keys.send(:batch_add_keys).should be_true
+ end
+ end
+ end
+
+ describe :stdin do
+ let(:gitlab_keys) { build_gitlab_keys }
+ subject { gitlab_keys.send :stdin }
+ before { $stdin = 1 }
+
+ it { should equal(1) }
+ end
+
describe :rm_key do
let(:gitlab_keys) { build_gitlab_keys('rm-key', 'key-741', 'ssh-rsa AAAAB3NzaDAxx2E') }
- let(:temp_file) { mock(:temp_file, path: 'tmp_path') }
- before { Tempfile.should_receive(:open).and_yield(temp_file) }
- it "should receive valid cmd" do
- auth_file = GitlabConfig.new.auth_file
- valid_cmd = "sed '/shell key-741\"/d' #{auth_file} > tmp_path && mv tmp_path #{auth_file}"
- gitlab_keys.should_receive(:system).with(valid_cmd)
+ it "removes the right line" do
+ create_authorized_keys_fixture
+ other_line = "command=\"#{ROOT_PATH}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E"
+ open(tmp_authorized_keys_path, 'a') do |auth_file|
+ auth_file.puts "command=\"#{ROOT_PATH}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E"
+ auth_file.puts other_line
+ end
gitlab_keys.send :rm_key
+ File.read(tmp_authorized_keys_path).should == "existing content\n#{other_line}\n"
end
- it "should log an rm-key event" do
- $logger.should_receive(:info).with('Removing key key-741')
- gitlab_keys.send :rm_key
+ context "without file writing" do
+ before do
+ Tempfile.stub(:open)
+ gitlab_keys.stub(:lock).and_yield
+ end
+
+ it "should log an rm-key event" do
+ $logger.should_receive(:info).with('Removing key key-741')
+ gitlab_keys.send :rm_key
+ end
+
+ it "should return true" do
+ gitlab_keys.send(:rm_key).should be_true
+ end
+ end
+ end
+
+ describe :clear do
+ let(:gitlab_keys) { build_gitlab_keys('clear') }
+
+ it "should return true" do
+ gitlab_keys.stub(:open)
+ gitlab_keys.send(:clear).should be_true
end
end
@@ -54,12 +150,24 @@ describe GitlabKeys do
gitlab_keys.exec
end
+ it 'batch-add-keys arg should execute batch_add_keys method' do
+ gitlab_keys = build_gitlab_keys('batch-add-keys')
+ gitlab_keys.should_receive(:batch_add_keys)
+ gitlab_keys.exec
+ end
+
it 'rm-key arg should execute rm_key method' do
gitlab_keys = build_gitlab_keys('rm-key')
gitlab_keys.should_receive(:rm_key)
gitlab_keys.exec
end
+ it 'clear arg should execute clear method' do
+ gitlab_keys = build_gitlab_keys('clear')
+ gitlab_keys.should_receive(:clear)
+ gitlab_keys.exec
+ end
+
it 'should puts message if unknown command arg' do
gitlab_keys = build_gitlab_keys('change-key')
gitlab_keys.should_receive(:puts).with('not allowed')
@@ -74,6 +182,46 @@ describe GitlabKeys do
end
end
+ describe :lock do
+ before do
+ GitlabKeys.any_instance.stub(lock_file: tmp_lock_file_path)
+ end
+
+ it "should raise exception if operation lasts more then timeout" do
+ key = GitlabKeys.new
+ expect do
+ key.send :lock, 1 do
+ sleep 2
+ end
+ end.to raise_error
+ end
+
+ it "should actually lock file" do
+ $global = ""
+ key = GitlabKeys.new
+
+ thr1 = Thread.new do
+ key.send :lock do
+ # Put bigger sleep here to test if main thread will
+ # wait for lock file released before executing code
+ sleep 1
+ $global << "foo"
+ end
+ end
+
+ # make sure main thread start lock command after
+ # thread above
+ sleep 0.5
+
+ key.send :lock do
+ $global << "bar"
+ end
+
+ thr1.join
+ $global.should == "foobar"
+ end
+ end
+
def build_gitlab_keys(*args)
argv(*args)
GitlabKeys.new
@@ -84,4 +232,18 @@ describe GitlabKeys do
ARGV[i] = arg
end
end
+
+ def create_authorized_keys_fixture
+ FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path))
+ open(tmp_authorized_keys_path, 'w') { |file| file.puts('existing content') }
+ gitlab_keys.stub(auth_file: tmp_authorized_keys_path)
+ end
+
+ def tmp_authorized_keys_path
+ File.join(ROOT_PATH, 'tmp', 'authorized_keys')
+ end
+
+ def tmp_lock_file_path
+ tmp_authorized_keys_path + '.lock'
+ end
end
diff --git a/spec/gitlab_logger_spec.rb b/spec/gitlab_logger_spec.rb
new file mode 100644
index 0000000..7d8df76
--- /dev/null
+++ b/spec/gitlab_logger_spec.rb
@@ -0,0 +1,11 @@
+require_relative 'spec_helper'
+require_relative '../lib/gitlab_logger'
+
+describe :convert_log_level do
+ subject { convert_log_level :extreme }
+
+ it "converts invalid log level to Logger::INFO" do
+ $stderr.should_receive(:puts).at_least(:once)
+ should eq(Logger::INFO)
+ end
+end
diff --git a/spec/gitlab_net_spec.rb b/spec/gitlab_net_spec.rb
index 965fa8e..9ccabe0 100644
--- a/spec/gitlab_net_spec.rb
+++ b/spec/gitlab_net_spec.rb
@@ -4,9 +4,11 @@ require_relative '../lib/gitlab_net'
describe GitlabNet, vcr: true do
let(:gitlab_net) { GitlabNet.new }
+ let(:changes) { ['0000000000000000000000000000000000000000 92d0970eefd7acb6d548878925ce2208cfe2d2ec refs/heads/branch4'] }
before do
gitlab_net.stub!(:host).and_return('https://dev.gitlab.org/api/v3/internal')
+ gitlab_net.stub!(:secret_token).and_return('a123')
end
describe :check do
@@ -16,29 +18,50 @@ describe GitlabNet, vcr: true do
result.code.should == '200'
end
end
+
+ it 'adds the secret_token to request' do
+ VCR.use_cassette("check-ok") do
+ Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123'))
+ gitlab_net.check
+ end
+ end
end
describe :discover do
it 'should return user has based on key id' do
VCR.use_cassette("discover-ok") do
- user = gitlab_net.discover('key-1')
+ user = gitlab_net.discover('key-126')
user['name'].should == 'Dmitriy Zaporozhets'
end
end
+
+ it 'adds the secret_token to request' do
+ VCR.use_cassette("discover-ok") do
+ Net::HTTP::Get.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123'))
+ gitlab_net.discover('key-126')
+ end
+ end
end
describe :allowed? do
context 'ssh key with access to project' do
it 'should allow pull access for dev.gitlab.org' do
VCR.use_cassette("allowed-pull") do
- access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-1', 'master')
+ access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-126', changes)
access.should be_true
end
end
+ it 'adds the secret_token theo request' do
+ VCR.use_cassette("allowed-pull") do
+ Net::HTTP::Post.any_instance.should_receive(:set_form_data).with(hash_including(secret_token: 'a123'))
+ gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-126', changes)
+ end
+ end
+
it 'should allow push access for dev.gitlab.org' do
VCR.use_cassette("allowed-push") do
- access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-1', 'master')
+ access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-126', changes)
access.should be_true
end
end
@@ -47,17 +70,92 @@ describe GitlabNet, vcr: true do
context 'ssh key without access to project' do
it 'should deny pull access for dev.gitlab.org' do
VCR.use_cassette("denied-pull") do
- access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-2', 'master')
+ access = gitlab_net.allowed?('git-receive-pack', 'gitlab/gitlabhq.git', 'key-2', changes)
access.should be_false
end
end
it 'should deny push access for dev.gitlab.org' do
VCR.use_cassette("denied-push") do
- access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-2', 'master')
+ access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'key-2', changes)
+ access.should be_false
+ end
+ end
+
+ it 'should deny push access for dev.gitlab.org (with user)' do
+ VCR.use_cassette("denied-push-with-user") do
+ access = gitlab_net.allowed?('git-upload-pack', 'gitlab/gitlabhq.git', 'user-1', changes)
access.should be_false
end
end
end
end
+
+ describe :host do
+ let(:net) { GitlabNet.new }
+ subject { net.send :host }
+
+ it { should include(net.send(:config).gitlab_url) }
+ it("uses API version 3") { should include("api/v3") }
+ end
+
+ describe :http_client_for do
+ subject { gitlab_net.send :http_client_for, URI('https://localhost/') }
+ before do
+ gitlab_net.stub! :cert_store
+ gitlab_net.send(:config).http_settings.stub(:[]).with('self_signed_cert') { true }
+ end
+
+ its(:verify_mode) { should eq(OpenSSL::SSL::VERIFY_NONE) }
+ end
+
+ describe :http_request_for do
+ let(:get) do
+ double(Net::HTTP::Get).tap do |get|
+ Net::HTTP::Get.stub(:new) { get }
+ end
+ end
+ let(:user) { 'user' }
+ let(:password) { 'password' }
+ let(:url) { URI 'http://localhost/' }
+ subject { gitlab_net.send :http_request_for, url }
+
+ before do
+ gitlab_net.send(:config).http_settings.stub(:[]).with('user') { user }
+ gitlab_net.send(:config).http_settings.stub(:[]).with('password') { password }
+ get.should_receive(:basic_auth).with(user, password).once
+ end
+
+ it { should_not be_nil }
+ end
+
+ describe :cert_store do
+ let(:store) do
+ double(OpenSSL::X509::Store).tap do |store|
+ OpenSSL::X509::Store.stub(:new) { store }
+ end
+ end
+
+ before :each do
+ store.should_receive(:set_default_paths).once
+ end
+
+ after do
+ gitlab_net.send :cert_store
+ end
+
+ it "calls add_file with http_settings['ca_file']" do
+ gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { 'test_file' }
+ gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { nil }
+ store.should_receive(:add_file).with('test_file')
+ store.should_not_receive(:add_path)
+ end
+
+ it "calls add_path with http_settings['ca_path']" do
+ gitlab_net.send(:config).http_settings.stub(:[]).with('ca_file') { nil }
+ gitlab_net.send(:config).http_settings.stub(:[]).with('ca_path') { 'test_path' }
+ store.should_not_receive(:add_file)
+ store.should_receive(:add_path).with('test_path')
+ end
+ end
end
diff --git a/spec/gitlab_post_receive_spec.rb b/spec/gitlab_post_receive_spec.rb
new file mode 100644
index 0000000..50c6f0a
--- /dev/null
+++ b/spec/gitlab_post_receive_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+require 'gitlab_post_receive'
+
+describe GitlabPostReceive do
+ let(:repository_path) { "/home/git/repositories" }
+ let(:repo_name) { 'dzaporozhets/gitlab-ci' }
+ let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
+ let(:gitlab_post_receive) { GitlabPostReceive.new(repo_path, 'key-123', 'wow') }
+
+ before do
+ GitlabConfig.any_instance.stub(repos_path: repository_path)
+ end
+
+ describe :initialize do
+ it { gitlab_post_receive.repo_path.should == repo_path }
+ it { gitlab_post_receive.changes.should == 'wow' }
+ end
+end
diff --git a/spec/gitlab_projects_spec.rb b/spec/gitlab_projects_spec.rb
index 6ff72ec..a592e62 100644
--- a/spec/gitlab_projects_spec.rb
+++ b/spec/gitlab_projects_spec.rb
@@ -31,8 +31,8 @@ describe GitlabProjects do
it "should create a branch" do
gl_projects_create.exec
gl_projects.exec
- branch_ref = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip
- master_ref = `cd #{tmp_repo_path} && git rev-parse master`.strip
+ branch_ref = capture_in_tmp_repo(%W(git rev-parse test_branch))
+ master_ref = capture_in_tmp_repo(%W(git rev-parse master))
branch_ref.should == master_ref
end
end
@@ -49,9 +49,9 @@ describe GitlabProjects do
it "should remove a branch" do
gl_projects_create.exec
gl_projects_create_branch.exec
- branch_ref = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip
+ branch_ref = capture_in_tmp_repo(%W(git rev-parse test_branch))
gl_projects.exec
- branch_del = `cd #{tmp_repo_path} && git rev-parse test_branch`.strip
+ branch_del = capture_in_tmp_repo(%W(git rev-parse test_branch))
branch_del.should_not == branch_ref
end
end
@@ -60,14 +60,36 @@ describe GitlabProjects do
let(:gl_projects_create) {
build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git')
}
- let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, 'test_tag', 'master') }
+ context "lightweight tag" do
+ let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, 'test_tag', 'master') }
+
+ it "should create a tag" do
+ gl_projects_create.exec
+ gl_projects.exec
+ tag_ref = capture_in_tmp_repo(%W(git rev-parse test_tag))
+ master_ref = capture_in_tmp_repo(%W(git rev-parse master))
+ tag_ref.should == master_ref
+ end
+ end
+ context "annotated tag" do
+ msg = 'some message'
+ tag_name = 'test_annotated_tag'
- it "should create a tag" do
- gl_projects_create.exec
- gl_projects.exec
- tag_ref = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip
- master_ref = `cd #{tmp_repo_path} && git rev-parse master`.strip
- tag_ref.should == master_ref
+ let(:gl_projects) { build_gitlab_projects('create-tag', repo_name, tag_name, 'master', msg) }
+
+ it "should create an annotated tag" do
+ gl_projects_create.exec
+ system(*%W(git --git-dir=#{tmp_repo_path} config user.name Joe))
+ system(*%W(git --git-dir=#{tmp_repo_path} config user.email joe@smith.com))
+ gl_projects.exec
+
+ tag_ref = capture_in_tmp_repo(%W(git rev-parse #{tag_name}^{}))
+ master_ref = capture_in_tmp_repo(%W(git rev-parse master))
+ tag_msg = capture_in_tmp_repo(%W(git tag -l -n1 #{tag_name}))
+
+ tag_ref.should == master_ref
+ tag_msg.should == tag_name + ' ' + msg
+ end
end
end
@@ -83,9 +105,9 @@ describe GitlabProjects do
it "should remove a branch" do
gl_projects_create.exec
gl_projects_create_tag.exec
- branch_ref = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip
+ branch_ref = capture_in_tmp_repo(%W(git rev-parse test_tag))
gl_projects.exec
- branch_del = `cd #{tmp_repo_path} && git rev-parse test_tag`.strip
+ branch_del = capture_in_tmp_repo(%W(git rev-parse test_tag))
branch_del.should_not == branch_ref
end
end
@@ -95,14 +117,15 @@ describe GitlabProjects do
it "should create a directory" do
gl_projects.stub(system: true)
+ GitlabProjects.stub(create_hooks: true)
gl_projects.exec
File.exists?(tmp_repo_path).should be_true
end
it "should receive valid cmd" do
- valid_cmd = "cd #{tmp_repo_path} && git init --bare"
- valid_cmd << " && ln -s #{ROOT_PATH}/hooks/update #{tmp_repo_path}/hooks/update"
- gl_projects.should_receive(:system).with(valid_cmd)
+ valid_cmd = ['git', "--git-dir=#{tmp_repo_path}", 'init', '--bare']
+ gl_projects.should_receive(:system).with(*valid_cmd).and_return(true)
+ GitlabProjects.should_receive(:create_hooks).with(tmp_repo_path)
gl_projects.exec
end
@@ -112,6 +135,22 @@ describe GitlabProjects do
end
end
+ describe :list_projects do
+ let(:gl_projects) do
+ build_gitlab_projects('add-project', "list_test/#{repo_name}")
+ end
+
+ before do
+ FileUtils.mkdir_p(tmp_repos_path)
+ end
+
+ it 'should create projects and list them' do
+ GitlabProjects.stub(create_hooks: true)
+ gl_projects.exec
+ gl_projects.send(:list_projects).should == ["list_test/#{repo_name}"]
+ end
+ end
+
describe :mv_project do
let(:gl_projects) { build_gitlab_projects('mv-project', repo_name, 'repo.git') }
let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') }
@@ -175,11 +214,12 @@ describe GitlabProjects do
describe :update_head do
let(:gl_projects) { build_gitlab_projects('update-head', repo_name, 'stable') }
+ let(:gl_projects_fail) { build_gitlab_projects 'update-head', repo_name }
before do
FileUtils.mkdir_p(tmp_repo_path)
- system("git init --bare #{tmp_repo_path}")
- system("touch #{tmp_repo_path}/refs/heads/stable")
+ system(*%W(git init --bare #{tmp_repo_path}))
+ FileUtils.touch(File.join(tmp_repo_path, "refs/heads/stable"))
File.read(File.join(tmp_repo_path, 'HEAD')).strip.should == 'ref: refs/heads/master'
end
@@ -192,20 +232,55 @@ describe GitlabProjects do
$logger.should_receive(:info).with("Update head in project #{repo_name} to <stable>.")
gl_projects.exec
end
+
+ it "should failed and log an error" do
+ $logger.should_receive(:error).with("update-head failed: no branch provided.")
+ gl_projects_fail.exec.should be_false
+ end
end
describe :import_project do
- let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') }
+ context 'success import' do
+ let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') }
- it "should import a repo" do
- gl_projects.exec
- File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_true
+ it { gl_projects.exec.should be_true }
+
+ it "should import a repo" do
+ gl_projects.exec
+ File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_true
+ end
+
+ it "should log an import-project event" do
+ message = "Importing project #{repo_name} from <https://github.com/randx/six.git> to <#{tmp_repo_path}>."
+ $logger.should_receive(:info).with(message)
+ gl_projects.exec
+ end
end
- it "should log an import-project event" do
- message = "Importing project #{repo_name} from <https://github.com/randx/six.git> to <#{tmp_repo_path}>."
- $logger.should_receive(:info).with(message)
- gl_projects.exec
+ context 'already exists' do
+ let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/randx/six.git') }
+
+ it 'should import only once' do
+ gl_projects.exec.should be_true
+ gl_projects.exec.should be_false
+ end
+ end
+
+ context 'timeout' do
+ let(:gl_projects) { build_gitlab_projects('import-project', repo_name, 'https://github.com/gitlabhq/gitlabhq.git', '1') }
+
+ it { gl_projects.exec.should be_false }
+
+ it "should not import a repo" do
+ gl_projects.exec
+ File.exists?(File.join(tmp_repo_path, 'HEAD')).should be_false
+ end
+
+ it "should log an import-project event" do
+ message = "Importing project #{repo_name} from <https://github.com/gitlabhq/gitlabhq.git> failed due to timeout."
+ $logger.should_receive(:error).with(message)
+ gl_projects.exec
+ end
end
end
@@ -236,7 +311,8 @@ describe GitlabProjects do
FileUtils.mkdir_p(File.join(tmp_repos_path, 'forked-to-namespace'))
gl_projects_fork.exec.should be_true
File.exists?(dest_repo).should be_true
- File.exists?(File.join(dest_repo, '/hooks/update')).should be_true
+ File.exists?(File.join(dest_repo, '/hooks/pre-receive')).should be_true
+ File.exists?(File.join(dest_repo, '/hooks/post-receive')).should be_true
end
it "should not fork if a project of the same name already exists" do
@@ -299,4 +375,8 @@ describe GitlabProjects do
def repo_name
'gitlab-ci.git'
end
+
+ def capture_in_tmp_repo(cmd)
+ IO.popen([*cmd, {chdir: tmp_repo_path}]).read.strip
+ end
end
diff --git a/spec/gitlab_shell_spec.rb b/spec/gitlab_shell_spec.rb
index 44dca6d..4741303 100644
--- a/spec/gitlab_shell_spec.rb
+++ b/spec/gitlab_shell_spec.rb
@@ -48,6 +48,14 @@ describe GitlabShell do
its(:repo_name) { should == 'dmitriy.zaporozhets/gitlab-ci.git' }
its(:git_cmd) { should == 'git-upload-pack' }
end
+
+ context 'with an invalid number of arguments' do
+ before { ssh_cmd 'foobar' }
+
+ it "should raise an DisallowedCommandError" do
+ expect { subject.send :parse_cmd }.to raise_error(GitlabShell::DisallowedCommandError)
+ end
+ end
end
describe :exec do
@@ -60,7 +68,7 @@ describe GitlabShell do
end
it "should execute the command" do
- subject.should_receive(:exec_cmd).with("git-upload-pack #{File.join(repository_path, 'gitlab-ci.git')}")
+ subject.should_receive(:exec_cmd).with("git-upload-pack", File.join(repository_path, 'gitlab-ci.git'))
end
it "should set the GL_ID environment variable" do
@@ -89,7 +97,7 @@ describe GitlabShell do
end
it "should execute the command" do
- subject.should_receive(:exec_cmd).with("git-receive-pack #{File.join(repository_path, 'gitlab-ci.git')}")
+ subject.should_receive(:exec_cmd).with("git-receive-pack", File.join(repository_path, 'gitlab-ci.git'))
end
it "should log the command execution" do
@@ -145,6 +153,31 @@ describe GitlabShell do
end
end
+ describe :exec_cmd do
+ let(:shell) { GitlabShell.new }
+ before { Kernel.stub!(:exec) }
+
+ it "uses Kernel::exec method" do
+ Kernel.should_receive(:exec).with(kind_of(Hash), 1, unsetenv_others: true).once
+ shell.send :exec_cmd, 1
+ end
+ end
+
+ describe :api do
+ let(:shell) { GitlabShell.new }
+ subject { shell.send :api }
+
+ it { should be_a(GitlabNet) }
+ end
+
+ describe :escape_path do
+ let(:shell) { GitlabShell.new }
+ before { File.stub(:absolute_path) { 'y' } }
+ subject { -> { shell.send(:escape_path, 'z') } }
+
+ it { should raise_error(SystemExit, "Wrong repository path") }
+ end
+
def ssh_cmd(cmd)
ENV['SSH_ORIGINAL_COMMAND'] = cmd
end
diff --git a/spec/names_helper_spec.rb b/spec/names_helper_spec.rb
new file mode 100644
index 0000000..081dac9
--- /dev/null
+++ b/spec/names_helper_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+require 'names_helper'
+
+describe NamesHelper do
+ include NamesHelper
+
+ describe :extract_repo_name do
+ it { extract_repo_name(' /opt/repos/randx/gitlab.git', '/opt/repos').should == 'randx/gitlab' }
+ it { extract_repo_name("/opt/repos/randx/gitlab.git\r\n", '/opt/repos/').should == 'randx/gitlab' }
+ end
+
+ describe :extract_ref_name do
+ it { extract_ref_name('refs/heads/awesome-feature').should == 'awesome-feature' }
+ it { extract_ref_name('refs/tags/v2.2.1').should == 'v2.2.1' }
+ it { extract_ref_name('refs/tags/releases/v2.2.1').should == 'releases/v2.2.1' }
+ end
+end
+
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ea3872d..612af76 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,8 +1,11 @@
ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), ".."))
-if ENV['TRAVIS']
+if ENV['COVERALLS']
require 'coveralls'
Coveralls.wear!
+else
+ require 'simplecov'
+ SimpleCov.start
end
require 'vcr'
diff --git a/spec/vcr_cassettes/allowed-pull.yml b/spec/vcr_cassettes/allowed-pull.yml
index db2ef29..337b00f 100644
--- a/spec/vcr_cassettes/allowed-pull.yml
+++ b/spec/vcr_cassettes/allowed-pull.yml
@@ -1,16 +1,20 @@
---
http_interactions:
- request:
- method: get
- uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=1&project=gitlab/gitlabhq&ref=master
+ method: post
+ uri: https://dev.gitlab.org/api/v3/internal/allowed
body:
encoding: US-ASCII
- string: ''
+ string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=126&secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
+ Content-Type:
+ - application/x-www-form-urlencoded
response:
status:
code: 200
@@ -19,7 +23,7 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 12:13:31 GMT
+ - Wed, 03 Sep 2014 11:27:36 GMT
Content-Type:
- application/json
Content-Length:
@@ -28,21 +32,17 @@ http_interactions:
- keep-alive
Status:
- 200 OK
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Etag:
- - ! '"b326b5062b2f0e69046810717534cb09"'
+ - '"b326b5062b2f0e69046810717534cb09"'
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- - ac00b27743e177559476f6575e45d1c2
+ - 85bfa9b3-fddc-4810-a033-f1eabc04c66c
X-Runtime:
- - '0.017272'
- X-Rack-Cache:
- - miss
+ - '0.089741'
body:
- encoding: US-ASCII
+ encoding: UTF-8
string: 'true'
http_version:
- recorded_at: Mon, 11 Mar 2013 12:13:31 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:36 GMT
recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/allowed-push.yml b/spec/vcr_cassettes/allowed-push.yml
index 96b8c9a..cb757bf 100644
--- a/spec/vcr_cassettes/allowed-push.yml
+++ b/spec/vcr_cassettes/allowed-push.yml
@@ -1,16 +1,20 @@
---
http_interactions:
- request:
- method: get
- uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-upload-pack&key_id=1&project=gitlab/gitlabhq&ref=master
+ method: post
+ uri: https://dev.gitlab.org/api/v3/internal/allowed
body:
encoding: US-ASCII
- string: ''
+ string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=126&secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
+ Content-Type:
+ - application/x-www-form-urlencoded
response:
status:
code: 200
@@ -19,7 +23,7 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 12:13:32 GMT
+ - Wed, 03 Sep 2014 11:27:37 GMT
Content-Type:
- application/json
Content-Length:
@@ -28,21 +32,17 @@ http_interactions:
- keep-alive
Status:
- 200 OK
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Etag:
- - ! '"b326b5062b2f0e69046810717534cb09"'
+ - '"b326b5062b2f0e69046810717534cb09"'
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- - c65f8ea13ac34cf182fcb6daefeb1bdd
+ - 115e7a72-b3a6-49e1-b531-980c71088c9d
X-Runtime:
- - '0.018934'
- X-Rack-Cache:
- - miss
+ - '0.833195'
body:
- encoding: US-ASCII
+ encoding: UTF-8
string: 'true'
http_version:
- recorded_at: Mon, 11 Mar 2013 12:13:31 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:37 GMT
recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/check-ok.yml b/spec/vcr_cassettes/check-ok.yml
index bac5d43..1ba8b04 100644
--- a/spec/vcr_cassettes/check-ok.yml
+++ b/spec/vcr_cassettes/check-ok.yml
@@ -5,10 +5,12 @@ http_interactions:
uri: https://dev.gitlab.org/api/v3/internal/check
body:
encoding: US-ASCII
- string: ''
+ string: secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
response:
@@ -19,30 +21,26 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 13:05:30 GMT
+ - Wed, 03 Sep 2014 11:27:35 GMT
Content-Type:
- application/json
Content-Length:
- - '71'
+ - '72'
Connection:
- keep-alive
Status:
- 200 OK
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Etag:
- - ! '"cb194c2a4bfe01ae695d15ad834b7f5c"'
+ - '"23ee74bf6981b57b9d0e0d8a91f3ec1b"'
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- - 66424e020a58b1fc50efcf846e301b40
+ - c389e282-4d7b-4041-9aed-336e5b007424
X-Runtime:
- - '0.003728'
- X-Rack-Cache:
- - miss
+ - '0.958718'
body:
- encoding: US-ASCII
- string: ! '{"api_version":"v3","gitlab_version":"5.0.0pre","gitlab_rev":"473efc8"}'
+ encoding: UTF-8
+ string: '{"api_version":"v3","gitlab_version":"7.3.0.pre","gitlab_rev":"e8f1331"}'
http_version:
- recorded_at: Mon, 11 Mar 2013 13:05:30 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:35 GMT
recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/denied-pull.yml b/spec/vcr_cassettes/denied-pull.yml
index ef71121..9941e70 100644
--- a/spec/vcr_cassettes/denied-pull.yml
+++ b/spec/vcr_cassettes/denied-pull.yml
@@ -1,16 +1,20 @@
---
http_interactions:
- request:
- method: get
- uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=2&project=gitlab/gitlabhq&ref=master
+ method: post
+ uri: https://dev.gitlab.org/api/v3/internal/allowed
body:
encoding: US-ASCII
- string: ''
+ string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=2&secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
+ Content-Type:
+ - application/x-www-form-urlencoded
response:
status:
code: 404
@@ -19,7 +23,7 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 12:21:31 GMT
+ - Wed, 03 Sep 2014 11:27:38 GMT
Content-Type:
- application/json
Content-Length:
@@ -28,19 +32,15 @@ http_interactions:
- keep-alive
Status:
- 404 Not Found
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Cache-Control:
- no-cache
X-Request-Id:
- - bda6c4046ea050c5bad39a38337f5771
+ - f9c23a82-be65-41c8-958a-5f3e1b9aa58b
X-Runtime:
- - '0.005632'
- X-Rack-Cache:
- - miss
+ - '0.028027'
body:
- encoding: US-ASCII
- string: ! '{"message":"404 Not found"}'
+ encoding: UTF-8
+ string: '{"message":"404 Not found"}'
http_version:
- recorded_at: Mon, 11 Mar 2013 12:21:30 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:38 GMT
recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/denied-push-with-user.yml b/spec/vcr_cassettes/denied-push-with-user.yml
new file mode 100644
index 0000000..4694797
--- /dev/null
+++ b/spec/vcr_cassettes/denied-push-with-user.yml
@@ -0,0 +1,48 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://dev.gitlab.org/api/v3/internal/allowed
+ body:
+ encoding: US-ASCII
+ string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&user_id=1&secret_token=a123
+ headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ Accept:
+ - "*/*"
+ User-Agent:
+ - Ruby
+ Content-Type:
+ - application/x-www-form-urlencoded
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Server:
+ - nginx/1.1.19
+ Date:
+ - Wed, 03 Sep 2014 11:27:39 GMT
+ Content-Type:
+ - application/json
+ Content-Length:
+ - '4'
+ Connection:
+ - keep-alive
+ Status:
+ - 200 OK
+ Etag:
+ - '"b326b5062b2f0e69046810717534cb09"'
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 8bd01f76-6029-4921-ba97-12323a7d5750
+ X-Runtime:
+ - '0.019640'
+ body:
+ encoding: UTF-8
+ string: 'false'
+ http_version:
+ recorded_at: Wed, 03 Sep 2014 11:27:39 GMT
+recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/denied-push.yml b/spec/vcr_cassettes/denied-push.yml
index 8116aa1..fc0a309 100644
--- a/spec/vcr_cassettes/denied-push.yml
+++ b/spec/vcr_cassettes/denied-push.yml
@@ -1,16 +1,20 @@
---
http_interactions:
- request:
- method: get
- uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-upload-pack&key_id=2&project=gitlab/gitlabhq&ref=master
+ method: post
+ uri: https://dev.gitlab.org/api/v3/internal/allowed
body:
encoding: US-ASCII
- string: ''
+ string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&project=gitlab%2Fgitlabhq&key_id=2&secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
+ Content-Type:
+ - application/x-www-form-urlencoded
response:
status:
code: 404
@@ -19,7 +23,7 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 12:21:31 GMT
+ - Wed, 03 Sep 2014 11:27:38 GMT
Content-Type:
- application/json
Content-Length:
@@ -28,19 +32,15 @@ http_interactions:
- keep-alive
Status:
- 404 Not Found
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Cache-Control:
- no-cache
X-Request-Id:
- - 6242870566154e17170c86ebb7f7a448
+ - 8b0210d2-4db5-4325-8120-740e22eaf7d5
X-Runtime:
- - '0.005174'
- X-Rack-Cache:
- - miss
+ - '0.015198'
body:
- encoding: US-ASCII
- string: ! '{"message":"404 Not found"}'
+ encoding: UTF-8
+ string: '{"message":"404 Not found"}'
http_version:
- recorded_at: Mon, 11 Mar 2013 12:21:31 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:38 GMT
recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/dev_gitlab_org.yml b/spec/vcr_cassettes/dev_gitlab_org.yml
deleted file mode 100644
index 02410e0..0000000
--- a/spec/vcr_cassettes/dev_gitlab_org.yml
+++ /dev/null
@@ -1,48 +0,0 @@
----
-http_interactions:
-- request:
- method: get
- uri: https://dev.gitlab.org/api/v3/internal/allowed?action=git-receive-pack&key_id=100&project=gitlab/gitlabhq&ref=master
- body:
- encoding: US-ASCII
- string: ''
- headers:
- Accept:
- - ! '*/*'
- User-Agent:
- - Ruby
- response:
- status:
- code: 200
- message: OK
- headers:
- Server:
- - nginx/1.1.19
- Date:
- - Mon, 11 Mar 2013 12:13:05 GMT
- Content-Type:
- - application/json
- Content-Length:
- - '4'
- Connection:
- - keep-alive
- Status:
- - 200 OK
- X-Ua-Compatible:
- - IE=Edge,chrome=1
- Etag:
- - ! '"b326b5062b2f0e69046810717534cb09"'
- Cache-Control:
- - max-age=0, private, must-revalidate
- X-Request-Id:
- - aabf04bba02ad88d256633dc34097095
- X-Runtime:
- - '0.017099'
- X-Rack-Cache:
- - miss
- body:
- encoding: US-ASCII
- string: 'true'
- http_version:
- recorded_at: Mon, 11 Mar 2013 12:13:05 GMT
-recorded_with: VCR 2.4.0
diff --git a/spec/vcr_cassettes/discover-ok.yml b/spec/vcr_cassettes/discover-ok.yml
index beec0c1..a86243c 100644
--- a/spec/vcr_cassettes/discover-ok.yml
+++ b/spec/vcr_cassettes/discover-ok.yml
@@ -2,13 +2,15 @@
http_interactions:
- request:
method: get
- uri: https://dev.gitlab.org/api/v3/internal/discover?key_id=1
+ uri: https://dev.gitlab.org/api/v3/internal/discover?key_id=126
body:
encoding: US-ASCII
- string: ''
+ string: secret_token=a123
headers:
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- - ! '*/*'
+ - "*/*"
User-Agent:
- Ruby
response:
@@ -19,30 +21,26 @@ http_interactions:
Server:
- nginx/1.1.19
Date:
- - Mon, 11 Mar 2013 12:25:45 GMT
+ - Wed, 03 Sep 2014 11:27:35 GMT
Content-Type:
- application/json
Content-Length:
- - '332'
+ - '56'
Connection:
- keep-alive
Status:
- 200 OK
- X-Ua-Compatible:
- - IE=Edge,chrome=1
Etag:
- - ! '"132e22d01addb4fe7fc1ee9f954b69e1"'
+ - '"1d75c1cf3d4bfa4d2b7bb6a0bcfd7f55"'
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- - 47a6a45ce3073d1f7497d15290c109ef
+ - ef4513ae-0424-4941-8be0-b5a3a7b4bf12
X-Runtime:
- - '0.007938'
- X-Rack-Cache:
- - miss
+ - '0.016934'
body:
- encoding: US-ASCII
- string: ! '{"name":"Dmitriy Zaporozhets"}'
+ encoding: UTF-8
+ string: '{"name":"Dmitriy Zaporozhets","username":"dzaporozhets"}'
http_version:
- recorded_at: Mon, 11 Mar 2013 12:25:44 GMT
+ recorded_at: Wed, 03 Sep 2014 11:27:35 GMT
recorded_with: VCR 2.4.0
diff --git a/support/rewrite-hooks.sh b/support/rewrite-hooks.sh
index 3c96b6f..585eaeb 100755
--- a/support/rewrite-hooks.sh
+++ b/support/rewrite-hooks.sh
@@ -1,28 +1,5 @@
#!/bin/bash
+# This script is deprecated. Use bin/create-hooks instead.
-# $1 is an optional argument specifying the location of the repositories directory.
-# Defaults to /home/git/repositories if not provided
-
-home_dir="/home/git"
-src=${1:-"$home_dir/repositories"}
-
-function create_link_in {
- ln -s -f "$home_dir/gitlab-shell/hooks/update" "$1/hooks/update"
-}
-
-for dir in `ls "$src/"`
-do
- if [ -d "$src/$dir" ]; then
- if [[ "$dir" =~ ^.*\.git$ ]]
- then
- create_link_in "$src/$dir"
- else
- for subdir in `ls "$src/$dir/"`
- do
- if [ -d "$src/$dir/$subdir" ] && [[ "$subdir" =~ ^.*\.git$ ]]; then
- create_link_in "$src/$dir/$subdir"
- fi
- done
- fi
- fi
-done
+gitlab_shell_dir="$(cd $(dirname $0) && pwd)/.."
+exec ${gitlab_shell_dir}/bin/create-hooks