shell_spec.rb 14.0 KB
Newer Older
1
require 'spec_helper'
2
require 'stringio'
3

4
describe Gitlab::Shell do
D
Dmitriy Zaporozhets 已提交
5
  let(:project) { double('Project', id: 7, path: 'diaspora') }
6
  let(:gitlab_shell) { described_class.new }
7
  let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
8 9

  before do
10
    allow(Project).to receive(:find).and_return(project)
11 12
  end

13 14 15 16 17
  it { is_expected.to respond_to :add_key }
  it { is_expected.to respond_to :remove_key }
  it { is_expected.to respond_to :add_repository }
  it { is_expected.to respond_to :remove_repository }
  it { is_expected.to respond_to :fork_repository }
18

19
  it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
20

21
  describe 'memoized secret_token' do
22 23 24 25 26
    let(:secret_file) { 'tmp/tests/.secret_shell_test' }
    let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' }

    before do
      allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file)
27
      allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
28
      FileUtils.mkdir('tmp/tests/shell-secret-test')
29
      described_class.ensure_secret_token!
30 31 32 33 34 35 36 37
    end

    after do
      FileUtils.rm_rf('tmp/tests/shell-secret-test')
      FileUtils.rm_rf(secret_file)
    end

    it 'creates and links the secret token file' do
38
      secret_token = described_class.secret_token
39

40
      expect(File.exist?(secret_file)).to be(true)
41
      expect(File.read(secret_file).chomp).to eq(secret_token)
42 43 44 45 46
      expect(File.symlink?(link_file)).to be(true)
      expect(File.readlink(link_file)).to eq(secret_file)
    end
  end

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  describe 'projects commands' do
    let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
    let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
    let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }

    before do
      allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
      allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
      allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
    end

    describe '#mv_repository' do
      it 'executes the command' do
        expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
          [projects_path, 'mv-project', 'storage/path', 'project/path.git', 'new/path.git']
        )
        gitlab_shell.mv_repository('storage/path', 'project/path', 'new/path')
      end
    end

    describe '#add_key' do
      it 'removes trailing garbage' do
        allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
        expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
          [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
        )

        gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
      end
76 77 78
    end
  end

79
  describe Gitlab::Shell::KeyAdder do
80
    describe '#add_key' do
81 82
      it 'removes trailing garbage' do
        io = spy(:io)
83 84
        adder = described_class.new(io)

85 86 87 88 89
        adder.add_key('key-42', "ssh-rsa foo bar\tbaz")

        expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
      end

90 91 92 93 94 95 96 97 98
      it 'handles multiple spaces in the key' do
        io = spy(:io)
        adder = described_class.new(io)

        adder.add_key('key-42', "ssh-rsa  foo")

        expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
      end

99 100 101 102 103
      it 'raises an exception if the key contains a tab' do
        expect do
          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
        end.to raise_error(Gitlab::Shell::Error)
      end
104

105 106 107 108
      it 'raises an exception if the key contains a newline' do
        expect do
          described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
        end.to raise_error(Gitlab::Shell::Error)
109 110 111
      end
    end
  end
112 113

  describe 'projects commands' do
114 115 116
    let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
    let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
    let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }
117 118

    before do
119 120
      allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
      allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
121 122 123
      allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
    end

124
    describe '#add_repository' do
J
Jacob Vosmaer 已提交
125 126 127 128 129
      shared_examples '#add_repository' do
        let(:repository_storage) { 'default' }
        let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
        let(:repo_name) { 'project/path' }
        let(:created_path) { File.join(repository_storage_path, repo_name + '.git') }
130

J
Jacob Vosmaer 已提交
131
        after do
132 133 134
          FileUtils.rm_rf(created_path)
        end

J
Jacob Vosmaer 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
        it 'creates a repository' do
          expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy

          expect(File.stat(created_path).mode & 0o777).to eq(0o770)

          hooks_path = File.join(created_path, 'hooks')
          expect(File.lstat(hooks_path)).to be_symlink
          expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path)
        end

        it 'returns false when the command fails' do
          FileUtils.mkdir_p(File.dirname(created_path))
          # This file will block the creation of the repo's .git directory. That
          # should cause #add_repository to fail.
          FileUtils.touch(created_path)

          expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy
        end
153 154
      end

J
Jacob Vosmaer 已提交
155
      context 'with gitaly' do
J
Jacob Vosmaer 已提交
156 157
        it_behaves_like '#add_repository'
      end
158

159
      context 'without gitaly', :skip_gitaly_mock do
J
Jacob Vosmaer 已提交
160
        it_behaves_like '#add_repository'
161 162 163 164 165 166 167
      end
    end

    describe '#remove_repository' do
      it 'returns true when the command succeeds' do
        expect(Gitlab::Popen).to receive(:popen)
          .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
168
            nil, popen_vars).and_return([nil, 0])
169 170 171 172 173 174 175

        expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true
      end

      it 'returns false when the command fails' do
        expect(Gitlab::Popen).to receive(:popen)
          .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'],
176
            nil, popen_vars).and_return(["error", 1])
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

        expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false
      end
    end

    describe '#mv_repository' do
      it 'returns true when the command succeeds' do
        expect(Gitlab::Popen).to receive(:popen)
          .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
                nil, popen_vars).and_return([nil, 0])

        expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be true
      end

      it 'returns false when the command fails' do
        expect(Gitlab::Popen).to receive(:popen)
          .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'],
                nil, popen_vars).and_return(["error", 1])

        expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be false
      end
    end

    describe '#fork_repository' do
      it 'returns true when the command succeeds' do
        expect(Gitlab::Popen).to receive(:popen)
203
          .with([projects_path, 'fork-repository', 'current/storage', 'project/path.git', 'new/storage', 'fork/path.git'],
204 205
                nil, popen_vars).and_return([nil, 0])

206
        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'fork/path')).to be true
207 208 209 210
      end

      it 'return false when the command fails' do
        expect(Gitlab::Popen).to receive(:popen)
211
          .with([projects_path, 'fork-repository', 'current/storage', 'project/path.git', 'new/storage', 'fork/path.git'],
212 213
                nil, popen_vars).and_return(["error", 1])

214
        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'fork/path')).to be false
215 216 217
      end
    end

218 219 220 221
    shared_examples 'fetch_remote' do |gitaly_on|
      let(:project2) { create(:project, :repository) }
      let(:repository) { project2.repository }

222
      def fetch_remote(ssh_auth = nil)
223
        gitlab_shell.fetch_remote(repository.raw_repository, 'new/storage', ssh_auth: ssh_auth)
224 225
      end

226
      def expect_popen(fail = false, vars = {})
227 228 229
        popen_args = [
          projects_path,
          'fetch-remote',
230 231
          TestEnv.repos_path,
          repository.relative_path,
232 233 234 235
          'new/storage',
          Gitlab.config.gitlab_shell.git_timeout.to_s
        ]

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        return_value = fail ? ["error", 1] : [nil, 0]

        expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)).and_return(return_value)
      end

      def expect_gitaly_call(fail, vars = {})
        receive_fetch_remote =
          if fail
            receive(:fetch_remote).and_raise(GRPC::NotFound)
          else
            receive(:fetch_remote).and_return(true)
          end

        expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive_fetch_remote
      end

      if gitaly_on
        def expect_call(fail, vars = {})
          expect_gitaly_call(fail, vars)
        end
      else
        def expect_call(fail, vars = {})
          expect_popen(fail, vars)
        end
260 261 262 263 264 265 266 267 268 269 270 271 272
      end

      def build_ssh_auth(opts = {})
        defaults = {
          ssh_import?: true,
          ssh_key_auth?: false,
          ssh_known_hosts: nil,
          ssh_private_key: nil
        }

        double(:ssh_auth, defaults.merge(opts))
      end

273
      it 'returns true when the command succeeds' do
274
        expect_call(false)
275

276
        expect(fetch_remote).to be_truthy
277 278 279
      end

      it 'raises an exception when the command fails' do
280
        expect_call(true)
281

282
        expect { fetch_remote }.to raise_error(Gitlab::Shell::Error)
283 284 285 286
      end

      context 'SSH auth' do
        it 'passes the SSH key if specified' do
287
          expect_call(false, 'GITLAB_SHELL_SSH_KEY' => 'foo')
288 289 290 291 292 293 294

          ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo')

          expect(fetch_remote(ssh_auth)).to be_truthy
        end

        it 'does not pass an empty SSH key' do
295
          expect_call(false)
296 297 298 299 300 301 302

          ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '')

          expect(fetch_remote(ssh_auth)).to be_truthy
        end

        it 'does not pass the key unless SSH key auth is to be used' do
303
          expect_call(false)
304 305 306 307 308 309 310

          ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo')

          expect(fetch_remote(ssh_auth)).to be_truthy
        end

        it 'passes the known_hosts data if specified' do
311
          expect_call(false, 'GITLAB_SHELL_KNOWN_HOSTS' => 'foo')
312 313 314 315 316 317 318

          ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo')

          expect(fetch_remote(ssh_auth)).to be_truthy
        end

        it 'does not pass empty known_hosts data' do
319
          expect_call(false)
320 321 322 323 324 325 326

          ssh_auth = build_ssh_auth(ssh_known_hosts: '')

          expect(fetch_remote(ssh_auth)).to be_truthy
        end

        it 'does not pass known_hosts data unless SSH is to be used' do
327
          expect_call(false, popen_vars)
328 329

          ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo')
330

331 332
          expect(fetch_remote(ssh_auth)).to be_truthy
        end
333 334 335
      end
    end

336
    describe '#fetch_remote local', :skip_gitaly_mock do
337 338 339 340 341 342 343
      it_should_behave_like 'fetch_remote', false
    end

    describe '#fetch_remote gitaly' do
      it_should_behave_like 'fetch_remote', true
    end

344 345 346
    describe '#import_repository' do
      it 'returns true when the command succeeds' do
        expect(Gitlab::Popen).to receive(:popen)
347 348
          .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
                nil, popen_vars).and_return([nil, 0])
349 350 351 352 353 354

        expect(gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git')).to be true
      end

      it 'raises an exception when the command fails' do
        expect(Gitlab::Popen).to receive(:popen)
355 356
        .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"],
              nil, popen_vars).and_return(["error", 1])
357 358 359 360 361

        expect { gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git') }.to raise_error(Gitlab::Shell::Error, "error")
      end
    end
  end
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409

  describe 'namespace actions' do
    subject { described_class.new }
    let(:storage_path) { Gitlab.config.repositories.storages.default.path }

    describe '#add_namespace' do
      it 'creates a namespace' do
        subject.add_namespace(storage_path, "mepmep")

        expect(subject.exists?(storage_path, "mepmep")).to be(true)
      end
    end

    describe '#exists?' do
      context 'when the namespace does not exist' do
        it 'returns false' do
          expect(subject.exists?(storage_path, "non-existing")).to be(false)
        end
      end

      context 'when the namespace exists' do
        it 'returns true' do
          subject.add_namespace(storage_path, "mepmep")

          expect(subject.exists?(storage_path, "mepmep")).to be(true)
        end
      end
    end

    describe '#remove' do
      it 'removes the namespace' do
        subject.add_namespace(storage_path, "mepmep")
        subject.rm_namespace(storage_path, "mepmep")

        expect(subject.exists?(storage_path, "mepmep")).to be(false)
      end
    end

    describe '#mv_namespace' do
      it 'renames the namespace' do
        subject.add_namespace(storage_path, "mepmep")
        subject.mv_namespace(storage_path, "mepmep", "2mep")

        expect(subject.exists?(storage_path, "mepmep")).to be(false)
        expect(subject.exists?(storage_path, "2mep")).to be(true)
      end
    end
  end
410
end