Commit 3d4f71f1 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ash.mckenzie/7516-add-qa-tests-for-geo-push-to-primary-proxy' into 'master'

Geo: HTTP push to secondary specs

See merge request gitlab-org/gitlab-ee!7491
parents a066f3e0 55959343
...@@ -3,11 +3,21 @@ module QA ...@@ -3,11 +3,21 @@ module QA
module Page module Page
module Project module Project
module Show module Show
def wait_for_repository_replication def wait_for_repository_replication(max_wait: Runtime::Geo.max_file_replication_time)
wait(max: Runtime::Geo.max_file_replication_time) do wait_until_geo_max_replication_time(max_wait: max_wait) do
!page.has_text?(/No repository|The repository for this project is empty/) !page.has_text?(/No repository|The repository for this project is empty/)
end end
end end
def wait_for_repository_replication_with(text, max_wait: Runtime::Geo.max_file_replication_time)
wait_until_geo_max_replication_time(max_wait: max_wait) do
page.has_text?(text)
end
end
def wait_until_geo_max_replication_time(max_wait: Runtime::Geo.max_file_replication_time)
wait(max: max_wait) { yield }
end
end end
end end
end end
......
# frozen_string_literal: true
require 'cgi' require 'cgi'
require 'uri' require 'uri'
require 'open3' require 'open3'
require 'fileutils'
require 'tmpdir'
module QA module QA
module Git module Git
class Repository class Repository
include Scenario::Actable include Scenario::Actable
attr_writer :password
attr_accessor :env_vars
def initialize def initialize
@ssh_cmd = "" # 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 end
def self.perform(*args) def self.perform(*args)
...@@ -21,36 +31,27 @@ module QA ...@@ -21,36 +31,27 @@ module QA
@uri = URI(address) @uri = URI(address)
end end
def username=(name) def username=(username)
@username = name @username = username
@uri.user = name @uri.user = username
end
def password=(pass)
@password = pass
@uri.password = CGI.escape(pass).gsub('+', '%20')
end end
def use_default_credentials def use_default_credentials
if ::QA::Runtime::User.ldap_user? self.username, self.password = default_credentials
self.username = Runtime::User.ldap_username
self.password = Runtime::User.ldap_password add_credentials_to_netrc unless ssh_key_set?
else
self.username = Runtime::User.username
self.password = Runtime::User.password
end
end end
def clone(opts = '') def clone(opts = '')
run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./")) run("git clone #{opts} #{uri} ./")
end end
def checkout(branch_name) def checkout(branch_name)
`git checkout "#{branch_name}"` run(%Q{git checkout "#{branch_name}"})
end end
def checkout_new_branch(branch_name) def checkout_new_branch(branch_name)
`git checkout -b "#{branch_name}"` run(%Q{git checkout -b "#{branch_name}"})
end end
def shallow_clone def shallow_clone
...@@ -58,12 +59,10 @@ module QA ...@@ -58,12 +59,10 @@ module QA
end end
def configure_identity(name, email) def configure_identity(name, email)
`git config user.name #{name}` run(%Q{git config user.name #{name}})
`git config user.email #{email}` run(%Q{git config user.email #{email}})
end
def configure_ssh_command(command) add_credentials_to_netrc
@ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
end end
def commit_file(name, contents, message) def commit_file(name, contents, message)
...@@ -74,54 +73,103 @@ module QA ...@@ -74,54 +73,103 @@ module QA
def add_file(name, contents) def add_file(name, contents)
::File.write(name, contents) ::File.write(name, contents)
`git add #{name}` run(%Q{git add #{name}})
end end
def commit(message) def commit(message)
`git commit -m "#{message}"` run(%Q{git commit -m "#{message}"})
end end
def push_changes(branch = 'master') def push_changes(branch = 'master')
output, _ = run_and_redact_credentials(build_git_command("git push #{@uri} #{branch}")) run("git push #{uri} #{branch}")
output
end end
def commits def commits
`git log --oneline`.split("\n") run('git log --oneline').split("\n")
end end
def use_ssh_key(key) def use_ssh_key(key)
@private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}") @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
File.binwrite(@private_key_file, key.private_key) File.binwrite(private_key_file, key.private_key)
File.chmod(0700, @private_key_file) File.chmod(0700, private_key_file)
@known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}") @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
keyscan_params = ['-H'] keyscan_params = ['-H']
keyscan_params << "-p #{@uri.port}" if @uri.port keyscan_params << "-p #{uri.port}" if uri.port
keyscan_params << @uri.host keyscan_params << uri.host
run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{@known_hosts_file.path}") run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
configure_ssh_command("ssh -i #{@private_key_file.path} -o UserKnownHostsFile=#{@known_hosts_file.path}") self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end end
def delete_ssh_key def delete_ssh_key
return unless @private_key_file return unless ssh_key_set?
@private_key_file.close(true) private_key_file.close(true)
@known_hosts_file.close(true) known_hosts_file.close(true)
end end
def build_git_command(command_str) private
[@ssh_cmd, command_str].compact.join(' ')
attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
def debug?
Runtime::Env.respond_to?(:verbose?) && Runtime::Env.verbose?
end end
private def ssh_key_set?
!private_key_file.nil?
end
def run(command_str)
command = [env_vars, command_str, '2>&1'].compact.join(' ')
warn "DEBUG: command=[#{command}]" if debug?
output, _ = Open3.capture2(command)
output = output.chomp.gsub(/\s+$/, '')
warn "DEBUG: output=[#{output}]" if debug?
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?
# Since the remote URL contains the credentials, and git occasionally FileUtils.mkdir_p(tmp_netrc_directory)
# outputs the URL. Note that stderr is redirected to stdout. File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
def run_and_redact_credentials(command) File.chmod(0600, netrc_file_path)
Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'")
end end
end end
end end
......
# frozen_string_literal: true
module QA
context :geo, :orchestrated, :geo do
describe 'GitLab Geo HTTP push secondary' do
let(:file_name) { 'README.md' }
let(:file_content_primary) { 'This is a Geo project! Commit from primary.' }
let(:file_content_secondary) { 'This is a Geo project! Commit from secondary.' }
it 'is redirected to the primary and ultimately replicated to the secondary' do
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
# Visit the primary node and login
Page::Main::Login.act { sign_in_using_credentials }
# Create a new Project
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project'
project.description = 'Geo test project'
end
# Perform a git push over HTTP directly to the primary
Factory::Repository::Push.fabricate! do |push|
push.repository_http_uri = project.repository_http_location.uri
push.file_name = file_name
push.file_content = "# #{file_content_primary}"
push.commit_message = 'Add README.md'
end
# Validate git push worked and file exists with content
Page::Project::Show.perform do |show|
show.wait_for_repository_replication
expect(page).to have_content file_name
expect(page).to have_content file_content_primary
end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
# Visit the secondary node and login
Page::Main::OAuth.act { authorize! if needs_authorization? }
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
Page::Main::Menu.perform { |menu| menu.go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(project.name)
dashboard.go_to_project(project.name)
end
# Validate the content has been sync'd from the primary
Page::Project::Show.perform do |show|
show.wait_for_repository_replication_with(file_content_primary)
expect(page).to have_content file_name
expect(page).to have_content file_content_primary
end
# Grab the HTTP URI for the secondary and store as 'location'
location = Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
# Perform a git push over HTTP at the secondary
Factory::Repository::Push.fabricate! do |push|
push.new_branch = false
push.repository_http_uri = location.uri
push.file_name = file_name
push.file_content = "# #{file_content_secondary}"
push.commit_message = 'Update README.md'
end
# Validate git push worked and new content is visible
Page::Project::Show.perform do |show|
show.wait_for_repository_replication_with(file_content_secondary)
show.refresh
expect(page).to have_content file_name
expect(page).to have_content file_content_secondary
end
end
end
end
end
end
end
...@@ -8,10 +8,8 @@ describe QA::Git::Repository do ...@@ -8,10 +8,8 @@ describe QA::Git::Repository do
end end
describe '#clone' do describe '#clone' do
it 'redacts credentials from the URI in output' do it 'is unable to resolve host' do
output, _ = repository.clone expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
expect(output).to include("fatal: unable to access 'http://****@foo/bar.git/'")
end end
end end
...@@ -20,10 +18,8 @@ describe QA::Git::Repository do ...@@ -20,10 +18,8 @@ describe QA::Git::Repository do
`git init` # need a repo to push from `git init` # need a repo to push from
end end
it 'redacts credentials from the URI in output' do it 'fails to push changes' do
output, _ = repository.push_changes expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
expect(output).to include("error: failed to push some refs to 'http://****@foo/bar.git'")
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