Commit 89bd72d1 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '173-remove-unneeded-binaries' into 'master'

Remove dead Ruby code and unused binaries

See merge request gitlab-org/gitlab-shell!346
parents c88d80fe 8cf3f4c7
......@@ -4,7 +4,6 @@ tmp/*
*.log
/*.log*
authorized_keys.lock
coverage/
.gitlab_shell_secret
.bundle
tags
......@@ -15,7 +14,4 @@ hooks/*.d
/bin/gitlab-shell
/bin/gitlab-shell-authorized-keys-check
/bin/gitlab-shell-authorized-principals-check
/bin/gitaly-upload-pack
/bin/gitaly-receive-pack
/bin/gitaly-upload-archive
/bin/check
image: "ruby:2.3"
variables:
INSTALL_BUNDLER_VERSION: "~> 2.0.1"
before_script:
- export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
- gem install --force --bindir /usr/local/bin bundler -v "$INSTALL_BUNDLER_VERSION"
- cp config.yml.example config.yml
- bundle install
.rspec_definition: &rspec_definition
script:
# Skip the experimental Golang wrapper in the Ruby specs. These are now
# primarily regression tests for particular versions of Ruby.
#
# The full rspec suite is also run against each suppported golang version
- make test_ruby
rspec:
<<: *rspec_definition
except:
- tags
verify_ruby:
script:
- make verify_ruby
except:
- tags
#ruby 2.6
rspec:ruby2.6:
image: ruby:2.6
variables:
INSTALL_BUNDLER_VERSION: ~> 1.17.3
<<: *rspec_definition
except:
- tags
#ruby 2.2
rspec:ruby2.2:
image: ruby:2.2
variables:
INSTALL_BUNDLER_VERSION: ~> 1.17.3
<<: *rspec_definition
except:
- tags
#ruby 2.1
rspec:ruby2.1:
image: ruby:2.1
variables:
INSTALL_BUNDLER_VERSION: ~> 1.17.3
<<: *rspec_definition
except:
- tags
.go: &go_definition
.test: &test_definition
before_script:
# Set up the environment to run integration tests (still written in Ruby)
- apt-get update -qq && apt-get install -y ruby ruby-dev
- ruby -v
- export PATH=~/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
- gem update --system
- gem install --bindir /usr/local/bin bundler
- cp config.yml.example config.yml
- gem install --force --bindir /usr/local/bin bundler -v 1.17.2
- bundle install
script:
# Now set up to run the Golang tests
- make build
- cp config.yml.example config.yml
- go version
- which go
- make build verify_golang test_golang
# Run the full Ruby test suite in the "go" tests. As more functionality is
# migrated into these tests and out of Ruby, the amount of work here will
# reduce
- make test_ruby
script:
- make verify test
go:1.11:
<<: *go_definition
<<: *test_definition
image: golang:1.11
go:1.12:
<<: *go_definition
<<: *test_definition
image: golang:1.12
go:1.13:
<<: *go_definition
<<: *test_definition
image: golang:1.13
codequality:
......
source 'https://rubygems.org'
group :development, :test do
gem 'listen', '~> 0.5.0'
gem 'pry', '~> 0.12.2'
gem 'rspec', '~> 3.8.0'
gem 'rspec-parameterized', '~> 0.4.0'
gem 'rubocop', '0.49.1', require: false
gem 'simplecov', '~> 0.9.0', require: false
gem 'vcr', '~> 4.0.0'
gem 'webmock', '~> 3.4.0'
end
GEM
remote: https://rubygems.org/
specs:
abstract_type (0.0.7)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
ast (2.4.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
coderay (1.1.2)
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
crack (0.4.3)
safe_yaml (~> 1.0.0)
debug_inspector (0.0.3)
diff-lcs (1.3)
docile (1.1.5)
equalizer (0.0.11)
hashdiff (0.3.7)
ice_nine (0.11.2)
listen (0.5.3)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.2)
multi_json (1.13.1)
parallel (1.12.1)
parser (2.5.1.2)
ast (~> 2.4.0)
powerpack (0.1.2)
proc_to_ast (0.1.0)
coderay
parser
unparser
procto (0.0.3)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
public_suffix (3.0.3)
rainbow (2.2.2)
rake
rake (12.3.3)
......@@ -55,12 +22,6 @@ GEM
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-parameterized (0.4.0)
binding_of_caller
parser
proc_to_ast
rspec (>= 2.13, < 4)
unparser
rspec-support (3.8.0)
rubocop (0.49.1)
parallel (~> 1.10)
......@@ -70,40 +31,14 @@ GEM
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0)
safe_yaml (1.0.4)
simplecov (0.9.2)
docile (~> 1.1.0)
multi_json (~> 1.0)
simplecov-html (~> 0.9.0)
simplecov-html (0.9.0)
thread_safe (0.3.6)
unicode-display_width (1.4.0)
unparser (0.2.8)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
diff-lcs (~> 1.3)
equalizer (~> 0.0.9)
parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2)
vcr (4.0.0)
webmock (3.4.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
PLATFORMS
ruby
DEPENDENCIES
listen (~> 0.5.0)
pry (~> 0.12.2)
rspec (~> 3.8.0)
rspec-parameterized (~> 0.4.0)
rubocop (= 0.49.1)
simplecov (~> 0.9.0)
vcr (~> 4.0.0)
webmock (~> 3.4.0)
BUNDLED WITH
1.16.3
1.17.2
......@@ -14,15 +14,9 @@ verify_golang:
test: test_ruby test_golang
# The Ruby tests are now all integration specs that test the Go implementation.
test_ruby:
# bin/gitlab-shell, bin/gitlab-shell-authorized-keys-check and
# bin/gitlab-shell-authorized-principals-check must exist and need to be
# the Ruby version for rspec to be able to test.
cp bin/gitlab-shell-ruby bin/gitlab-shell
cp bin/gitlab-shell-authorized-keys-check-ruby bin/gitlab-shell-authorized-keys-check
cp bin/gitlab-shell-authorized-principals-check-ruby bin/gitlab-shell-authorized-principals-check
bundle exec rspec --color --tag '~go' --format d spec
rm -f bin/gitlab-shell bin/gitlab-shell-authorized-keys-check bin/gitlab-shell-authorized-principals-check
bundle exec rspec --color --format d spec
test_golang:
support/go-test
......
......@@ -45,26 +45,10 @@ plan](https://gitlab.com/gitlab-org/gitaly/issues/1226#note_126519133).
## Requirements
**GitLab shell will always use your system ruby (normally located at /usr/bin/ruby) and will not use the ruby your installed with a ruby version manager (such as RVM).**
It requires ruby 2.0 or higher.
Please uninstall any old ruby versions from your system:
GitLab Shell is written in Go, and needs a Go compiler to build. It still requires
Ruby to build and test, but not to run.
```
sudo apt-get remove ruby1.8
```
Download Ruby and compile it with:
```
mkdir /tmp/ruby && cd /tmp/ruby
curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz
cd ruby-2.1.5
./configure --disable-install-rdoc
make
sudo make install
```
To install gitlab-shell you also need a Go compiler version 1.8 or newer. https://golang.org/dl/
Download and install the current version of Go from https://golang.org/dl/
## Setup
......@@ -78,59 +62,25 @@ Checks if GitLab API access and redis via internal API can be reached:
## Testing
Run Ruby and Golang tests:
Run tests:
bundle install
make test
Run Rubocop and gofmt:
Run gofmt and rubocop:
bundle install
make verify
Run both test and verify (the default Makefile target):
bundle install
make validate
## Git LFS remark
Starting with GitLab 8.12, GitLab supports Git LFS authentication through SSH.
## Migration to Go feature flags
We are starting to migrate some features from Ruby to Go. To be able to do this
incrementally, we hide the Go implementation behind a feature flag.
To enable a feature, modify `migration` option in `config.yml` and ensure `enabled`
is set to `true` and feature to be enabled is added to `features`.
It should look something like this:
```yaml
migration:
enabled: true
features: ['discover']
```
Here are the following features that can be enabled:
- `discover`
- `2fa_recovery_codes`
### Configuring using Omnibus
If you're using Omnibus, these features can be enabled by adding something like this to `gitlab.rb`:
```ruby
gitlab_shell['migration'] = { enabled: true, features: ['discover', '2fa_recovery_codes'] }
```
This is equivalent to having this in `config.yml`:
```yaml
migration:
enabled: true
features: ['discover', '2fa_recovery_codes']
```
## Releasing a new version
GitLab Shell is versioned by git tags, and the version used by the Rails
......@@ -152,18 +102,6 @@ Rails application:
version**. (Note: this can be done as a separate MR to that, or in and MR
that will make use of the latest GitLab Shell changes.)
## Updating VCR fixtures
In order to generate new VCR fixtures you need to have a local GitLab instance
running and have next data:
1. gitlab-org/gitlab-test project.
2. SSH key with access to the project and ID 1 that belongs to Administrator.
3. SSH key without access to the project and ID 2.
You also need to modify `secret` variable at `spec/gitlab_net_spec.rb` so tests
can connect to your local instance.
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).
......
#!/usr/bin/env ruby
#
# GitLab shell authorized_keys helper. Query GitLab API to get the authorized
# command for a given ssh key fingerprint
#
# Ex.
# bin/gitlab-shell-authorized-keys-check <username> <public-key>
#
# Returns
# command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA...
#
# Expects to be called by the SSH daemon, via configuration like:
# AuthorizedKeysCommandUser git
# AuthorizedKeysCommand /bin/gitlab-shell-authorized-keys-check git %u %k
abort "# Wrong number of arguments. #{ARGV.size}. Usage:
# gitlab-shell-authorized-keys-check <expected-username> <actual-username> <key>" unless ARGV.size == 3
expected_username = ARGV[0]
abort '# No username provided' if expected_username.nil? || expected_username == ''
actual_username = ARGV[1]
abort '# No username provided' if actual_username.nil? || actual_username == ''
# Only check access if the requested username matches the configured username.
# Normally, these would both be 'git', but it can be configured by the user
exit 0 unless expected_username == actual_username
key = ARGV[2]
abort "# No key provided" if key.nil? || key == ''
require_relative '../lib/gitlab_init'
require_relative '../lib/gitlab_net'
require_relative '../lib/gitlab_keys'
authorized_key = GitlabNet.new.authorized_key(key)
if authorized_key.nil?
puts "# No key was found for #{key}"
else
puts GitlabKeys.key_line("key-#{authorized_key['id']}", authorized_key['key'])
end
#!/usr/bin/env ruby
#
# GitLab shell authorized principals helper. Emits the same sort of
# command="..." line as gitlab-shell-authorized-principals-check, with
# the right options.
#
# Ex.
# bin/gitlab-shell-authorized-keys-check <key-id> <principal1> [<principal2>...]
#
# Returns one line per principal passed in, e.g.:
# command="/bin/gitlab-shell username-{KEY_ID}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty {PRINCIPAL}
# [command="/bin/gitlab-shell username-{KEY_ID}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty {PRINCIPAL2}]
#
# Expects to be called by the SSH daemon, via configuration like:
# AuthorizedPrincipalsCommandUser root
# AuthorizedPrincipalsCommand /bin/gitlab-shell-authorized-principals-check git %i sshUsers
abort "# Wrong number of arguments. #{ARGV.size}. Usage:
# gitlab-shell-authorized-principals-check <key-id> <principal1> [<principal2>...]" unless ARGV.size >= 2
key_id = ARGV[0]
abort '# No key_id provided' if key_id.nil? || key_id == ''
principals = ARGV[1..-1]
principals.each { |principal|
abort '# An invalid principal was provided' if principal.nil? || principal == ''
}
require_relative '../lib/gitlab_init'
require_relative '../lib/gitlab_net'
require_relative '../lib/gitlab_keys'
principals.each { |principal|
puts GitlabKeys.principal_line("username-#{key_id}", principal.dup)
}
#!/usr/bin/env ruby
unless ENV['SSH_CONNECTION']
puts "Only ssh allowed"
exit
end
original_cmd = ENV.delete('SSH_ORIGINAL_COMMAND')
require_relative '../lib/gitlab_init'
#
#
# GitLab shell, invoked from ~/.ssh/authorized_keys or from an
# AuthorizedPrincipalsCommand in the key-less SSH CERT mode.
#
#
require File.join(ROOT_PATH, 'lib', 'gitlab_shell')
# We must match e.g. "key-12345" anywhere on the command-line. See
# https://gitlab.com/gitlab-org/gitlab-shell/issues/145
who = /\b(?:(?:key)-[0-9]+|username-\S+)\b/.match(ARGV.join(' ')).to_s
if GitlabShell.new(who).exec(original_cmd)
exit 0
else
exit 1
end
#!/usr/bin/env ruby
# The purpose of this executable is to test that gitlab-shell logging
# works correctly in combination with Kernel.exec.
require_relative '../lib/gitlab_init'
require_relative '../lib/gitlab_logger'
$logger.info(ARGV.first)
Kernel.exec('true')
package main
import (
"context"
"encoding/json"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/handler"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/logger"
"google.golang.org/grpc"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func init() {
logger.ProgName = "gitaly-receive-pack"
}
func main() {
handler.RunGitalyCommand(func(ctx context.Context, conn *grpc.ClientConn, requestJSON string) (int32, error) {
request, err := deserialize(requestJSON)
if err != nil {
return 1, err
}
return handler.ReceivePack(ctx, conn, request)
})
}
func deserialize(requestJSON string) (*pb.SSHReceivePackRequest, error) {
var request pb.SSHReceivePackRequest
if err := json.Unmarshal([]byte(requestJSON), &request); err != nil {
return nil, err
}
return &request, nil
}
package main
import (
"testing"
"github.com/stretchr/testify/require"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func Test_deserialize(t *testing.T) {
tests := []struct {
name string
requestJSON string
want *pb.SSHReceivePackRequest
wantErr bool
}{
{
name: "empty",
requestJSON: "",
want: nil,
wantErr: true,
},
{
name: "empty_hash",
requestJSON: "{}",
want: &pb.SSHReceivePackRequest{},
wantErr: false,
},
{
name: "nil",
requestJSON: "null",
want: &pb.SSHReceivePackRequest{},
wantErr: false,
},
{
name: "values",
requestJSON: `{"gl_id": "1234"}`,
want: &pb.SSHReceivePackRequest{GlId: "1234"},
wantErr: false,
},
{
name: "invalid_json",
requestJSON: `{"gl_id": "1234`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := deserialize(tt.requestJSON)
require.EqualValues(t, got, tt.want, "Got %+v, wanted %+v", got, tt.want)
if tt.wantErr {
require.Error(t, err, "Wanted an error, got %+v", err)
} else {
require.NoError(t, err, "Wanted no error, got %+v", err)
}
})
}
}
package main
import (
"context"
"encoding/json"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/handler"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/logger"
"google.golang.org/grpc"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func init() {
logger.ProgName = "gitaly-upload-archive"
}
func main() {
handler.RunGitalyCommand(func(ctx context.Context, conn *grpc.ClientConn, requestJSON string) (int32, error) {
request, err := deserialize(requestJSON)
if err != nil {
return 1, err
}
return handler.UploadArchive(ctx, conn, request)
})
}
func deserialize(argumentJSON string) (*pb.SSHUploadArchiveRequest, error) {
var request pb.SSHUploadArchiveRequest
if err := json.Unmarshal([]byte(argumentJSON), &request); err != nil {
return nil, err
}
return &request, nil
}
package main
import (
"testing"
"github.com/stretchr/testify/require"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func Test_deserialize(t *testing.T) {
tests := []struct {
name string
requestJSON string
want *pb.SSHUploadArchiveRequest
wantErr bool
}{
{
name: "empty",
requestJSON: "",
want: nil,
wantErr: true,
},
{
name: "empty_hash",
requestJSON: "{}",
want: &pb.SSHUploadArchiveRequest{},
wantErr: false,
},
{
name: "nil",
requestJSON: "null",
want: &pb.SSHUploadArchiveRequest{},
wantErr: false,
},
{
name: "values",
requestJSON: `{"repository": { "storage_name": "12345"} }`,
want: &pb.SSHUploadArchiveRequest{Repository: &pb.Repository{StorageName: "12345"}},
wantErr: false,
},
{
name: "invalid_json",
requestJSON: `{"gl_id": "1234`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := deserialize(tt.requestJSON)
require.EqualValues(t, got, tt.want, "Got %+v, wanted %+v", got, tt.want)
if tt.wantErr {
require.Error(t, err, "Wanted an error, got %+v", err)
} else {
require.NoError(t, err, "Wanted no error, got %+v", err)
}
})
}
}
package main
import (
"context"
"encoding/json"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/handler"
"gitlab.com/gitlab-org/gitlab-shell/go/internal/logger"
"google.golang.org/grpc"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func init() {
logger.ProgName = "gitaly-upload-pack"
}
func main() {
handler.RunGitalyCommand(func(ctx context.Context, conn *grpc.ClientConn, requestJSON string) (int32, error) {
request, err := deserialize(requestJSON)
if err != nil {
return 1, err
}
return handler.UploadPack(ctx, conn, request)
})
}
func deserialize(requestJSON string) (*pb.SSHUploadPackRequest, error) {
var request pb.SSHUploadPackRequest
if err := json.Unmarshal([]byte(requestJSON), &request); err != nil {
return nil, err
}
return &request, nil
}
package main
import (
"testing"
"github.com/stretchr/testify/require"
pb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)
func Test_deserialize(t *testing.T) {
tests := []struct {
name string
requestJSON string
want *pb.SSHUploadPackRequest
wantErr bool
}{
{
name: "empty",
requestJSON: "",
want: nil,
wantErr: true,
},
{
name: "empty_hash",
requestJSON: "{}",
want: &pb.SSHUploadPackRequest{},
wantErr: false,
},
{
name: "nil",
requestJSON: "null",
want: &pb.SSHUploadPackRequest{},
wantErr: false,
},
{
name: "values",
requestJSON: `{"repository": { "storage_name": "12345"} }`,
want: &pb.SSHUploadPackRequest{Repository: &pb.Repository{StorageName: "12345"}},
wantErr: false,
},
{
name: "invalid_json",
requestJSON: `{"gl_id": "1234`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := deserialize(tt.requestJSON)
require.EqualValues(t, got, tt.want, "Got %+v, wanted %+v", got, tt.want)
if tt.wantErr {
require.Error(t, err, "Wanted an error, got %+v", err)
} else {
require.NoError(t, err, "Wanted no error, got %+v", err)
}
})
}
}
require_relative 'action/custom'
module Action
end
require 'base64'
require_relative '../http_helper'
require_relative '../console_helper'
module Action
class Custom
include HTTPHelper
include ConsoleHelper
class BaseError < StandardError; end
class MissingPayloadError < BaseError; end
class MissingAPIEndpointsError < BaseError; end
class MissingDataError < BaseError; end
class UnsuccessfulError < BaseError; end
NO_MESSAGE_TEXT = 'No message'.freeze
DEFAULT_HEADERS = { 'Content-Type' => CONTENT_TYPE_JSON }.freeze
def initialize(gl_id, payload)
@gl_id = gl_id
@payload = payload
end
def execute
validate!
inform_client(info_message) if info_message
process_api_endpoints!
end
private
attr_reader :gl_id, :payload
def process_api_endpoints!
output = ''
resp = nil
data_with_gl_id = data.merge('gl_id' => gl_id)
api_endpoints.each do |endpoint|
url = "#{base_url}#{endpoint}"
json = { 'data' => data_with_gl_id, 'output' => output }
resp = post(url, {}, headers: DEFAULT_HEADERS, options: { json: json })
# Net::HTTPSuccess is the parent of Net::HTTPOK, Net::HTTPCreated etc.
case resp
when Net::HTTPSuccess, Net::HTTPMultipleChoices
true
else
raise_unsuccessful!(resp)
end
begin
body = JSON.parse(resp.body)
rescue JSON::ParserError
raise UnsuccessfulError, 'Response was not valid JSON'
end
print_flush(body['result'])
# In the context of the git push sequence of events, it's necessary to read
# stdin in order to capture output to pass onto subsequent commands
output = read_stdin
end
resp
end
def base_url
config.gitlab_url
end
def data
@data ||= payload['data']
end
def api_endpoints
data['api_endpoints']
end
def info_message
data['info_message']
end
def config
@config ||= GitlabConfig.new
end
def api
@api ||= GitlabNet.new
end
def read_stdin
Base64.encode64($stdin.read)
end
def print_flush(str)
return false unless str
$stdout.print(Base64.decode64(str))
$stdout.flush
end
def inform_client(str)
$stderr.puts(format_gitlab_output(str))
end
def format_gitlab_output(str)
format_for_stderr(str.split("\n")).join("\n")
end
def validate!
validate_payload!
validate_data!
validate_api_endpoints!
end
def validate_payload!
raise MissingPayloadError if !payload.is_a?(Hash) || payload.empty?
end
def validate_data!
raise MissingDataError unless data.is_a?(Hash)
end
def validate_api_endpoints!
raise MissingAPIEndpointsError if !api_endpoints.is_a?(Array) ||
api_endpoints.empty?
end
def raise_unsuccessful!(result)
message = "#{exception_message_for(result.body)} (#{result.code})"
raise UnsuccessfulError, format_gitlab_output(message)
end
def exception_message_for(body)
body = JSON.parse(body)
return body['message'] unless body['message'].to_s.empty?
body['result'].to_s.empty? ? NO_MESSAGE_TEXT : Base64.decode64(body['result'])
rescue JSON::ParserError
NO_MESSAGE_TEXT
end
end
end
# frozen_string_literal: true
module ConsoleHelper
LINE_PREFACE = '> GitLab:'
def write_stderr(messages)
format_for_stderr(messages).each do |message|
$stderr.puts(message)
end
end
def format_for_stderr(messages)
Array(messages).each_with_object([]) do |message, all|
all << "#{LINE_PREFACE} #{message}" unless message.empty?
end
end
end
require 'json'
class GitAccessStatus
HTTP_MULTIPLE_CHOICES = '300'.freeze
attr_reader :message, :gl_repository, :gl_project_path, :gl_id, :gl_username,
:gitaly, :git_protocol, :git_config_options, :payload,
:gl_console_messages
def initialize(status, status_code, message, gl_repository: nil,
gl_project_path: nil, gl_id: nil,
gl_username: nil, gitaly: nil, git_protocol: nil,
git_config_options: nil, payload: nil, gl_console_messages: [])
@status = status
@status_code = status_code
@message = message
@gl_repository = gl_repository
@gl_project_path = gl_project_path
@gl_id = gl_id
@gl_username = gl_username
@git_config_options = git_config_options
@gitaly = gitaly
@git_protocol = git_protocol
@payload = payload
@gl_console_messages = gl_console_messages
end
def self.create_from_json(json, status_code)
values = JSON.parse(json)
new(values["status"],
status_code,
values["message"],
gl_repository: values["gl_repository"],
gl_project_path: values["gl_project_path"],
gl_id: values["gl_id"],
gl_username: values["gl_username"],
git_config_options: values["git_config_options"],
gitaly: values["gitaly"],
git_protocol: values["git_protocol"],
payload: values["payload"],
gl_console_messages: values["gl_console_messages"])
end
def allowed?
@status
end
def custom_action?
@status_code == HTTP_MULTIPLE_CHOICES
end
end
require 'yaml'
class GitlabConfig
attr_reader :config
def initialize
@config = YAML.load_file(File.join(ROOT_PATH, 'config.yml'))
end
def home
ENV['HOME']
end
def auth_file
@config['auth_file'] ||= File.join(home, ".ssh/authorized_keys")
end
def secret_file
@config['secret_file'] ||= File.join(ROOT_PATH, '.gitlab_shell_secret')
end
# Pass a default value because this is called from a repo's context; in which
# case, the repo's hooks directory should be the default.
#
def custom_hooks_dir(default: nil)
@config['custom_hooks_dir'] || default
end
def gitlab_url
(@config['gitlab_url'] ||= "http://localhost:8080").sub(%r{/*$}, '')
end
def http_settings
@config['http_settings'] ||= {}
end
def log_file
@config['log_file'] ||= File.join(ROOT_PATH, 'gitlab-shell.log')
end
def log_level
@config['log_level'] ||= 'INFO'
end
def log_format
@config['log_format'] ||= 'text'
end
def audit_usernames
@config['audit_usernames'] ||= false
end
def metrics_log_file
@config['metrics_log_file'] ||= File.join(ROOT_PATH, 'gitlab-shell-metrics.log')
end
end
ROOT_PATH = ENV.fetch('GITLAB_SHELL_DIR', File.expand_path('..', __dir__))
# We are transitioning parts of gitlab-shell into the gitaly project. In
# gitaly, GITALY_EMBEDDED will be true.
GITALY_EMBEDDED = false
require_relative 'gitlab_config'
module GitlabKeys
class KeyError < StandardError; end
def self.command(whatever)
"#{ROOT_PATH}/bin/gitlab-shell #{whatever}"
end
def self.command_key(key_id)
unless /\A[a-z0-9-]+\z/ =~ key_id
raise KeyError, "Invalid key_id: #{key_id.inspect}"
end
command(key_id)
end
def self.whatever_line(command, trailer)
"command=\"#{command}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{trailer}"
end
def self.key_line(key_id, public_key)
public_key.chomp!
if public_key.include?("\n")
raise KeyError, "Invalid public_key: #{public_key.inspect}"
end
whatever_line(command_key(key_id), public_key)
end
def self.principal_line(username_key_id, principal)
principal.chomp!
if principal.include?("\n")
raise KeyError, "Invalid principal: #{principal.inspect}"
end
whatever_line(command_key(username_key_id), principal)
end
end
require 'base64'
require 'json'
class GitlabLfsAuthentication
# TODO: These don't need to be public
attr_accessor :username, :lfs_token, :repository_http_path
def initialize(username, lfs_token, repository_http_path, expires_in = nil)
@username = username
@lfs_token = lfs_token
@repository_http_path = repository_http_path
@expires_in = expires_in
end
def self.build_from_json(json)
values = JSON.parse(json)
new(values['username'],
values['lfs_token'],
values['repository_http_path'],
values['expires_in'])
rescue
nil
end
# Source: https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md#ssh
#
def authentication_payload
payload = { header: { Authorization: authorization }, href: href }
payload[:expires_in] = @expires_in if @expires_in
JSON.generate(payload)
end
private
def authorization
"Basic #{Base64.strict_encode64("#{username}:#{lfs_token}")}"
end
def href
"#{repository_http_path}/info/lfs"
end
end
require 'json'
require 'logger'
require 'time'
require_relative 'gitlab_config'
def convert_log_level(log_level)
Logger.const_get(log_level.upcase)
rescue NameError
$stderr.puts "WARNING: Unrecognized log level #{log_level.inspect}."
$stderr.puts "WARNING: Falling back to INFO."
Logger::INFO
end
class GitlabLogger
# Emulate the quoting logic of logrus
# https://github.com/sirupsen/logrus/blob/v1.0.5/text_formatter.go#L143-L156
SHOULD_QUOTE = /[^a-zA-Z0-9\-._\/@^+]/
LEVELS = {
Logger::INFO => 'info'.freeze,
Logger::DEBUG => 'debug'.freeze,
Logger::WARN => 'warn'.freeze,
Logger::ERROR => 'error'.freeze
}.freeze
def initialize(level, path, log_format)
@level = level
@log_file = File.open(path, 'ab')
# By default Ruby will buffer writes. This is a problem when we exec
# into a new command before Ruby flushed its buffers. Setting 'sync' to
# true disables Ruby's buffering.
@log_file.sync = true
@log_format = log_format
end
def info(message, data = {})
log_at(Logger::INFO, message, data)
end
def debug(message, data = {})
log_at(Logger::DEBUG, message, data)
end
def warn(message, data = {})
log_at(Logger::WARN, message, data)
end
def error(message, data = {})
log_at(Logger::ERROR, message, data)
end
private
attr_reader :log_file, :log_format
def log_at(level, message, data)
return unless @level <= level
data[:pid] = pid
data[:level] = LEVELS[level]
data[:msg] = message
# Use RFC3339 to match logrus in the Go parts of gitlab-shell
data[:time] = time_now.to_datetime.rfc3339
case log_format
when 'json'
# Don't use IO#puts because of https://bugs.ruby-lang.org/issues/14042
log_file.print("#{format_json(data)}\n")
else
log_file.print("#{format_text(data)}\n")
end
end
def pid
Process.pid
end
def time_now
Time.now
end
def format_text(data)
# We start the line with these fields to match the behavior of logrus
result = [
format_key_value(:time, data.delete(:time)),
format_key_value(:level, data.delete(:level)),
format_key_value(:msg, data.delete(:msg))
]
data.sort.each { |k, v| result << format_key_value(k, v) }
result.join(' ')
end
def format_key_value(key, value)
value_string = value.to_s
value_string = value_string.inspect if SHOULD_QUOTE =~ value_string
"#{key}=#{value_string}"
end
def format_json(data)
data.each do |key, value|
next unless value.is_a?(String)
value = value.dup.force_encoding('utf-8')
value = value.inspect unless value.valid_encoding?
data[key] = value.freeze
end
data.to_json
end
end
config = GitlabConfig.new
$logger = GitlabLogger.new(convert_log_level(config.log_level), config.log_file, config.log_format)
require_relative 'gitlab_config'
require_relative 'gitlab_logger'
module GitlabMetrics
module System
# THREAD_CPUTIME is not supported on OS X
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process.
clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end
else
def self.cpu_time
Process.
clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end
end
# Returns the current monotonic clock time in a given precision.
#
# Returns the time as a Fixnum.
def self.monotonic_time
if defined?(Process::CLOCK_MONOTONIC)
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
else
Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
end
end
end
def self.logger
$logger
end
# Measures the execution time of a block.
#
# Example:
#
# GitlabMetrics.measure(:find_by_username_duration) do
# User.find_by_username(some_username)
# end
#
# name - The name of the field to store the execution time in.
#
# Returns the value yielded by the supplied block.
def self.measure(name)
start_real = System.monotonic_time
start_cpu = System.cpu_time
retval = yield
real_time = System.monotonic_time - start_real
cpu_time = System.cpu_time - start_cpu
logger.debug('metrics', name: name, wall_time: real_time, cpu_time: cpu_time)
retval
end
end
require 'net/http'
require 'openssl'
require 'json'
require_relative 'gitlab_config'
require_relative 'gitlab_access_status'
require_relative 'gitlab_lfs_authentication'
require_relative 'http_helper'
class GitlabNet # rubocop:disable Metrics/ClassLength
include HTTPHelper
CHECK_TIMEOUT = 5
API_INACCESSIBLE_MESSAGE = 'API is not accessible'.freeze
def check_access(cmd, gl_repository, repo, who, changes, protocol, env: {})
changes = changes.join("\n") unless changes.is_a?(String)
params = {
action: cmd,
changes: changes,
gl_repository: gl_repository,
project: sanitize_path(repo),
protocol: protocol,
env: env
}
who_sym, _, who_v = self.class.parse_who(who)
params[who_sym] = who_v
url = "#{internal_api_endpoint}/allowed"
resp = post(url, params)
case resp
when Net::HTTPSuccess, Net::HTTPMultipleChoices, Net::HTTPUnauthorized,
Net::HTTPNotFound, Net::HTTPServiceUnavailable
if resp.content_type == CONTENT_TYPE_JSON
return GitAccessStatus.create_from_json(resp.body, resp.code)
end
end
GitAccessStatus.new(false, resp.code, API_INACCESSIBLE_MESSAGE)
end
def discover(who)
_, who_k, who_v = self.class.parse_who(who)
resp = get("#{internal_api_endpoint}/discover?#{who_k}=#{who_v}")
JSON.parse(resp.body) rescue nil
end
def lfs_authenticate(gl_id, repo, operation)
id_sym, _, id = self.class.parse_who(gl_id)
params = { project: sanitize_path(repo), operation: operation }
case id_sym
when :key_id
params[:key_id] = id
when :user_id
params[:user_id] = id
else
raise ArgumentError, "lfs_authenticate() got unsupported GL_ID='#{gl_id}'!"
end
resp = post("#{internal_api_endpoint}/lfs_authenticate", params)
GitlabLfsAuthentication.build_from_json(resp.body) if resp.code == '200'
end
def broadcast_message
resp = get("#{internal_api_endpoint}/broadcast_message")
JSON.parse(resp.body) rescue {}
end
def merge_request_urls(gl_repository, repo_path, changes)
changes = changes.join("\n") unless changes.is_a?(String)
changes = changes.encode('UTF-8', 'ASCII', invalid: :replace, replace: '')
url = "#{internal_api_endpoint}/merge_request_urls?project=#{URI.escape(repo_path)}&changes=#{URI.escape(changes)}"
url += "&gl_repository=#{URI.escape(gl_repository)}" if gl_repository
resp = get(url)
if resp.code == '200'
JSON.parse(resp.body)
else
[]
end
rescue
[]
end
def check
get("#{internal_api_endpoint}/check", options: { read_timeout: CHECK_TIMEOUT })
end
def authorized_key(key)
resp = get("#{internal_api_endpoint}/authorized_keys?key=#{URI.escape(key, '+/=')}")
JSON.parse(resp.body) if resp.code == "200"
rescue
nil
end
def two_factor_recovery_codes(gl_id)
id_sym, _, id = self.class.parse_who(gl_id)
resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", id_sym => id)
JSON.parse(resp.body) if resp.code == '200'
rescue
{}
end
def notify_post_receive(gl_repository, repo_path)
params = { gl_repository: gl_repository, project: repo_path }
resp = post("#{internal_api_endpoint}/notify_post_receive", params)
resp.code == '200'
rescue
false
end
def post_receive(gl_repository, identifier, changes, push_options)
params = {
gl_repository: gl_repository,
identifier: identifier,
changes: changes,
:"push_options[]" => push_options, # rubocop:disable Style/HashSyntax
}
resp = post("#{internal_api_endpoint}/post_receive", params)
raise NotFound if resp.code == '404'
JSON.parse(resp.body) if resp.code == '200'
end
def pre_receive(gl_repository)
resp = post("#{internal_api_endpoint}/pre_receive", gl_repository: gl_repository)
raise NotFound if resp.code == '404'
JSON.parse(resp.body) if resp.code == '200'
end
def self.parse_who(who)
if who.start_with?("key-")
value = who.gsub("key-", "")
raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
[:key_id, 'key_id', value]
elsif who.start_with?("user-")
value = who.gsub("user-", "")
raise ArgumentError, "who='#{who}' is invalid!" unless value =~ /\A[0-9]+\z/
[:user_id, 'user_id', value]
elsif who.start_with?("username-")
[:username, 'username', who.gsub("username-", "")]
else
raise ArgumentError, "who='#{who}' is invalid!"
end
end
protected
def sanitize_path(repo)
repo.delete("'")
end
end
class GitlabNet
class ApiUnreachableError < StandardError; end
class NotFound < StandardError; end
end
# frozen_string_literal: true
require 'shellwords'
require 'pathname'
require_relative 'gitlab_net'
require_relative 'gitlab_metrics'
require_relative 'action'
require_relative 'console_helper'
class GitlabShell # rubocop:disable Metrics/ClassLength
include ConsoleHelper
class AccessDeniedError < StandardError; end
class DisallowedCommandError < StandardError; end
class InvalidRepositoryPathError < StandardError; end
GIT_UPLOAD_PACK_COMMAND = 'git-upload-pack'
GIT_RECEIVE_PACK_COMMAND = 'git-receive-pack'
GIT_UPLOAD_ARCHIVE_COMMAND = 'git-upload-archive'
GIT_LFS_AUTHENTICATE_COMMAND = 'git-lfs-authenticate'
GITALY_COMMANDS = {
GIT_UPLOAD_PACK_COMMAND => File.join(ROOT_PATH, 'bin', 'gitaly-upload-pack'),
GIT_UPLOAD_ARCHIVE_COMMAND => File.join(ROOT_PATH, 'bin', 'gitaly-upload-archive'),
GIT_RECEIVE_PACK_COMMAND => File.join(ROOT_PATH, 'bin', 'gitaly-receive-pack')
}.freeze
GIT_COMMANDS = (GITALY_COMMANDS.keys + [GIT_LFS_AUTHENTICATE_COMMAND]).freeze
TWO_FACTOR_RECOVERY_COMMAND = '2fa_recovery_codes'
GL_PROTOCOL = 'ssh'
attr_accessor :gl_id, :gl_repository, :gl_project_path, :repo_name, :command, :git_access, :git_protocol
def initialize(who)
who_sym, = GitlabNet.parse_who(who)
if who_sym == :username
@who = who
else
@gl_id = who
end
@config = GitlabConfig.new
end
# The origin_cmd variable contains UNTRUSTED input. If the user ran
# ssh git@gitlab.example.com 'evil command', then origin_cmd contains
# 'evil command'.
def exec(origin_cmd)
unless origin_cmd
puts "Welcome to GitLab, #{username}!"
return true
end
args = Shellwords.shellwords(origin_cmd)
args = parse_cmd(args)
access_status = nil
if GIT_COMMANDS.include?(args.first)
access_status = GitlabMetrics.measure('verify-access') { verify_access }
@gl_repository = access_status.gl_repository
@git_protocol = ENV['GIT_PROTOCOL']
@gl_project_path = access_status.gl_project_path
@gitaly = access_status.gitaly
@username = access_status.gl_username
@git_config_options = access_status.git_config_options
@gl_id = access_status.gl_id if defined?(@who)
write_stderr(access_status.gl_console_messages)
elsif !defined?(@gl_id)
# We're processing an API command like 2fa_recovery_codes, but
# don't have a @gl_id yet, that means we're in the "username"
# mode and need to materialize it, calling the "user" method
# will do that and call the /discover method.
user
end
if @command == GIT_RECEIVE_PACK_COMMAND && access_status.custom_action?
# If the response from /api/v4/allowed is a HTTP 300, we need to perform
# a Custom Action and therefore should return and not call process_cmd()
#
return process_custom_action(access_status)
end
process_cmd(args)
true
rescue GitlabNet::ApiUnreachableError
write_stderr('Failed to authorize your Git request: internal API unreachable')
false
rescue AccessDeniedError => ex
$logger.warn('Access denied', command: origin_cmd, user: log_username)
write_stderr(ex.message)
false
rescue DisallowedCommandError
$logger.warn('Denied disallowed command', command: origin_cmd, user: log_username)
write_stderr('Disallowed command')
false
rescue InvalidRepositoryPathError
write_stderr('Invalid repository path')
false
rescue Action::Custom::BaseError => ex
$logger.warn('Custom action error', exception: ex.class, message: ex.message,
command: origin_cmd, user: log_username)
$stderr.puts ex.message
false
end
protected
def parse_cmd(args)
# Handle Git for Windows 2.14 using "git upload-pack" instead of git-upload-pack
if args.length == 3 && args.first == 'git'
@command = "git-#{args[1]}"
args = [@command, args.last]
else
@command = args.first
end
@git_access = @command
return args if TWO_FACTOR_RECOVERY_COMMAND == @command
raise DisallowedCommandError unless GIT_COMMANDS.include?(@command)
case @command
when GIT_LFS_AUTHENTICATE_COMMAND
raise DisallowedCommandError unless args.count >= 2
@repo_name = args[1]
case args[2]
when 'download'
@git_access = GIT_UPLOAD_PACK_COMMAND
when 'upload'
@git_access = GIT_RECEIVE_PACK_COMMAND
else
raise DisallowedCommandError
end
else
raise DisallowedCommandError unless args.count == 2
@repo_name = args.last
end
args
end
def verify_access
status = api.check_access(@git_access, nil, @repo_name, @who || @gl_id, '_any', GL_PROTOCOL)
raise AccessDeniedError, status.message unless status.allowed?
status
end
def process_custom_action(access_status)
Action::Custom.new(@gl_id, access_status.payload).execute
end
def process_cmd(args)
return api_2fa_recovery_codes if TWO_FACTOR_RECOVERY_COMMAND == @command
if @command == GIT_LFS_AUTHENTICATE_COMMAND
GitlabMetrics.measure('lfs-authenticate') do
operation = args[2]
$logger.info('Processing LFS authentication', operation: operation, user: log_username)
lfs_authenticate(operation)
end
return
end
# TODO: instead of building from pieces here in gitlab-shell, build the
# entire gitaly_request in gitlab-ce and pass on as-is here.
args = JSON.dump(
'repository' => @gitaly['repository'],
'gl_repository' => @gl_repository,
'gl_project_path' => @gl_project_path,
'gl_id' => @gl_id,
'gl_username' => @username,
'git_config_options' => @git_config_options,
'git_protocol' => @git_protocol
)
gitaly_address = @gitaly['address']
executable = GITALY_COMMANDS.fetch(@command)
gitaly_bin = File.basename(executable)
args_string = [gitaly_bin, gitaly_address, args].join(' ')
$logger.info('executing git command', command: args_string, user: log_username)
exec_cmd(executable, gitaly_address: gitaly_address, token: @gitaly['token'], json_args: args)
end
# This method is not covered by Rspec because it ends the current Ruby process.
def exec_cmd(executable, gitaly_address:, token:, json_args:)
env = { 'GITALY_TOKEN' => token }
args = [executable, gitaly_address, json_args]
# We use 'chdir: ROOT_PATH' to let the next executable know where config.yml is.
Kernel.exec(env, *args, unsetenv_others: true, chdir: ROOT_PATH)
end
def api
GitlabNet.new
end
def user
return @user if defined?(@user)
begin
if defined?(@who)
@user = api.discover(@who)
@gl_id = "user-#{@user['id']}" if @user && @user.key?('id')
else
@user = api.discover(@gl_id)
end
rescue GitlabNet::ApiUnreachableError
@user = nil
end
end
def username_from_discover
return nil unless user && user['username']
"@#{user['username']}"
end
def username
@username ||= username_from_discover || 'Anonymous'
end
# User identifier to be used in log messages.
def log_username
@config.audit_usernames ? username : "user with id #{@gl_id}"
end
def lfs_authenticate(operation)
lfs_access = api.lfs_authenticate(@gl_id, @repo_name, operation)
return unless lfs_access
puts lfs_access.authentication_payload
end
private
def continue?(question)
puts "#{question} (yes/no)"
STDOUT.flush # Make sure the question gets output before we wait for input
continue = STDIN.gets.chomp
puts '' # Add a buffer in the output
continue == 'yes'
end
def api_2fa_recovery_codes
continue = continue?(
"Are you sure you want to generate new two-factor recovery codes?\n" \
"Any existing recovery codes you saved will be invalidated."
)
unless continue
puts 'New recovery codes have *not* been generated. Existing codes will remain valid.'
return
end
resp = api.two_factor_recovery_codes(@gl_id)
if resp['success']
codes = resp['recovery_codes'].join("\n")
puts "Your two-factor authentication recovery codes are:\n\n" \
"#{codes}\n\n" \
"During sign in, use one of the codes above when prompted for\n" \
"your two-factor code. Then, visit your Profile Settings and add\n" \
"a new device so you do not lose access to your account again."
else
puts "An error occurred while trying to generate new recovery codes.\n" \
"#{resp['message']}"
end
end
end
module HooksUtils
module_function
# Gets an array of Git push options from the environment
def get_push_options
count = ENV['GIT_PUSH_OPTION_COUNT'].to_i
result = []
count.times do |i|
result.push(ENV["GIT_PUSH_OPTION_#{i}"])
end
result
end
end
require_relative 'httpunix'
require_relative 'gitlab_logger'
require_relative 'gitlab_net/errors'
module HTTPHelper
READ_TIMEOUT = 300
CONTENT_TYPE_JSON = 'application/json'.freeze
protected
def config
@config ||= GitlabConfig.new
end
def base_api_endpoint
"#{config.gitlab_url}/api/v4"
end
def internal_api_endpoint
"#{base_api_endpoint}/internal"
end
def http_client_for(uri, options = {})
http = if uri.is_a?(URI::HTTPUNIX)
Net::HTTPUNIX.new(uri.hostname)
else
Net::HTTP.new(uri.host, uri.port)
end
http.read_timeout = options[:read_timeout] || read_timeout
if uri.is_a?(URI::HTTPS)
http.use_ssl = true
http.cert_store = cert_store
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.http_settings['self_signed_cert']
end
http
end
def http_request_for(method, uri, params: {}, headers: {}, options: {})
request_klass = method == :get ? Net::HTTP::Get : Net::HTTP::Post
request = request_klass.new(uri.request_uri, headers)
user = config.http_settings['user']
password = config.http_settings['password']
request.basic_auth(user, password) if user && password
if options[:json]
request.body = options[:json].merge(secret_token: secret_token).to_json
else
request.set_form_data(params.merge(secret_token: secret_token))
end
if uri.is_a?(URI::HTTPUNIX)
# The HTTPUNIX HTTP client does not set a correct Host header. This can
# lead to 400 Bad Request responses.
request['Host'] = 'localhost'
end
request
end
def request(method, url, params: {}, headers: {}, options: {})
$logger.debug('Performing request', method: method.to_s.upcase, url: url)
uri = URI.parse(url)
http = http_client_for(uri, options)
request = http_request_for(method, uri,
params: params,
headers: headers,
options: options)
begin
start_time = Time.new
response = http.start { http.request(request) }
rescue => e
$logger.warn('Failed to connect', method: method.to_s.upcase, url: url, error: e)
raise GitlabNet::ApiUnreachableError
ensure
fields = { method: method.to_s.upcase, url: url, duration: Time.new - start_time, gitaly_embedded: GITALY_EMBEDDED }
$logger.info('finished HTTP request', fields)
end
case response
when Net::HTTPSuccess, Net::HTTPMultipleChoices
$logger.debug('Received response', code: response.code, body: response.body)
else
$logger.error('Call failed', method: method.to_s.upcase, url: url, code: response.code, body: response.body)
end
response
end
def get(url, headers: {}, options: {})
request(:get, url, headers: headers, options: options)
end
def post(url, params, headers: {}, options: {})
request(:post, url, params: params, headers: headers, options: options)
end
def cert_store
@cert_store ||= begin
store = OpenSSL::X509::Store.new
store.set_default_paths
ca_file = config.http_settings['ca_file']
store.add_file(ca_file) if ca_file
ca_path = config.http_settings['ca_path']
store.add_path(ca_path) if ca_path
store
end
end
def secret_token
@secret_token ||= File.read config.secret_file
end
def read_timeout
config.http_settings['read_timeout'] || READ_TIMEOUT
end
end
# support for http+unix://... connection scheme
#
# The URI scheme has the same structure as the similar one for python requests. See:
# http://fixall.online/theres-no-need-to-reinvent-the-wheelhttpsgithubcommsabramorequests-unixsocketurl/241810/
# https://github.com/msabramo/requests-unixsocket
require 'uri'
require 'net/http'
module URI
class HTTPUNIX < HTTP
def hostname
# decode %XX from path to file
v = host
URI.decode(v)
end
# port is not allowed in URI
DEFAULT_PORT = nil
def set_port(v)
return v unless v
raise InvalidURIError, "http+unix:// cannot contain port"
end
end
@@schemes['HTTP+UNIX'] = HTTPUNIX
end
# Based on:
# - http://stackoverflow.com/questions/15637226/ruby-1-9-3-simple-get-request-to-unicorn-through-socket
# - Net::HTTP::connect
module Net
class HTTPUNIX < HTTP
def initialize(socketpath, port = nil)
super(socketpath, port)
@port = nil # HTTP will set it to default - override back -> set DEFAULT_PORT
end
# override to prevent ":<port>" being appended to HTTP_HOST
def addr_port
address
end
def connect
D "opening connection to #{address} ..."
s = UNIXSocket.new(address)
D "opened"
@socket = BufferedIO.new(s)
@socket.read_timeout = @read_timeout
@socket.continue_timeout = @continue_timeout
@socket.debug_output = @debug_output
on_connect
end
end
end
require 'pathname'
class ObjectDirsHelper
class << self
def all_attributes
{
"GIT_ALTERNATE_OBJECT_DIRECTORIES" => absolute_alt_object_dirs,
"GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE" => relative_alt_object_dirs,
"GIT_OBJECT_DIRECTORY" => absolute_object_dir,
"GIT_OBJECT_DIRECTORY_RELATIVE" => relative_object_dir
}
end
def absolute_object_dir
ENV['GIT_OBJECT_DIRECTORY']
end
def relative_object_dir
relative_path(absolute_object_dir)
end
def absolute_alt_object_dirs
ENV['GIT_ALTERNATE_OBJECT_DIRECTORIES'].to_s.split(File::PATH_SEPARATOR)
end
def relative_alt_object_dirs
absolute_alt_object_dirs.map { |dir| relative_path(dir) }.compact
end
private
def relative_path(absolute_path)
return if absolute_path.nil?
repo_dir = Dir.pwd
Pathname.new(absolute_path).relative_path_from(Pathname.new(repo_dir)).to_s
end
end
end
require_relative '../spec_helper'
require_relative '../../lib/action/custom'
describe Action::Custom do
let(:repo_name) { 'gitlab-ci.git' }
let(:gl_id) { 'key-1' }
let(:secret) { "0a3938d9d95d807e94d937af3a4fbbea" }
let(:base_url) { 'http://localhost:3000' }
subject { described_class.new(gl_id, payload) }
describe '#execute' do
context 'with an empty payload' do
let(:payload) { {} }
it 'raises a MissingPayloadError exception' do
expect { subject.execute }.to raise_error(Action::Custom::MissingPayloadError)
end
end
context 'with api_endpoints defined' do
before do
allow(subject).to receive(:base_url).and_return(base_url)
allow(subject).to receive(:secret_token).and_return(secret)
allow($stdin).to receive(:read).and_return('')
end
context 'that are valid' do
let(:payload) do
{
'action' => 'geo_proxy_to_primary',
'data' => {
'api_endpoints' => %w{/api/v4/fake/info_refs /api/v4/fake/push},
'primary_repo' => 'http://localhost:3001/user1/repo1.git'
}
}
end
context 'and responds correctly' do
it 'prints a Base64 encoded result to $stdout' do
VCR.use_cassette("custom-action-ok") do
expect($stdout).to receive(:print).with('info_refs-result').ordered
expect($stdout).to receive(:print).with('push-result').ordered
subject.execute
end
end
context 'with results printed to $stdout' do
before do
allow($stdout).to receive(:print).with('info_refs-result')
allow($stdout).to receive(:print).with('push-result')
end
it 'returns an instance of Net::HTTPCreated' do
VCR.use_cassette("custom-action-ok") do
expect(subject.execute).to be_instance_of(Net::HTTPCreated)
end
end
context 'and with an information message provided' do
before do
payload['data']['info_message'] = 'Important message here.'
end
it 'prints said informational message to $stderr' do
VCR.use_cassette("custom-action-ok-with-message") do
expect { subject.execute }.to output(/Important message here./).to_stderr
end
end
end
end
end
context 'but responds incorrectly' do
it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-ok-not-json") do
expect do
subject.execute
end.to raise_error(Action::Custom::UnsuccessfulError, 'Response was not valid JSON')
end
end
end
end
context 'that are invalid' do
context 'where api_endpoints gl_id is missing' do
let(:payload) do
{
'action' => 'geo_proxy_to_primary',
'data' => {
'primary_repo' => 'http://localhost:3001/user1/repo1.git'
}
}
end
it 'raises a MissingAPIEndpointsError exception' do
expect { subject.execute }.to raise_error(Action::Custom::MissingAPIEndpointsError)
end
end
context 'where api_endpoints are empty' do
let(:payload) do
{
'action' => 'geo_proxy_to_primary',
'data' => {
'api_endpoints' => [],
'primary_repo' => 'http://localhost:3001/user1/repo1.git'
}
}
end
it 'raises a MissingAPIEndpointsError exception' do
expect { subject.execute }.to raise_error(Action::Custom::MissingAPIEndpointsError)
end
end
context 'where data gl_id is missing' do
let(:payload) { { 'api_endpoints' => %w{/api/v4/fake/info_refs /api/v4/fake/push} } }
it 'raises a MissingDataError exception' do
expect { subject.execute }.to raise_error(Action::Custom::MissingDataError)
end
end
context 'where API endpoints are bad' do
let(:payload) do
{
'action' => 'geo_proxy_to_primary',
'data' => {
'api_endpoints' => %w{/api/v4/fake/info_refs_bad /api/v4/fake/push_bad},
'primary_repo' => 'http://localhost:3001/user1/repo1.git'
}
}
end
context 'and response is JSON' do
it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-not-ok-json") do
expect do
subject.execute
end.to raise_error(Action::Custom::UnsuccessfulError, '> GitLab: You cannot perform write operations on a read-only instance (403)')
end
end
end
context 'and response is not JSON' do
it 'raises an UnsuccessfulError exception' do
VCR.use_cassette("custom-action-not-ok-not-json") do
expect do
subject.execute
end.to raise_error(Action::Custom::UnsuccessfulError, '> GitLab: No message (403)')
end
end
end
end
end
end
end
end
require_relative 'spec_helper'
require_relative '../lib/console_helper'
describe ConsoleHelper do
using RSpec::Parameterized::TableSyntax
class DummyClass
include ConsoleHelper
end
subject { DummyClass.new }
describe '#write_stderr' do
where(:messages, :stderr_output) do
'test' | "> GitLab: test\n"
%w{test1 test2} | "> GitLab: test1\n> GitLab: test2\n"
end
with_them do
it 'puts to $stderr, prefaced with > GitLab:' do
expect { subject.write_stderr(messages) }.to output(stderr_output).to_stderr
end
end
end
describe '#format_for_stderr' do
where(:messages, :result) do
'test' | ['> GitLab: test']
%w{test1 test2} | ['> GitLab: test1', '> GitLab: test2']
end
with_them do
it 'returns message(s), prefaced with > GitLab:' do
expect(subject.format_for_stderr(messages)).to eq(result)
end
end
end
end
require_relative 'spec_helper'
require_relative '../lib/gitlab_config'
describe GitlabConfig do
let(:config) { GitlabConfig.new }
let(:config_data) { {} }
before { expect(YAML).to receive(:load_file).and_return(config_data) }
describe '#gitlab_url' do
let(:url) { 'http://test.com' }
subject { config.gitlab_url }
before { config_data['gitlab_url'] = url }
it { is_expected.not_to be_empty }
it { is_expected.to eq(url) }
context 'remove trailing slashes' do
before { config_data['gitlab_url'] = url + '//' }
it { is_expected.to eq(url) }
end
end
describe '#audit_usernames' do
subject { config.audit_usernames }
it("returns false by default") { is_expected.to eq(false) }
end
describe '#log_format' do
subject { config.log_format }
it 'returns "text" by default' do
is_expected.to eq('text')
end
end
end
require_relative 'spec_helper'
require_relative '../lib/gitlab_keys'
require 'stringio'
describe GitlabKeys do
before do
$logger = double('logger').as_null_object
end
describe '.command' do
it 'the internal "command" utility function' do
command = "#{ROOT_PATH}/bin/gitlab-shell does-not-validate"
expect(described_class.command('does-not-validate')).to eq(command)
end
it 'does not raise a KeyError on invalid input' do
command = "#{ROOT_PATH}/bin/gitlab-shell foo\nbar\nbaz\n"
expect(described_class.command("foo\nbar\nbaz\n")).to eq(command)
end
end
describe '.command_key' do
it 'returns the "command" part of the key line' do
command = "#{ROOT_PATH}/bin/gitlab-shell key-123"
expect(described_class.command_key('key-123')).to eq(command)
end
it 'raises KeyError on invalid input' do
expect { described_class.command_key("\nssh-rsa AAA") }.to raise_error(described_class::KeyError)
end
end
describe '.key_line' do
let(:line) { %(command="#{ROOT_PATH}/bin/gitlab-shell key-741",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E) }
it 'returns the key line' do
expect(described_class.key_line('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to eq(line)
end
it 'silently removes a trailing newline' do
expect(described_class.key_line('key-741', "ssh-rsa AAAAB3NzaDAxx2E\n")).to eq(line)
end
it 'raises KeyError on invalid input' do
expect { described_class.key_line('key-741', "ssh-rsa AAA\nssh-rsa AAA") }.to raise_error(described_class::KeyError)
end
end
describe '.principal_line' do
let(:line) { %(command="#{ROOT_PATH}/bin/gitlab-shell username-someuser",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty sshUsers) }
it 'returns the key line' do
expect(described_class.principal_line('username-someuser', 'sshUsers')).to eq(line)
end
it 'silently removes a trailing newline' do
expect(described_class.principal_line('username-someuser', "sshUsers\n")).to eq(line)
end
it 'raises KeyError on invalid input' do
expect { described_class.principal_line('username-someuser', "sshUsers\nloginUsers") }.to raise_error(described_class::KeyError)
end
end
end
require 'spec_helper'
require 'gitlab_lfs_authentication'
require 'json'
describe GitlabLfsAuthentication do
let(:payload_from_gitlab_api) do
{
username: 'dzaporozhets',
lfs_token: 'wsnys8Zm8Jn7zyhHTAAK',
repository_http_path: 'http://gitlab.dev/repo'
}
end
subject do
GitlabLfsAuthentication.build_from_json(
JSON.generate(payload_from_gitlab_api)
)
end
describe '#build_from_json' do
it { expect(subject.username).to eq('dzaporozhets') }
it { expect(subject.lfs_token).to eq('wsnys8Zm8Jn7zyhHTAAK') }
it { expect(subject.repository_http_path).to eq('http://gitlab.dev/repo') }
end
describe '#authentication_payload' do
shared_examples 'a valid payload' do
it 'should be proper JSON' do
payload = subject.authentication_payload
json_payload = JSON.parse(payload)
expect(json_payload['header']['Authorization']).to eq('Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL')
expect(json_payload['href']).to eq('http://gitlab.dev/repo/info/lfs')
end
end
context 'without expires_in' do
let(:result) { { 'header' => { 'Authorization' => 'Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL' }, 'href' => 'http://gitlab.dev/repo/info/lfs' }.to_json }
it { expect(subject.authentication_payload).to eq(result) }
it_behaves_like 'a valid payload'
end
context 'with expires_in' do
let(:result) { { 'header' => { 'Authorization' => 'Basic ZHphcG9yb3poZXRzOndzbnlzOFptOEpuN3p5aEhUQUFL' }, 'href' => 'http://gitlab.dev/repo/info/lfs', 'expires_in' => 1800 }.to_json }
before do
payload_from_gitlab_api[:expires_in] = 1800
end
it { expect(subject.authentication_payload).to eq(result) }
it_behaves_like 'a valid payload'
end
end
end
require_relative 'spec_helper'
require_relative '../lib/gitlab_logger'
require 'securerandom'
describe :convert_log_level do
subject { convert_log_level :extreme }
it "converts invalid log level to Logger::INFO" do
expect($stderr).to receive(:puts).at_least(:once)
is_expected.to eq(Logger::INFO)
end
end
describe GitlabLogger do
subject { described_class.new(level, '/dev/null', format) }
let(:format) { 'text' }
let(:output) { StringIO.new }
let(:level) { Logger::INFO }
let(:time) { Time.at(123_456_789).utc } # '1973-11-29T21:33:09+00:00'
let(:pid) { 1234 }
before do
allow(subject).to receive(:log_file).and_return(output)
allow(subject).to receive(:time_now).and_return(time)
allow(subject).to receive(:pid).and_return(pid)
end
def first_line
output.string.lines.first.chomp
end
describe 'field sorting' do
it 'sorts fields, except time, level, msg' do
# Intentionally put 'foo' before 'baz' to see the effect of sorting
subject.info('hello world', foo: 'bar', baz: 'qux')
expect(first_line).to eq('time="1973-11-29T21:33:09+00:00" level=info msg="hello world" baz=qux foo=bar pid=1234')
end
end
describe '#error' do
context 'when the log level is too high' do
let(:level) { Logger::FATAL }
it 'does nothing' do
subject.info('hello world')
expect(output.string).to eq('')
end
end
it 'logs data' do
subject.error('hello world', foo: 'bar')
expect(first_line).to eq('time="1973-11-29T21:33:09+00:00" level=error msg="hello world" foo=bar pid=1234')
end
end
describe '#info' do
context 'when the log level is too high' do
let(:level) { Logger::ERROR }
it 'does nothing' do
subject.info('hello world')
expect(output.string).to eq('')
end
end
it 'logs data' do
subject.info('hello world', foo: 'bar')
expect(first_line).to eq('time="1973-11-29T21:33:09+00:00" level=info msg="hello world" foo=bar pid=1234')
end
end
describe '#warn' do
context 'when the log level is too high' do
let(:level) { Logger::ERROR }
it 'does nothing' do
subject.warn('hello world')
expect(output.string).to eq('')
end
end
it 'logs data' do
subject.warn('hello world', foo: 'bar')
expect(first_line).to eq('time="1973-11-29T21:33:09+00:00" level=warn msg="hello world" foo=bar pid=1234')
end
end
describe '#debug' do
it 'does nothing' do
subject.debug('hello world')
expect(output.string).to eq('')
end
context 'when the log level is low enough' do
let(:level) { Logger::DEBUG }
it 'logs data' do
subject.debug('hello world', foo: 'bar')
expect(first_line).to eq('time="1973-11-29T21:33:09+00:00" level=debug msg="hello world" foo=bar pid=1234')
end
end
end
describe 'json logging' do
let(:format) { 'json' }
it 'writes valid JSON data' do
subject.info('hello world', foo: 'bar')
expect(JSON.parse(first_line)).to eq(
'foo' => 'bar',
'level' => 'info',
'msg' => 'hello world',
'pid' => 1234,
'time' => '1973-11-29T21:33:09+00:00'
)
end
it 'handles non-UTF8 string values' do
subject.info("hello\x80world")
expect(JSON.parse(first_line)).to include('msg' => '"hello\x80world"')
end
end
describe 'log flushing' do
it 'logs get written even when calling Kernel.exec' do
msg = SecureRandom.hex(12)
test_logger_status = system('bin/test-logger', msg)
expect(test_logger_status).to eq(true)
grep_status = system('grep', '-q', '-e', msg, GitlabConfig.new.log_file)
expect(grep_status).to eq(true)
end
end
end
require_relative 'spec_helper'
require_relative '../lib/gitlab_metrics'
describe GitlabMetrics do
describe '.measure' do
before do
$logger = double('logger').as_null_object
end
it 'returns the return value of the block' do
val = described_class.measure('foo') { 10 }
expect(val).to eq(10)
end
it 'writes the metrics data to a log file' do
expect($logger).to receive(:debug).
with('metrics', a_metrics_log_message('foo'))
described_class.measure('foo') { 10 }
end
it 'calls proper measure methods' do
expect(described_class::System).to receive(:monotonic_time).twice.and_call_original
expect(described_class::System).to receive(:cpu_time).twice.and_call_original
described_class.measure('foo') { 10 }
end
end
end
RSpec::Matchers.define :a_metrics_log_message do |x|
match do |actual|
[
actual.fetch(:name) == x,
actual.fetch(:wall_time).is_a?(Numeric),
actual.fetch(:cpu_time).is_a?(Numeric),
].all?
end
end
This diff is collapsed.
......@@ -3,13 +3,17 @@ require_relative 'spec_helper'
describe 'bin/gitlab-shell-authorized-principals-check' do
include_context 'gitlab shell'
before(:all) do
write_config({})
end
def mock_server(server)
# Do nothing as we're not connecting to a server in this check.
end
let(:authorized_principals_check_path) { File.join(tmp_root_path, 'bin', 'gitlab-shell-authorized-principals-check') }
shared_examples 'authorized principals check' do
describe 'authorized principals check' do
it 'succeeds when a valid principal is given' do
output, status = run!
......@@ -39,35 +43,6 @@ describe 'bin/gitlab-shell-authorized-principals-check' do
end
end
describe 'without go features' do
before(:all) do
write_config({})
end
it_behaves_like 'authorized principals check'
end
describe 'without go features (via go)', :go do
before(:all) do
write_config({})
end
it_behaves_like 'authorized principals check'
end
describe 'with the go authorized-principals-check feature', :go do
before(:all) do
write_config(
'migration' => {
'enabled' => true,
'features' => ['authorized-principals-check']
}
)
end
it_behaves_like 'authorized principals check'
end
def run!(key_id: 'key', principals: ['principal'])
cmd = [
authorized_principals_check_path,
......
......@@ -7,6 +7,12 @@ require 'base64'
describe 'Custom bin/gitlab-shell git-receive-pack' do
include_context 'gitlab shell'
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => 'git-receive-pack group/repo' } }
before(:context) do
write_config("gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}")
end
def mock_server(server)
server.mount_proc('/geo/proxy_git_push_ssh/info_refs') do |req, res|
res.content_type = 'application/json'
......@@ -58,7 +64,9 @@ describe 'Custom bin/gitlab-shell git-receive-pack' do
end
end
shared_examples 'dialog for performing a custom action' do
describe 'dialog for performing a custom action' do
let(:inaccessible_error) { "Internal API error (403)\n" }
context 'when API calls perform successfully' do
def verify_successful_call!(cmd)
Open3.popen3(env, cmd) do |stdin, stdout, stderr|
......@@ -103,32 +111,4 @@ describe 'Custom bin/gitlab-shell git-receive-pack' do
end
end
end
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => 'git-receive-pack group/repo' } }
describe 'without go features' do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
)
end
it_behaves_like 'dialog for performing a custom action' do
let(:inaccessible_error) { "> GitLab: API is not accessible\n" }
end
end
describe 'with go features', :go do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
"migration" => { "enabled" => true,
"features" => ["git-receive-pack"] }
)
end
it_behaves_like 'dialog for performing a custom action' do
let(:inaccessible_error) { "Internal API error (403)\n" }
end
end
end
......@@ -5,6 +5,10 @@ require 'open3'
describe 'bin/gitlab-shell' do
include_context 'gitlab shell'
before(:context) do
write_config("gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}")
end
def mock_server(server)
server.mount_proc('/api/v4/internal/discover') do |req, res|
identifier = req.query['key_id'] || req.query['username'] || req.query['user_id']
......@@ -35,7 +39,7 @@ describe 'bin/gitlab-shell' do
Open3.capture3(env, cmd)
end
shared_examples 'results with keys' do
describe 'results with keys' do
# Basic valid input
it 'succeeds and prints username when a valid known key id is given' do
output, _, status = run!(["key-100"])
......@@ -102,26 +106,6 @@ describe 'bin/gitlab-shell' do
expect(output).to eq("Welcome to GitLab, @someuser!\n")
expect(status).to be_success
end
end
describe 'without go features' do
before(:context) do
write_config("gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}")
end
it_behaves_like 'results with keys'
end
describe 'with the go discover feature', :go do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
"migration" => { "enabled" => true,
"features" => ["discover"] }
)
end
it_behaves_like 'results with keys'
it 'outputs "Only SSH allowed"' do
_, stderr, status = run!(["-c/usr/share/webapps/gitlab-shell/bin/gitlab-shell", "username-someuser"], env: {})
......
......@@ -6,6 +6,11 @@ describe 'bin/gitlab-shell git-lfs-authentication' do
include_context 'gitlab shell'
let(:path) { "https://gitlab.com/repo/path" }
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => 'git-lfs-authenticate project/repo download' } }
before(:context) do
write_config("gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}")
end
def mock_server(server)
server.mount_proc('/api/v4/internal/lfs_authenticate') do |req, res|
......@@ -49,7 +54,7 @@ describe 'bin/gitlab-shell git-lfs-authentication' do
end
end
shared_examples 'lfs authentication command' do
describe 'lfs authentication command' do
def successful_response
{
"header" => {
......@@ -119,28 +124,4 @@ describe 'bin/gitlab-shell git-lfs-authentication' do
end
end
end
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => 'git-lfs-authenticate project/repo download' } }
describe 'without go features' do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
)
end
it_behaves_like 'lfs authentication command'
end
describe 'with go features' do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
"migration" => { "enabled" => true,
"features" => ["git-lfs-authenticate"] }
)
end
it_behaves_like 'lfs authentication command'
end
end
This diff is collapsed.
......@@ -5,6 +5,12 @@ require 'open3'
describe 'bin/gitlab-shell 2fa_recovery_codes' do
include_context 'gitlab shell'
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => '2fa_recovery_codes' } }
before(:context) do
write_config("gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}")
end
def mock_server(server)
server.mount_proc('/api/v4/internal/two_factor_recovery_codes') do |req, res|
res.content_type = 'application/json'
......@@ -31,7 +37,7 @@ describe 'bin/gitlab-shell 2fa_recovery_codes' do
end
end
shared_examples 'dialog for regenerating recovery keys' do
describe 'dialog for regenerating recovery keys' do
context 'when the user agrees to regenerate keys' do
def verify_successful_regeneration!(cmd)
Open3.popen2(env, cmd) do |stdin, stdout|
......@@ -101,28 +107,4 @@ describe 'bin/gitlab-shell 2fa_recovery_codes' do
end
end
end
let(:env) { {'SSH_CONNECTION' => 'fake', 'SSH_ORIGINAL_COMMAND' => '2fa_recovery_codes' } }
describe 'without go features' do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
)
end
it_behaves_like 'dialog for regenerating recovery keys'
end
describe 'with go features', :go do
before(:context) do
write_config(
"gitlab_url" => "http+unix://#{CGI.escape(tmp_socket_path)}",
"migration" => { "enabled" => true,
"features" => ["2fa_recovery_codes"] }
)
end
it_behaves_like 'dialog for regenerating recovery keys'
end
end
require_relative 'spec_helper'
require_relative '../lib/hooks_utils.rb'
describe :get_push_options do
context "when GIT_PUSH_OPTION_COUNT is not set" do
it { expect(HooksUtils.get_push_options).to eq([]) }
end
context "when one option is given" do
before do
ENV['GIT_PUSH_OPTION_COUNT'] = '1'
ENV['GIT_PUSH_OPTION_0'] = 'aaa'
end
it { expect(HooksUtils.get_push_options).to eq(['aaa']) }
end
context "when multiple options are given" do
before do
ENV['GIT_PUSH_OPTION_COUNT'] = '3'
ENV['GIT_PUSH_OPTION_0'] = 'aaa'
ENV['GIT_PUSH_OPTION_1'] = 'bbb'
ENV['GIT_PUSH_OPTION_2'] = 'ccc'
end
it { expect(HooksUtils.get_push_options).to eq(['aaa', 'bbb', 'ccc']) }
end
end
require_relative 'spec_helper'
require_relative '../lib/httpunix'
describe URI::HTTPUNIX do
describe :parse do
uri = URI::parse('http+unix://%2Fpath%2Fto%2Fsocket/img.jpg')
subject { uri }
it { is_expected.to be_an_instance_of(URI::HTTPUNIX) }
it 'has the correct attributes' do
expect(subject.scheme).to eq('http+unix')
expect(subject.hostname).to eq('/path/to/socket')
expect(subject.path).to eq('/img.jpg')
end
end
end
describe Net::HTTPUNIX do
def tmp_socket_path
# This has to be a relative path shorter than 100 bytes due to
# limitations in how Unix sockets work.
'tmp/test-socket'
end
before(:all) do
# "hello world" over unix socket server in background thread
FileUtils.mkdir_p(File.dirname(tmp_socket_path))
@server = HTTPUNIXServer.new(BindAddress: tmp_socket_path)
@server.mount_proc '/' do |req, resp|
resp.body = "Hello World (at #{req.path})"
end
@webrick_thread = Thread.new { @server.start }
sleep(0.1) while @webrick_thread.alive? && @server.status != :Running
raise "Couldn't start HTTPUNIXServer" unless @server.status == :Running
end
after(:all) do
@server.shutdown if @server
@webrick_thread.join if @webrick_thread
end
it "talks via HTTP ok" do
VCR.turned_off do
begin
WebMock.allow_net_connect!
http = Net::HTTPUNIX.new(tmp_socket_path)
expect(http.get('/').body).to eq('Hello World (at /)')
expect(http.get('/path').body).to eq('Hello World (at /path)')
ensure
WebMock.disable_net_connect!
end
end
end
end
require_relative 'spec_helper'
require_relative '../lib/object_dirs_helper'
describe ObjectDirsHelper do
before do
allow(Dir).to receive(:pwd).and_return('/home/git/repositories/foo/bar.git')
end
describe '.all_attributes' do
it do
expect(described_class.all_attributes.keys).to include(*%w[
GIT_OBJECT_DIRECTORY
GIT_OBJECT_DIRECTORY_RELATIVE
GIT_ALTERNATE_OBJECT_DIRECTORIES
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
])
end
end
describe '.absolute_object_dir' do
subject { described_class.absolute_object_dir }
context 'when GIT_OBJECT_DIRECTORY is set' do
let(:dir) { '/home/git/repositories/foo/bar.git/./objects' }
before do
allow(ENV).to receive(:[]).with('GIT_OBJECT_DIRECTORY').and_return(dir)
end
it { expect(subject).to eq(dir) }
end
context 'when GIT_OBJECT_DIRECTORY is not set' do
it { expect(subject).to be_nil }
end
end
describe '.absolute_alt_object_dirs' do
subject { described_class.absolute_alt_object_dirs }
context 'when GIT_ALTERNATE_OBJECT_DIRECTORIES is set' do
let(:dirs) { [
'/home/git/repositories/foo/bar.git/./incoming-UKU6Gl',
'/home/git/repositories/foo/bar.git/./incoming-AcU7Qr'
] }
before do
allow(ENV).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES').and_return(dirs.join(File::PATH_SEPARATOR))
end
it { expect(subject).to eq(dirs) }
end
context 'when GIT_ALTERNATE_OBJECT_DIRECTORIES is not set' do
it { expect(subject).to eq([]) }
end
end
describe '.relative_alt_object_dirs' do
subject { described_class.relative_alt_object_dirs }
context 'when GIT_ALTERNATE_OBJECT_DIRECTORIES is set' do
let(:dirs) { [
'/home/git/repositories/foo/bar.git/./objects/incoming-UKU6Gl',
'/home/git/repositories/foo/bar.git/./objects/incoming-AcU7Qr'
] }
before do
allow(ENV).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES').and_return(dirs.join(File::PATH_SEPARATOR))
end
it { expect(subject).to eq(['objects/incoming-UKU6Gl', 'objects/incoming-AcU7Qr']) }
end
context 'when GIT_ALTERNATE_OBJECT_DIRECTORIES is not set' do
it { expect(subject).to eq([]) }
end
end
describe '.relative_object_dir' do
subject { described_class.relative_object_dir }
context 'when GIT_OBJECT_DIRECTORY is set' do
before do
allow(ENV).to receive(:[]).with('GIT_OBJECT_DIRECTORY').and_return('/home/git/repositories/foo/bar.git/./objects')
end
it { expect(subject).to eq('objects') }
end
context 'when GIT_OBJECT_DIRECTORY is not set' do
it { expect(subject).to be_nil }
end
end
end
require 'pry'
require 'rspec-parameterized'
require 'simplecov'
SimpleCov.start
require 'gitlab_init'
ROOT_PATH = File.expand_path('..', __dir__)
Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f }
RSpec.configure do |config|
config.run_all_when_everything_filtered = true
config.filter_run :focus
config.before(:each) do
stub_const('ROOT_PATH', File.expand_path('..', __dir__))
end
end
require 'yaml'
RSpec.shared_context 'gitlab shell', shared_context: :metadata do
def original_root_path
ROOT_PATH
......@@ -45,9 +47,8 @@ RSpec.shared_context 'gitlab shell', shared_context: :metadata do
raise "Couldn't start stub GitlabNet server" unless @server.status == :Running
system(original_root_path, 'bin/compile')
copy_dirs = ['bin', 'lib']
FileUtils.rm_rf(copy_dirs.map { |d| File.join(tmp_root_path, d) })
FileUtils.cp_r(copy_dirs, tmp_root_path)
FileUtils.rm_rf(File.join(tmp_root_path, 'bin'))
FileUtils.cp_r('bin', tmp_root_path)
end
after(:all) do
......
#!/bin/sh
printenv GL_ID | grep -q '^key_1$'
#!/bin/bash
#echo "fail: $0"
exit 1
#!/bin/bash
#echo "ok: $0"
exit 0
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'spec/vcr_cassettes'
c.hook_into :webmock
c.configure_rspec_metadata!
end
require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true)
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 67ab4954-19e6-42ce-aae6-55c8ae5a365e
X-Runtime:
- '0.230871'
body:
encoding: UTF-8
string: '{"status":true,"gl_repository":"project-3","gl_project_path":"gitlab-org/gitlab.test","repository_path":"/Users/dzaporozhets/Projects/gitlab-development-kit/repositories/gitlab-org/gitlab-test.git"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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
message: Not Found
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- text/html
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: '<p>The project you were looking for could not be found.</p>'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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
message: Not Found
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- text/plain
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: 'The project you were looking for could not be found.'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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
message: Not Found
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: '{"status":false,"message":"The project you were looking for could not be found."}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- text/html
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: '<p>The project you were looking for could not be found.</p>'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- text/plain
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: 'The project you were looking for could not be found.'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: '{"status":false,"message":"The project you were looking for could not be found."}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-receive-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=ssh&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '155'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:52 GMT
Etag:
- W/"45654cae433b5a9c5fbba1d45d382e52"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 8d4b8b06-fb6e-4f94-832f-72f8e0afad5f
X-Runtime:
- '0.289759'
body:
encoding: UTF-8
string: '{"status":true,"gl_repository":"project-3","gl_project_path":"gitlab-org/gitlab.test","repository_path":"/Users/dzaporozhets/Projects/gitlab-development-kit/repositories/gitlab-org/gitlab-test.git"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:52 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: http://localhost:3000/api/v4/internal/broadcast_message
body:
encoding: US-ASCII
string: secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '2'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:50 GMT
Etag:
- W/"99914b932bd37a50b983c5e7c90ae93b"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- d31271ab-e21f-4349-a4c3-54f238c075c3
X-Runtime:
- '0.254031'
body:
encoding: UTF-8
string: "{}"
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:50 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: http://localhost:3000/api/v4/internal/broadcast_message
body:
encoding: US-ASCII
string: secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '153'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 12:29:13 GMT
Etag:
- W/"ce6de457fcc884f50125e81a10f165ce"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- ebaaf2c2-112e-4b3c-9182-e8714cd2a29c
X-Runtime:
- '0.276085'
body:
encoding: UTF-8
string: '{"message":"Message","starts_at":"2017-06-21T12:28:00.000Z","ends_at":"2017-06-21T12:35:00.000Z","color":"#e75e40","font":"#ffffff","id":1,"active":true}'
http_version:
recorded_at: Wed, 21 Jun 2017 12:29:13 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: http://localhost:3000/api/v4/internal/check
body:
encoding: US-ASCII
string: secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '72'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:49 GMT
Etag:
- W/"e1b00c927355c31ffb83b0800821cdbd"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 19d8b75c-45d1-4b8b-9e47-2fda86f98187
X-Runtime:
- '0.245482'
body:
encoding: UTF-8
string: '{"api_version":"v4","gitlab_version":"9.3.0-pre","gitlab_rev":"8f537e5"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:49 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/fake/info_refs_bad
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-11"},"output":"","secret_token":"0a3938d9d95d807e94d937af3a4fbbea\n"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 403
message: Forbidden
headers:
Date:
- Fri, 20 Jul 2018 06:54:21 GMT
Connection:
- close
Content-Type:
- application/json
X-Request-Id:
- ea0644ac-e1ad-45f6-aa72-cc7910274318
X-Runtime:
- '1.236672'
body:
encoding: UTF-8
string: '{"message":"You cannot perform write operations on a read-only instance"}'
http_version:
recorded_at: Fri, 20 Jul 2018 06:54:21 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/fake/info_refs_bad
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-11"},"output":"","secret_token":"0a3938d9d95d807e94d937af3a4fbbea\n"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 403
message: Forbidden
headers:
Date:
- Fri, 20 Jul 2018 06:54:21 GMT
Connection:
- close
Content-Type:
- application/json
X-Request-Id:
- ea0644ac-e1ad-45f6-aa72-cc7910274318
X-Runtime:
- '1.236672'
body:
encoding: UTF-8
string: '""'
http_version:
recorded_at: Fri, 20 Jul 2018 06:54:21 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/fake/info_refs
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-1"},"output":"","secret_token":"0a3938d9d95d807e94d937af3a4fbbea"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 200
message: OK
headers:
Date:
- Fri, 20 Jul 2018 06:18:58 GMT
Connection:
- close
X-Frame-Options:
- SAMEORIGIN
X-Content-Type-Options:
- nosniff
Content-Type:
- application/json
Content-Length:
- '172'
Vary:
- Origin
Etag:
- W/"7d01e1e3dbcbe7cca9607461352f8244"
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- 03afa234-b6be-49ab-9392-4aa35c5dee25
X-Runtime:
- '1.436040'
body:
encoding: UTF-8
string: '{'
http_version:
recorded_at: Fri, 20 Jul 2018 06:18:58 GMT
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/fake/info_refs
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-1"},"output":"","secret_token":"0a3938d9d95d807e94d937af3a4fbbea"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 200
message: OK
headers:
Date:
- Fri, 20 Jul 2018 06:18:58 GMT
Connection:
- close
X-Frame-Options:
- SAMEORIGIN
X-Content-Type-Options:
- nosniff
Content-Type:
- application/json
Content-Length:
- '172'
Vary:
- Origin
Etag:
- W/"7d01e1e3dbcbe7cca9607461352f8244"
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- 03afa234-b6be-49ab-9392-4aa35c5dee25
X-Runtime:
- '1.436040'
body:
encoding: UTF-8
string: '{"result":"aW5mb19yZWZzLXJlc3VsdA==\n"}'
http_version:
recorded_at: Fri, 20 Jul 2018 06:18:58 GMT
- request:
method: post
uri: http://localhost:3000/api/v4/fake/push
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-1"},"output":"info_refs-result","secret_token":"0a3938d9d95d807e94d937af3a4fbbea"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 201
message: Created
headers:
Date:
- Fri, 20 Jul 2018 06:19:08 GMT
Connection:
- close
X-Frame-Options:
- SAMEORIGIN
X-Content-Type-Options:
- nosniff
Content-Type:
- application/json
Content-Length:
- '13'
Vary:
- Origin
Cache-Control:
- no-cache
X-Request-Id:
- 0c6894ac-7f8e-4cdb-871f-4cb64d3731ca
X-Runtime:
- '0.786754'
body:
encoding: UTF-8
string: '{"result":"cHVzaC1yZXN1bHQ=\n"}'
http_version:
recorded_at: Fri, 20 Jul 2018 06:19:08 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/fake/info_refs
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-1"},"output":"","secret_token":"0a3938d9d95d807e94d937af3a4fbbea"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 200
message: OK
headers:
Date:
- Fri, 20 Jul 2018 06:18:58 GMT
Connection:
- close
X-Frame-Options:
- SAMEORIGIN
X-Content-Type-Options:
- nosniff
Content-Type:
- application/json
Content-Length:
- '172'
Vary:
- Origin
Etag:
- W/"7d01e1e3dbcbe7cca9607461352f8244"
Cache-Control:
- max-age=0, private, must-revalidate
X-Request-Id:
- 03afa234-b6be-49ab-9392-4aa35c5dee25
X-Runtime:
- '1.436040'
body:
encoding: UTF-8
string: '{"result":"aW5mb19yZWZzLXJlc3VsdA==\n"}'
http_version:
recorded_at: Fri, 20 Jul 2018 06:18:58 GMT
- request:
method: post
uri: http://localhost:3000/api/v4/fake/push
body:
encoding: UTF-8
string: '{"data":{"gl_username":"user1","primary_repo":"http://localhost:3001/user1/repo1.git","gl_id":"key-1"},"output":"info_refs-result","secret_token":"0a3938d9d95d807e94d937af3a4fbbea"}'
headers:
Content-Type:
- application/json
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- localhost
response:
status:
code: 201
message: Created
headers:
Date:
- Fri, 20 Jul 2018 06:19:08 GMT
Connection:
- close
X-Frame-Options:
- SAMEORIGIN
X-Content-Type-Options:
- nosniff
Content-Type:
- application/json
Content-Length:
- '13'
Vary:
- Origin
Cache-Control:
- no-cache
X-Request-Id:
- 0c6894ac-7f8e-4cdb-871f-4cb64d3731ca
X-Runtime:
- '0.786754'
body:
encoding: UTF-8
string: '{"result":"cHVzaC1yZXN1bHQ=\n"}'
http_version:
recorded_at: Fri, 20 Jul 2018 06:19:08 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: http://localhost:3000/api/v4/internal/discover?key_id=1
body:
encoding: US-ASCII
string: secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '42'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:44:49 GMT
Etag:
- W/"63b4ab301951bea83c4fc398eba8e307"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- dc11b8d4-1972-417b-8305-2c35c849405c
X-Runtime:
- '0.230170'
body:
encoding: UTF-8
string: '{"name":"Administrator","username":"root"}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:44:49 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: post
uri: http://localhost:3000/api/v4/internal/allowed
body:
encoding: US-ASCII
string: action=git-upload-pack&changes=0000000000000000000000000000000000000000+92d0970eefd7acb6d548878925ce2208cfe2d2ec+refs%2Fheads%2Fbranch4&gl_repository&project=gitlab-org%2Fgitlab-test.git&protocol=http&env=%7B%7D&key_id=1&secret_token=0a3938d9d95d807e94d937af3a4fbbea%0A
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:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Length:
- '62'
Content-Type:
- application/json
Date:
- Wed, 21 Jun 2017 10:32:01 GMT
Etag:
- W/"71e09fcf8a60a03cd1acc22806386ead"
Vary:
- Origin
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 70bdecc9-0078-4a4b-aa6b-cac1b2578886
X-Runtime:
- '0.324202'
body:
encoding: UTF-8
string: '{"status":false,"message":"Pulling over HTTP is not allowed."}'
http_version:
recorded_at: Wed, 21 Jun 2017 10:32:01 GMT
recorded_with: VCR 2.4.0
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment