diff options
Diffstat (limited to 'qa/qa/git/repository.rb')
-rw-r--r-- | qa/qa/git/repository.rb | 154 |
1 files changed, 129 insertions, 25 deletions
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 3df6db05970..7f959441dac 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -1,12 +1,26 @@ +# frozen_string_literal: true + require 'cgi' require 'uri' require 'open3' +require 'fileutils' +require 'tmpdir' module QA module Git class Repository include Scenario::Actable + attr_writer :password + attr_accessor :env_vars + + def initialize + # We set HOME to the current working directory (which is a + # temporary directory created in .perform()) so the temporarily dropped + # .netrc can be utilised + self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}] + end + def self.perform(*args) Dir.mktmpdir do |dir| Dir.chdir(dir) { super } @@ -17,31 +31,27 @@ module QA @uri = URI(address) end - def username=(name) - @username = name - @uri.user = name - end - - def password=(pass) - @password = pass - @uri.password = CGI.escape(pass).gsub('+', '%20') + def username=(username) + @username = username + @uri.user = username end def use_default_credentials - self.username = Runtime::User.name - self.password = Runtime::User.password + self.username, self.password = default_credentials + + add_credentials_to_netrc unless ssh_key_set? end def clone(opts = '') - run_and_redact_credentials("git clone #{opts} #{@uri} ./") + run("git clone #{opts} #{uri} ./") end def checkout(branch_name) - `git checkout "#{branch_name}"` + run(%Q{git checkout "#{branch_name}"}) end def checkout_new_branch(branch_name) - `git checkout -b "#{branch_name}"` + run(%Q{git checkout -b "#{branch_name}"}) end def shallow_clone @@ -49,8 +59,10 @@ module QA end def configure_identity(name, email) - `git config user.name #{name}` - `git config user.email #{email}` + run(%Q{git config user.name #{name}}) + run(%Q{git config user.email #{email}}) + + add_credentials_to_netrc end def commit_file(name, contents, message) @@ -61,29 +73,121 @@ module QA def add_file(name, contents) ::File.write(name, contents) - `git add #{name}` + run(%Q{git add #{name}}) end def commit(message) - `git commit -m "#{message}"` + run(%Q{git commit -m "#{message}"}) end def push_changes(branch = 'master') - output, _ = run_and_redact_credentials("git push #{@uri} #{branch}") - - output + run("git push #{uri} #{branch}") end def commits - `git log --oneline`.split("\n") + run('git log --oneline').split("\n") + end + + def use_ssh_key(key) + @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}") + File.binwrite(private_key_file, key.private_key) + File.chmod(0700, private_key_file) + + @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}") + keyscan_params = ['-H'] + keyscan_params << "-p #{uri.port}" if uri.port + keyscan_params << uri.host + run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}") + + self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"} + end + + def delete_ssh_key + return unless ssh_key_set? + + private_key_file.close(true) + known_hosts_file.close(true) + end + + def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit') + self.git_protocol = version + add_file(file_name, file_content) + commit(commit_message) + push_changes + + fetch_supported_git_protocol + end + + def git_protocol=(value) + raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s) + + run("git config protocol.version #{value}") + end + + def fetch_supported_git_protocol + # ls-remote is one command known to respond to Git protocol v2 so we use + # it to get output including the version reported via Git tracing + output = run("git ls-remote #{uri}", "GIT_TRACE_PACKET=1") + output[/git< version (\d+)/, 1] || 'unknown' end private - # Since the remote URL contains the credentials, and git occasionally - # outputs the URL. Note that stderr is redirected to stdout. - def run_and_redact_credentials(command) - Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'") + attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file + + def ssh_key_set? + !private_key_file.nil? + end + + def run(command_str, *extra_env) + command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ') + Runtime::Logger.debug "Git: command=[#{command}]" + + output, _ = Open3.capture2(command) + output = output.chomp.gsub(/\s+$/, '') + Runtime::Logger.debug "Git: output=[#{output}]" + + output + end + + def default_credentials + if ::QA::Runtime::User.ldap_user? + [Runtime::User.ldap_username, Runtime::User.ldap_password] + else + [Runtime::User.username, Runtime::User.password] + end + end + + def tmp_netrc_directory + @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s) + end + + def netrc_file_path + @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc') + end + + def netrc_content + "machine #{uri.host} login #{username} password #{password}" + end + + def netrc_already_contains_content? + File.exist?(netrc_file_path) && + File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any? + end + + def add_credentials_to_netrc + # 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() + # + return if netrc_already_contains_content? + + FileUtils.mkdir_p(tmp_netrc_directory) + File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) } + File.chmod(0600, netrc_file_path) end end end |