lib/gitlab/qa/component/base.rb



module Gitlab
  module QA
    module Component
      class Base
        include Scenario::Actable

        attr_reader :docker
        attr_accessor :volumes, :network, :environment, :runner_network
        attr_writer :name, :exec_commands

        def initialize
          @docker = Docker::Engine.new
          @environment = {}
          @volumes = {}
          @network_aliases = []

          self.exec_commands = []
        end

        def add_network_alias(name)
          @network_aliases.push(name)
        end

        def name
          raise NotImplementedError, "#{self.class.name} must specify a default name"
        end

        def hostname
          "#{name}.#{network}"
        end

        def image
          return self.class.const_get('DOCKER_IMAGE') if self.class.const_defined?('DOCKER_IMAGE')

          raise NotImplementedError, "#{self.class.name} must specify a docker image as DOCKER_IMAGE"
        end

        def tag
          return self.class.const_get('DOCKER_IMAGE_TAG') if self.class.const_defined?('DOCKER_IMAGE_TAG')

          raise NotImplementedError, "#{self.class.name} must specify a docker image tag as DOCKER_IMAGE_TAG"
        end

        def instance(skip_teardown: false)
          instance_no_teardown do
            yield self if block_given?
          end
        ensure
          teardown unless skip_teardown
        end

        alias_method :launch_and_teardown_instance, :instance

        def prepare
          prepare_docker_image
          prepare_docker_container
          prepare_network
        end

        def prepare_docker_image
          pull
        end

        def prepare_network
          if runner_network && !docker.network_exists?(runner_network) # rubocop:disable Style/IfUnlessModifier
            docker.network_create("--driver=bridge --internal #{runner_network}")
          end

          return if docker.network_exists?(network)

          docker.network_create(network)
        end

        def prepare_docker_container
          return unless docker.container_exists?(name)

          docker.remove(name)
        end

        def start # rubocop:disable Metrics/AbcSize
          docker.run(image: image, tag: tag) do |command|
            command << "-d"
            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
        end

        def restart
          assert_name!

          docker.restart(name)
        end

        def teardown
          unless teardown?
            puts "The orchestrated docker containers have not been removed."
            docker.ps

            return
          end

          teardown!
        end

        def teardown!
          assert_name!

          return unless docker.running?(name)

          docker.stop(name)
          docker.remove(name)
        end

        def pull
          return if Runtime::Env.skip_pull?

          docker.pull(image: image, tag: tag)
        end

        def process_exec_commands
          exec_commands.each { |command| docker.exec(name, command) }
        end

        private

        attr_reader :exec_commands, :wait_until_ready, :reconfigure

        def assert_name!
          raise 'Invalid instance name!' unless name
        end

        def instance_no_teardown
          prepare
          start
          reconfigure
          wait_until_ready
          process_exec_commands

          yield self if block_given?
        end

        def teardown?
          !Runtime::Scenario.attributes.include?(:teardown) || Runtime::Scenario.teardown
        end
      end
    end
  end
end