# frozen_string_literal: true # rubocop:disable Metrics/AbcSize require 'securerandom' require 'net/http' require 'uri' require 'forwardable' require 'openssl' require 'tempfile' module Gitlab module QA module Component class Gitlab < Base extend Forwardable attr_reader :release, :omnibus_configuration, :omnibus_gitlab_rails_env attr_accessor :tls, :skip_availability_check, :runner_network, :seed_admin_token, :seed_db attr_writer :name, :relative_path def_delegators :release, :tag, :image, :edition CERTIFICATES_PATH = File.expand_path('../../../../tls_certificates', __dir__) DATA_SEED_PATH = File.expand_path('../../../../support/data', __dir__) SSL_PATH = '/etc/gitlab/ssl' TRUSTED_PATH = '/etc/gitlab/trusted-certs' DATA_PATH = '/tmp/data-seeds' def initialize super @skip_availability_check = false @omnibus_gitlab_rails_env = {} @omnibus_configuration = Runtime::OmnibusConfiguration.new(Runtime::Scenario.omnibus_configuration) @working_dir_tmp_cert_path = Dir.mktmpdir('certs', FileUtils.mkdir_p("#{Dir.pwd}/tmp")) @authority_cert_path = "#{@working_dir_tmp_cert_path}/authority" @gitlab_cert_path = "#{@working_dir_tmp_cert_path}/gitlab" @gitaly_cert_path = "#{@working_dir_tmp_cert_path}/gitaly" @volumes[@gitlab_cert_path] = SSL_PATH @volumes[@authority_cert_path] = TRUSTED_PATH @seed_admin_token = Runtime::Scenario.seed_admin_token @seed_db = Runtime::Scenario.seed_db self.release = 'CE' end def set_formless_login_token return if Runtime::Env.gitlab_qa_formless_login_token.to_s.strip.empty? @omnibus_gitlab_rails_env['GITLAB_QA_FORMLESS_LOGIN_TOKEN'] = Runtime::Env.gitlab_qa_formless_login_token end def set_license_mode return unless Runtime::Env.test_license_mode? @omnibus_gitlab_rails_env['GITLAB_LICENSE_MODE'] = 'test' @omnibus_gitlab_rails_env['CUSTOMER_PORTAL_URL'] = 'https://customers.staging.gitlab.com' end def elastic_url=(url) @environment['ELASTIC_URL'] = url end def release=(release) @release = QA::Release.new(release) end def name @name ||= "gitlab-#{edition}-#{SecureRandom.hex(4)}" end def address "#{scheme}://#{hostname}#{relative_path}" end def scheme tls ? 'https' : 'http' end def port tls ? '443:443' : '80' end def gitaly_tls @volumes.delete(@gitlab_cert_path) @volumes[@gitaly_cert_path] = SSL_PATH end def relative_path @relative_path ||= '' end def set_accept_insecure_certs Runtime::Env.accept_insecure_certs = 'true' end def prepare prepare_gitlab_omnibus_config copy_certificates super end def teardown! FileUtils.rm_rf(@working_dir_tmp_cert_path) super end def pull docker.login(**release.login_params) if release.login_params super end def prepare_gitlab_omnibus_config set_formless_login_token set_license_mode @omnibus_configuration << "gitlab_rails['env'] = #{@omnibus_gitlab_rails_env}" unless omnibus_gitlab_rails_env.empty? end def start # rubocop:disable Metrics/AbcSize ensure_configured! docker.run(image: image, tag: tag) do |command| command << "-d -p #{port}" command << "--name #{name}" command << "--net #{network}" command << "--hostname #{hostname}" @volumes.to_h.each do |to, from| command.volume(to, from, 'Z') end command.volume(File.join(Runtime::Env.host_artifacts_dir, name, 'logs'), '/var/log/gitlab', 'Z') @environment.to_h.each do |key, value| command.env(key, value) end @network_aliases.to_a.each do |network_alias| command << "--network-alias #{network_alias}" end end Docker::Command.execute("network connect --alias #{name}.#{network} --alias #{name}.#{runner_network} #{runner_network} #{name}") if runner_network end def reconfigure setup_omnibus @docker.attach(name) do |line, wait| puts line # TODO, workaround which allows to detach from the container # break if line =~ /gitlab Reconfigured!/ end end def wait_until_ready return if skip_availability_check if Availability.new(name, relative_path: relative_path, scheme: scheme, protocol_port: port.to_i).check(Runtime::Env.gitlab_availability_timeout) sleep 12 # TODO, handle that better puts ' -> GitLab is available.' else abort ' -> GitLab unavailable!' end end def process_exec_commands @docker.copy(name, DATA_SEED_PATH, DATA_PATH) if seed_admin_token || seed_db self.exec_commands += seed_admin_token_command if seed_admin_token self.exec_commands += seed_test_data_command if seed_db Runtime::Logger.info("Running exec_commands...") exec_commands.flatten.uniq.each { |command| @docker.exec(name, command) } end def sha_version json = @docker.read_file( @release.image, @release.tag, '/opt/gitlab/version-manifest.json' ) manifest = JSON.parse(json) manifest['software']['gitlab-rails']['locked_version'] end def copy_key_file(env_key) key_dir = ENV['CI_PROJECT_DIR'] || Dir.tmpdir key_file = Tempfile.new(env_key.downcase, key_dir) key_file.write(ENV.fetch(env_key)) key_file.close File.chmod(0o744, key_file.path) @volumes[key_file.path] = key_file.path key_file.path end private # Copy certs to a temporary directory in current working directory. # This is needed for docker-in-docker ci environments where mount points outside of build dir are not accessible # # @return [void] def copy_certificates FileUtils.cp_r("#{CERTIFICATES_PATH}/.", @working_dir_tmp_cert_path) end def ensure_configured! raise 'Please configure an instance first!' unless [name, release, network].all? end def setup_omnibus @docker.write_files(name) do |f| f.write('/etc/gitlab/gitlab.rb', @omnibus_configuration.to_s) end end def seed_test_data_command cmd = [] Runtime::Scenario.seed_db.each do |file_patterns| Dir["#{DATA_SEED_PATH}/#{file_patterns}"].map { |f| File.basename f }.each do |file| cmd << "gitlab-rails runner #{DATA_PATH}/#{file}" end end cmd.uniq end def seed_admin_token_command ["gitlab-rails runner #{DATA_PATH}/admin_access_token_seed.rb"] end class Availability def initialize(name, relative_path: '', scheme: 'http', protocol_port: 80) @docker = Docker::Engine.new host = @docker.hostname port = @docker.port(name, protocol_port).split(':').last @uri = URI.join("#{scheme}://#{host}:#{port}", "#{relative_path}/", 'help') end def check(retries) print "Waiting for GitLab at `#{@uri}` to become available " retries.times do return true if service_available? print '.' sleep 1 end false end private def service_available? response = Net::HTTP.start(@uri.host, @uri.port, opts) do |http| http.head2(@uri.request_uri) end print response.code response.code.to_i == 200 rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError => e print e.message false end def opts @uri.scheme == 'https' ? { use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE } : {} end end end end end end # rubocop:enable Metrics/AbcSize