shell_spec.rb 12.9 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 20 21
  it { is_expected.to respond_to :add_namespace }
  it { is_expected.to respond_to :rm_namespace }
  it { is_expected.to respond_to :mv_namespace }
  it { is_expected.to respond_to :exists? }
22

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

25
  describe 'memoized secret_token' do
26 27 28 29 30
    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)
31
      allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
32
      FileUtils.mkdir('tmp/tests/shell-secret-test')
33
      described_class.ensure_secret_token!
34 35 36 37 38 39 40 41
    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
42
      secret_token = described_class.secret_token
43

44
      expect(File.exist?(secret_file)).to be(true)
45
      expect(File.read(secret_file).chomp).to eq(secret_token)
46 47 48 49 50
      expect(File.symlink?(link_file)).to be(true)
      expect(File.readlink(link_file)).to eq(secret_file)
    end
  end

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 76 77 78 79
  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
80 81 82
    end
  end

83
  describe Gitlab::Shell::KeyAdder do
84
    describe '#add_key' do
85 86
      it 'removes trailing garbage' do
        io = spy(:io)
87 88
        adder = described_class.new(io)

89 90 91 92 93
        adder.add_key('key-42', "ssh-rsa foo bar\tbaz")

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

94 95 96 97 98 99 100 101 102
      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

103 104 105 106 107
      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
108

109 110 111 112
      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)
113 114 115
      end
    end
  end
116 117

  describe 'projects commands' do
118 119 120
    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') }
121 122

    before do
123 124
      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)
125 126 127
      allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
    end

128
    describe '#add_repository' do
J
Jacob Vosmaer 已提交
129 130 131 132 133
      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') }
134

J
Jacob Vosmaer 已提交
135
        after do
136 137 138
          FileUtils.rm_rf(created_path)
        end

J
Jacob Vosmaer 已提交
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
        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
157 158
      end

J
Jacob Vosmaer 已提交
159
      context 'with gitaly' do
J
Jacob Vosmaer 已提交
160 161
        it_behaves_like '#add_repository'
      end
162

J
Jacob Vosmaer 已提交
163 164
      context 'without gitaly', skip_gitaly_mock: true do
        it_behaves_like '#add_repository'
165 166 167 168 169 170 171
      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'],
172
            nil, popen_vars).and_return([nil, 0])
173 174 175 176 177 178 179

        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'],
180
            nil, popen_vars).and_return(["error", 1])
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

        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)
          .with([projects_path, 'fork-project', 'current/storage', 'project/path.git', 'new/storage', 'new-namespace'],
                nil, popen_vars).and_return([nil, 0])

        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be true
      end

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

        expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'new-namespace')).to be false
      end
    end

222 223 224 225
    shared_examples 'fetch_remote' do |gitaly_on|
      let(:project2) { create(:project, :repository) }
      let(:repository) { project2.repository }

226
      def fetch_remote(ssh_auth = nil)
227
        gitlab_shell.fetch_remote(repository.raw_repository, 'new/storage', ssh_auth: ssh_auth)
228 229
      end

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

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
        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
264 265 266 267 268 269 270 271 272 273 274 275 276
      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

277
      it 'returns true when the command succeeds' do
278
        expect_call(false)
279

280
        expect(fetch_remote).to be_truthy
281 282 283
      end

      it 'raises an exception when the command fails' do
284
        expect_call(true)
285

286
        expect { fetch_remote }.to raise_error(Gitlab::Shell::Error)
287 288 289 290
      end

      context 'SSH auth' do
        it 'passes the SSH key if specified' do
291
          expect_call(false, 'GITLAB_SHELL_SSH_KEY' => 'foo')
292 293 294 295 296 297 298

          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
299
          expect_call(false)
300 301 302 303 304 305 306

          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
307
          expect_call(false)
308 309 310 311 312 313 314

          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
315
          expect_call(false, 'GITLAB_SHELL_KNOWN_HOSTS' => 'foo')
316 317 318 319 320 321 322

          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
323
          expect_call(false)
324 325 326 327 328 329 330

          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
331
          expect_call(false, popen_vars)
332 333

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

335 336
          expect(fetch_remote(ssh_auth)).to be_truthy
        end
337 338 339
      end
    end

340 341 342 343 344 345 346 347
    describe '#fetch_remote local', skip_gitaly_mock: true do
      it_should_behave_like 'fetch_remote', false
    end

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

348 349 350
    describe '#import_repository' do
      it 'returns true when the command succeeds' do
        expect(Gitlab::Popen).to receive(:popen)
351 352
          .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])
353 354 355 356 357 358

        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)
359 360
        .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])
361 362 363 364 365

        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
366
end