Commit c1e9b9e8 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'qa-ml-improve-git-repo-auth' into 'master'

Add git credentials to .netrc when needed

Closes gitlab-org/quality/nightly#57 and #56857

See merge request gitlab-org/gitlab-ce!24691
parents ed6b49b1 f6539522
...@@ -5,15 +5,19 @@ require 'uri' ...@@ -5,15 +5,19 @@ require 'uri'
require 'open3' require 'open3'
require 'fileutils' require 'fileutils'
require 'tmpdir' require 'tmpdir'
require 'tempfile'
require 'securerandom'
module QA module QA
module Git module Git
class Repository class Repository
include Scenario::Actable include Scenario::Actable
attr_writer :password, :use_lfs attr_writer :use_lfs
attr_accessor :env_vars attr_accessor :env_vars
InvalidCredentialsError = Class.new(RuntimeError)
def initialize def initialize
# We set HOME to the current working directory (which is a # We set HOME to the current working directory (which is a
# temporary directory created in .perform()) so the temporarily dropped # temporary directory created in .perform()) so the temporarily dropped
...@@ -28,6 +32,14 @@ module QA ...@@ -28,6 +32,14 @@ module QA
end end
end end
def password=(password)
@password = password
raise InvalidCredentialsError, "Please provide a username when setting a password" unless username
try_add_credentials_to_netrc
end
def uri=(address) def uri=(address)
@uri = URI(address) @uri = URI(address)
end end
...@@ -148,16 +160,7 @@ module QA ...@@ -148,16 +160,7 @@ module QA
return unless add_credentials? return unless add_credentials?
return if netrc_already_contains_content? return if netrc_already_contains_content?
# Despite libcurl supporting a custom .netrc location through the save_netrc_content
# CURLOPT_NETRC_FILE environment variable, git does not support it :(
# Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
#
# This will create a .netrc in the correct working directory, which is
# a temporary directory created in .perform()
#
FileUtils.mkdir_p(tmp_home_dir)
File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
File.chmod(0600, netrc_file_path)
end end
private private
...@@ -175,7 +178,6 @@ module QA ...@@ -175,7 +178,6 @@ module QA
def add_credentials? def add_credentials?
return false if !username || !password return false if !username || !password
return true unless ssh_key_set? return true unless ssh_key_set?
return true if ssh_key_set? && use_lfs?
false false
end end
...@@ -214,6 +216,23 @@ module QA ...@@ -214,6 +216,23 @@ module QA
end end
end end
def read_netrc_content
File.exist?(netrc_file_path) ? File.readlines(netrc_file_path) : []
end
def save_netrc_content
# Despite libcurl supporting a custom .netrc location through the
# CURLOPT_NETRC_FILE environment variable, git does not support it :(
# Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
#
# This will create a .netrc in the correct working directory, which is
# a temporary directory created in .perform()
#
FileUtils.mkdir_p(tmp_home_dir)
File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
File.chmod(0600, netrc_file_path)
end
def tmp_home_dir def tmp_home_dir
@tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s) @tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
end end
...@@ -227,8 +246,7 @@ module QA ...@@ -227,8 +246,7 @@ module QA
end end
def netrc_already_contains_content? def netrc_already_contains_content?
File.exist?(netrc_file_path) && read_netrc_content.grep(/^#{netrc_content}$/).any?
File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
end end
end end
end end
......
...@@ -67,8 +67,6 @@ module QA ...@@ -67,8 +67,6 @@ module QA
email = user.email email = user.email
end end
repository.try_add_credentials_to_netrc
@output += repository.clone @output += repository.clone
repository.configure_identity(username, email) repository.configure_identity(username, email)
......
describe QA::Git::Repository do describe QA::Git::Repository do
include Support::StubENV include Support::StubENV
let(:repository) { described_class.new } shared_context 'git directory' do
let(:repository) { described_class.new }
let(:tmp_git_dir) { Dir.mktmpdir }
let(:tmp_netrc_dir) { Dir.mktmpdir }
before do before do
stub_env('GITLAB_USERNAME', 'root') stub_env('GITLAB_USERNAME', 'root')
cd_empty_temp_directory cd_empty_temp_directory
set_bad_uri set_bad_uri
repository.use_default_credentials
end
describe '#clone' do allow(repository).to receive(:tmp_home_dir).and_return(tmp_netrc_dir)
it 'is unable to resolve host' do
expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
end end
end
describe '#push_changes' do after do
before do # Switch to a safe dir before deleting tmp dirs to avoid dir access errors
`git init` # need a repo to push from FileUtils.cd __dir__
FileUtils.remove_entry_secure(tmp_git_dir, true)
FileUtils.remove_entry_secure(tmp_netrc_dir, true)
end end
it 'fails to push changes' do def cd_empty_temp_directory
expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'") FileUtils.cd tmp_git_dir
end
def set_bad_uri
repository.uri = 'http://foo/bar.git'
end end
end end
describe '#git_protocol=' do context 'with default credentials' do
[0, 1, 2].each do |version| include_context 'git directory' do
it "configures git to use protocol version #{version}" do before do
expect(repository).to receive(:run).with("git config protocol.version #{version}") repository.use_default_credentials
repository.git_protocol = version
end end
end end
it 'raises an error if the version is unsupported' do describe '#clone' do
expect { repository.git_protocol = 'foo' }.to raise_error(ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2") it 'is unable to resolve host' do
expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
end
end end
end
describe '#fetch_supported_git_protocol' do describe '#push_changes' do
it "reports the detected version" do before do
expect(repository).to receive(:run).and_return("packet: git< version 2") `git init` # need a repo to push from
expect(repository.fetch_supported_git_protocol).to eq('2') end
it 'fails to push changes' do
expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
end
end end
it 'reports unknown if version is unknown' do describe '#git_protocol=' do
expect(repository).to receive(:run).and_return("packet: git< version -1") [0, 1, 2].each do |version|
expect(repository.fetch_supported_git_protocol).to eq('unknown') it "configures git to use protocol version #{version}" do
expect(repository).to receive(:run).with("git config protocol.version #{version}")
repository.git_protocol = version
end
end
it 'raises an error if the version is unsupported' do
expect { repository.git_protocol = 'foo' }.to raise_error(ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2")
end
end end
it 'reports unknown if content does not identify a version' do describe '#fetch_supported_git_protocol' do
expect(repository).to receive(:run).and_return("foo") it "reports the detected version" do
expect(repository.fetch_supported_git_protocol).to eq('unknown') expect(repository).to receive(:run).and_return("packet: git< version 2")
expect(repository.fetch_supported_git_protocol).to eq('2')
end
it 'reports unknown if version is unknown' do
expect(repository).to receive(:run).and_return("packet: git< version -1")
expect(repository.fetch_supported_git_protocol).to eq('unknown')
end
it 'reports unknown if content does not identify a version' do
expect(repository).to receive(:run).and_return("foo")
expect(repository.fetch_supported_git_protocol).to eq('unknown')
end
end end
end
def cd_empty_temp_directory describe '#use_default_credentials' do
tmp_dir = 'tmp/git-repository-spec/' it 'adds credentials to .netrc' do
FileUtils.rm_rf(tmp_dir) if ::File.exist?(tmp_dir) expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
FileUtils.mkdir_p tmp_dir .to eq("machine foo login #{QA::Runtime::User.default_username} password #{QA::Runtime::User.default_password}\n")
FileUtils.cd tmp_dir end
end
end end
def set_bad_uri context 'with specific credentials' do
repository.uri = 'http://foo/bar.git' include_context 'git directory'
context 'before setting credentials' do
it 'does not add credentials to .netrc' do
expect(repository).not_to receive(:save_netrc_content)
end
end
describe '#password=' do
it 'raises an error if no username was given' do
expect { repository.password = 'foo' }
.to raise_error(QA::Git::Repository::InvalidCredentialsError,
"Please provide a username when setting a password")
end
it 'adds credentials to .netrc' do
repository.username = 'user'
repository.password = 'foo'
expect(File.read(File.join(tmp_netrc_dir, '.netrc')))
.to eq("machine foo login user password foo\n")
end
end
end end
end end
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