class PhpFpmDocker::Launcher

Represent a single docker image

Attributes

docker_image[R]
php_cmd_path[R]
spawn_cmd_path[R]

Public Class Methods

new(name) click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 14
def initialize(name) # rubocop:disable MethodLength
  @name = name

  # Create log dir if needed
  log_dir = Pathname.new('/var/log/php_fpm_docker')
  FileUtils.mkdir_p log_dir unless log_dir.directory?

  # Open logger
  @logger = Logger.new(log_dir.join("#{name}.log"), 'daily')
  @logger.info(to_s) { 'init' }

  test
end

Public Instance Methods

bind_mounts() click to toggle source

Get neccessary bind mounts

# File lib/php_fpm_docker/launcher.rb, line 164
def bind_mounts
  @ini_file[:main]['bind_mounts'].split(',') || []
end
check_pools() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 117
def check_pools
  'do nothing'
end
check_pools_n() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 121
def check_pools_n
  pools_action(@pools, @pools.keys, :check)
end
create_missing_pool_objects() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 77
def create_missing_pool_objects
  return if @pools.nil?
  return if @pools_old.nil?
  (@pools.keys & @pools_old.keys).each do |hash|
    @pools[hash][:object] = @pools_old[hash][:object]
  end
end
docker_opts() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 198
def docker_opts
  {
    'Image' => @docker_image.id
  }
end
fork_run() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 52
def fork_run
  Signal.trap('USR1') do
    @logger.info(to_s) { 'Signal USR1 received reloading now' }
    reload_pools
  end
  Signal.trap('TERM') do
    @logger.info(to_s) { 'Signal TERM received stopping me now' }
    stop_pools
    exit 0
  end
  Kernel.loop do
    check_pools
    sleep 1
  end
end
move_existing_pool_objects() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 85
def move_existing_pool_objects
  return if @pools.nil?
  @pools.keys.each do |hash|
    pool = @pools[hash]
    # skip if there's already an object
    next if pool.key?(:object)
    pool[:object] = Pool.new(
      config: pool[:config],
      name: pool[:name],
      launcher: self
    )
  end
end
parse_config() click to toggle source

Parse the config file for all pools

# File lib/php_fpm_docker/launcher.rb, line 174
def parse_config # rubocop:disable MethodLength
  # Test for file usability
  fail "Config file '#{@config_path}' not found"\
  unless @config_path.file?
  fail "Config file '#{@config_path}' not readable"\
  unless @config_path.readable?

  @ini_file = IniFile.load(@config_path)

  begin
    docker_image = @ini_file[:main]['docker_image']
    @docker_image = Docker::Image.get(docker_image)
    @logger.info(to_s) do
      "Docker image id=#{@docker_image.id[0..11]} name=#{docker_image}"
    end
  rescue NoMethodError
    raise 'No docker_image in section main in config found'
  rescue Docker::Error::NotFoundError
    raise "Docker_image '#{docker_image}' not found"
  rescue Excon::Errors::SocketError => e
    raise "Docker connection could not be established: #{e.message}"
  end
end
pools_action(pools, pools_hashes, action) click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 125
def pools_action(pools, pools_hashes, action)
  message = ''
  if pools_hashes.length > 0
    message << "Pools to #{action}: "
    message <<  pools_hashes.map { |p| pools[p][:name] }.join(', ')
    pools_hashes.each do |pool_hash|
      pool = pools[pool_hash]
      begin
        pool[:object].send(action)
      rescue => e
        @logger.warn(pool[:object].to_s) do
          "Failed to #{action}: #{e.message}"
        end
      end
    end
  else
    message << "No pools to #{action}"
  end
  @logger.info(to_s) { message }
end
pools_config_content_from_file(config_path) click to toggle source

Reads config sections from a inifile

# File lib/php_fpm_docker/launcher.rb, line 205
def pools_config_content_from_file(config_path)
  ini_file = IniFile.load(config_path)

  ret_val = []
  ini_file.each_section do |section|
    ret_val << [section, ini_file[section]]
  end
  ret_val
end
pools_config_contents() click to toggle source

Merges config sections form all inifiles

# File lib/php_fpm_docker/launcher.rb, line 216
def pools_config_contents
  ret_val = []

  # Loop over
  Dir[@pools_directory.join('*.conf').to_s].each do |config_path|
    ret_val += pools_config_content_from_file(config_path)
  end
  ret_val
end
pools_from_config() click to toggle source

Hashes configs to detect changes

# File lib/php_fpm_docker/launcher.rb, line 227
def pools_from_config
  configs = {}

  pools_config_contents.each do |section|
    # Hash section name and content
    d = Digest::SHA2.new(256)
    hash = d.reset.update(section[0]).update(section[1].to_s).to_s

    configs[hash] = {
      name: section[0],
      config: section[1]
    }
  end
  configs
end
reload_pools(pools = nil) click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 99
def reload_pools(pools = nil)
  @pools_old = @pools
  if pools.nil?
    @pools = pools_from_config
  else
    @pools = pools
  end

  move_existing_pool_objects
  create_missing_pool_objects

  # Pools to stop
  pools_action(@pools_old, @pools_old.keys - @pools.keys, :stop)

  # Pools to start
  pools_action(@pools, @pools.keys - @pools_old.keys, :start)
end
run() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 42
def run
  start_pools

  pid = fork do
    fork_run
  end
  Process.detach(pid)
  pid
end
start_pools() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 68
def start_pools
  @pools = {}
  reload_pools
end
stop_pools() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 73
def stop_pools
  reload_pools({})
end
test() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 28
def test
  test_directories

  # Parse config
  parse_config

  # Test docker image
  test_docker_image

rescue RuntimeError => e
  @logger.fatal(to_s) { "Error while init: #{e.message}" }
  exit 1
end
test_directories() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 146
def test_directories
  # Get config dirs and paths
  @conf_directory = Pathname.new('/etc/php_fpm_docker/conf.d').join(@name)
  fail "Config directory '#{@conf_directory}' not found" \
  unless @conf_directory.directory?

  @pools_directory = @conf_directory.join('pools.d')
  fail "Pool directory '#{@pools_directory}' not found" \
  unless @pools_directory.directory?

  @config_path = @conf_directory.join('config.ini')
end
test_docker_cmd(cmd) click to toggle source

Docker init

# File lib/php_fpm_docker/launcher.rb, line 244
def test_docker_cmd(cmd) # rubocop:disable MethodLength
  # retry this block 3 times
  tries ||= 3

  opts = docker_opts
  opts['Cmd'] = cmd
  dict = {}

  # Set timeout
  Docker.options[:read_timeout] = 2

  cont = Docker::Container.create(opts)
  cont.start
  output = cont.attach
  dict[:ret_val] = cont.wait(5)['StatusCode']
  cont.delete(force: true)

  dict[:stdout] = output[0].first
  dict[:stderr] = output[1].first

  # Set timeout
  Docker.options[:read_timeout] = 15

  @logger.debug(to_s) do
    "cmd=#{cmd.join(' ')} ret_val=#{dict[:ret_val]}" \
    " stdout=#{dict[:stdout]} stderr=#{dict[:stderr]}"
  end

  dict
rescue Docker::Error::TimeoutError => e
  if (tries -= 1) > 0
    cont.delete(force: true) if cont.nil?
    @logger.debug(to_s) { 'ran into timeout retry' }
    retry
  end
  raise e
end
test_docker_image() click to toggle source

Testing the docker image if i can be used

# File lib/php_fpm_docker/launcher.rb, line 283
def test_docker_image # rubocop:disable MethodLength
  # Test possible php commands
  ['php-cgi', 'php5-cgi', 'php', 'php5'].each do |php_cmd|
    result = test_docker_cmd [:which, php_cmd]

    next unless result[:ret_val] == 0

    php_cmd_path = result[:stdout].strip

    result = test_docker_cmd [php_cmd_path, '-v']

    next unless result[:ret_val] == 0
    php_version_re = /PHP [A-Za-z0-9\.\-\_]+ \(cgi-fcgi\)/
    next if php_version_re.match(result[:stdout]).nil?

    @php_cmd_path = php_cmd_path
    break
  end
  fail 'No usable fast-cgi enabled php found in image' if @php_cmd_path.nil?

  # Test if spawn-fcgi exists
  result = test_docker_cmd [:which, 'spawn-fcgi']
  fail 'No usable spawn-fcgi found in image' unless result[:ret_val] == 0
  @spawn_cmd_path = result[:stdout].strip
end
to_s() click to toggle source
# File lib/php_fpm_docker/launcher.rb, line 159
def to_s
  "<Launcher:#{@name}>"
end
web_path() click to toggle source

Get webs base path

# File lib/php_fpm_docker/launcher.rb, line 169
def web_path
  Pathname.new(@ini_file[:main]['web_path'] || '/var/www')
end