#!/bin/env oo-ruby

#--
# Copyright 2012 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++

# Serialize and coalesce requests to reload/restart Apache.
#
# oo-httpd-singular will deliver either the reload or restart command
# to Apache.  As simultaneous and multiple calls can become extremely
# expensive; the requests are serialized behind a lock file.  Multiple
# requests; typically queuing up while one request is running will be
# coalesced into a single restart or reload.

require 'rubygems'
require 'daemons'
require 'optparse'
require 'open4'
require 'thread'
require 'tempfile'
require 'json'
require 'systemu'

HTTPD_SYSTEM="apachectl"
HTTPD_CMDS=["graceful"]
HTTPD_PRE_CMDS=["configtest"]
LOCKFILE=File.join(Dir::tmpdir,"httpd_singular.lock")
REQPREFIX="httpd_singular.req"

def single_instance(&block)
  File.open(LOCKFILE, File::CREAT|File::TRUNC|File::RDWR, 0640) do |f|
    f.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
    begin
      f.flock(File::LOCK_EX)
      block.call
    ensure
      f.flock(File::LOCK_UN)
    end
  end
end

 
def load_reqset(reqset=[])
  reqfilter="#{REQPREFIX}.#{ARGV[0]}."

  Dir.foreach(Dir::tmpdir) do |dent|
    next if dent[0,reqfilter.length] != reqfilter
    fpath=File.join(Dir::tmpdir,dent)
    next if File.size(fpath) != 0
    reqset << fpath
  end
  reqset.uniq
end


$OPTIONS = {}
OptionParser.new do |opts|
  opts.banner = "\nUsage: #{$0} [-b] [#{HTTPD_CMDS.join('|')}]\n" +
    "\nExample: #{$0} graceful\n"

  $OPTIONS[:background] = false
  opts.on('-b', '--[no-]background', 'Run in the background and ignore return status.') do |bg|
    $OPTIONS[:background] = bg
  end

  opts.on('-h', '--help', 'Show this message') do
    puts opts
    exit
  end
end.parse!


if ! HTTPD_CMDS.include?(ARGV[0])
  warn("Command must be one of: #{HTTPD_CMDS.join(' ')}")
  exit(false)
end

# Create the request
reqfile = Tempfile.new("#{REQPREFIX}.#{ARGV[0]}.XXXXXX")
reqfile.sync = true

if $OPTIONS[:background]
  Daemons.daemonize
end

# Process all requests and get our output back
rc=0
single_instance do
  
  reqset = load_reqset()

  if reqset.length != 0
    out=""
    err=""
    rc=0
    [HTTPD_PRE_CMDS, ARGV[0]].flatten.each do |cmd|
      status, out, err =  systemu "#{HTTPD_SYSTEM} #{cmd}"
      if status.exitstatus != 0
        rc = status.exitstatus
        break
      end
    end

    # Wait up to 1 minute for Apache to finish reloading.
    systemu "/usr/bin/curl -m 60 -k https://127.0.0.1/"

    reqset.each do |fpath|
      File.open(fpath,'w') { |f|
        f.write( { "RC"=> rc,
                   "STDOUT"=> out,
                   "STDERR"=> err }.to_json )
        f.flush()
      }
    end
  end

  reqfile.seek(0, IO::SEEK_SET)
  res = JSON.parse(reqfile.read)
  reqfile.unlink()
  reqfile.close()

  if not $OPTIONS[:background]
    $stderr.write(res["STDERR"])
    $stdout.write(res["STDOUT"])
    rc = res["RC"]
  end
end # single_instance

exit(rc)
