class Gitlab::QA::Component::Base

def add_network_alias(name)

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

def assert_name!

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

def hostname

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

def image

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 initialize

def initialize
  @docker = Docker::Engine.new
  @logger = Runtime::Logger.logger
  @environment = {}
  @volumes = {}
  @ports = []
  @network_aliases = []
  @exec_commands = []
  @additional_hosts = []
  @secrets = []
end

def instance(skip_teardown: false)

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

def instance_no_teardown # rubocop:disable Metrics/AbcSize

rubocop:disable Metrics/AbcSize
def instance_no_teardown # rubocop:disable Metrics/AbcSize
  begin
    retries ||= 0
    prepare
    start
    reconfigure
    wait_until_ready
    process_exec_commands
  rescue Support::ShellCommand::StatusError => e
    reconfigure_log_file = get_reconfigure_log_file_from_artefact
    # for scenarios where a service fails during startup, attempt to retry to avoid flaky failures
    if (retries += 1) < 3
      unless reconfigure_log_file.nil?
        Runtime::Logger.info(
          "Follow the document " \
          "https://gitlab.com/gitlab-org/quality/runbooks/-/blob/main/debug_orchestrated_test_locally/ " \
          "for debugging the test failure locally.")
        # Tailing the reconfigure logs after retries are over and before raising exception
        Runtime::Logger.info("Tail of the reconfigure log file, see artifacts for full log: #{reconfigure_log_file}")
        Support::ShellCommand.new("tail -n 100 #{reconfigure_log_file}", stream_output: true).execute!
      end
      Runtime::Logger.warn(
        "Retry instance_no_teardown due to Support::ShellCommand::StatusError -- attempt #{retries}"
      )
      teardown!
      retry
    end
    # Printing logs to stdout for last retry failure
    if !reconfigure_log_file.nil? && retries == 3
      # Tailing the reconfigure logs after retries are over and before raising exception
      Runtime::Logger.info("Tail of the reconfigure log file, see artifacts for full log: #{reconfigure_log_file}")
      Support::ShellCommand.new("tail -n 100 #{reconfigure_log_file}", stream_output: true).execute!
    end
    raise e
  end
  yield self if block_given?
end

def ip_address

def ip_address
  docker.inspect(name) { |command| command << "-f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'" }
end

def log_volume

def log_volume
  @log_volume ||= {
    src: File.join(Runtime::Env.host_artifacts_dir, name, 'logs'),
    dest: '/var/log/gitlab'
  }
end

def name

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

def prepare

def prepare
  prepare_docker_image
  prepare_docker_container
  prepare_network
end

def prepare_airgapped_network

def prepare_airgapped_network
  return unless airgapped_network && !docker.network_exists?(network)
  docker.network_create("--driver=bridge --internal #{network}")
end

def prepare_docker_container

def prepare_docker_container
  return unless docker.container_exists?(name)
  docker.remove(name)
end

def prepare_docker_image

def prepare_docker_image
  pull
end

def prepare_network

def prepare_network
  prepare_airgapped_network
  prepare_runner_network
  return if docker.network_exists?(network)
  docker.network_create(network)
end

def prepare_runner_network

def prepare_runner_network
  return unless runner_network && !docker.network_exists?(runner_network)
  docker.network_create("--driver=bridge --internal #{runner_network}")
end

def process_exec_commands

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

def pull

def pull
  return if Runtime::Env.skip_pull?
  docker.pull(image: image, tag: tag)
end

def restart

def restart
  assert_name!
  docker.restart(name)
end

def start # rubocop:disable Metrics/AbcSize

rubocop:disable Metrics/AbcSize
def start # rubocop:disable Metrics/AbcSize
  docker.run(image: image, tag: tag, mask_secrets: secrets) do |command|
    command << "-d"
    command << "--name #{name}"
    command << "--net #{network}"
    command << "--hostname #{hostname}"
    @ports.each do |mapping|
      command.port(mapping)
    end
    @volumes.to_h.each do |to, from|
      command.volume(to, from, 'Z')
    end
    command.volume(*log_volume.values) unless log_volume.empty?
    @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
    @additional_hosts.each do |host|
      command << "--add-host=#{host}"
    end
  end
end

def start_instance

def start_instance
  instance_no_teardown
end

def tag

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 teardown

def teardown
  unless teardown?
    Runtime::Logger.info("The orchestrated docker containers have not been removed.")
    docker.ps
    return
  end
  teardown!
end

def teardown!

def teardown!
  assert_name!
  return unless docker.running?(name)
  docker.remove(name)
end

def teardown?

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