#!/usr/bin/env ruby

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you 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.
#

$top_srcdir = File::expand_path(File.join(File.dirname(__FILE__), '..'))

require 'rubygems'
require 'optparse'
require 'yaml'

# See if we can require +name+ and return +true+ if the library is there,
# +false+ otherwise. Note that, as a side effect, the library will be
# loaded
def library_present?(name)
  begin
    require name
    true
  rescue LoadError
    false
  end
end

DEFAULT_CONFIG = "~/.deltacloud/config"

options = {
  :env => 'development',
  :logdir => "/var/log/deltacloud-core"
}
optparse = OptionParser.new do |opts|

opts.banner = <<BANNER
Usage:
deltacloudd -i <driver> [options]

Options:
BANNER
  opts.on( '-i', '--driver DRIVER', 'Driver to use') do |driver|
    ENV["API_DRIVER"] = driver
  end
  opts.on( '-r', '--hostname HOSTNAME',
           'Bind to HOST address (default: localhost)') do |host|
    ENV["API_HOST"] = host
  end
  opts.on( '-p', '--port PORT', 'Use PORT (default: 3001)') do |port|
    ENV["API_PORT"] = port
  end
  opts.on( '-P', '--provider PROVIDER', 'Use PROVIDER (default is set in the driver)') do |provider|
    ENV['API_PROVIDER'] = provider
  end
  opts.on('-f', '--frontends FRONTENDS', 'Enable different frontends (cimi, ec2, deltacloud)') do |frontend|
    ENV['API_FRONTEND'] = frontend
  end
  opts.on( '-c', '--config [FILE]', 'Read provider and other config from FILE (default: ~/.deltacloud/config)') do |config|
    options[:config] = File::expand_path(config || DEFAULT_CONFIG)
  end
  opts.on( '-e', '--env ENV', 'Environment (default: "development")') { |env| options[:env] = env }
  opts.on( '-d', '--daemon', 'Run daemonized in the background, logging to SYSLOG (default: "disabled")') do
    options[:daemon] = true
  end
  opts.on( '-u', '--user USER', 'User to run daemon as. Use with -d (default: "nobody")') { |user| options[:user] = user }
  opts.on( '-g', '--group GROUP', 'Group to run daemon as. Use with -d (default: "nobody")') { |group| options[:group] = group }
  opts.on( '-b', '--pid PID', 'File to store PID (default: tmp/pids/thin.pid)') { |pid| options[:pid] = pid }
  opts.on( '-l', '--drivers', 'List available drivers') { |env| options[:drivers] = true }
  opts.on( '-L', '--log FILE', 'Log requests to a file (default: disabled)') { |log| ENV['API_LOG'] = log }
  opts.on( '-s', '--ssl', 'Enable SSL (default: disabled)') { |ssl| options[:ssl] = true }
  opts.on( '-k', '--ssl-key KEY', 'SSL key file to use') { |key| options[:ssl_key] = key }
  opts.on( '-C', '--ssl-cert CERT', 'SSL certificate file to use') { |cert| options[:ssl_cert] = cert }
  opts.on( '-t', '--timeout TIMEOUT', 'Timeout for single request (default: 60)') do |timeout|
    ENV["API_TIMEOUT"] = timeout
  end
  opts.on( '-V', '--verbose', 'Set verbose logging on') do |verbose|
    ENV["API_VERBOSE"] ||= 'true'
  end
  opts.on(  nil, '--webrick', 'Force the use of WEBRick as a server') do |opt|
    options[:webrick] = true
  end
  opts.on('--logdir LOGDIR', "Directory for log files, defaults to #{options[:logdir]}") do |opt|
    options[:logdir] = opt
  end
  opts.on( '-h', '--help', '') { options[:help] = true }

  opts.separator <<EOS

Config file:

  Server configuration can be specified in a YAML file; the file must
  contain a hash, where the keys are driver names; each driver entry is
  also a hash. Possible keys are
    :provider - the provider to use for this driver
    :user     - the user name for this driver
    :password - the password for this driver

  Note that specifying :user and :password turns off authentication on the
  server, and any request is forwarded to the backend cloud with the
  specified credentials.

  Note, for SSL you need to have API_SSL_KEY and API_SSL_CERT environment
  variables set.
EOS
end

optparse.parse!

if options[:help]
 puts optparse
 exit(0)
end

unless options[:drivers] or ENV["API_DRIVER"]
  puts "You need to specify a driver to use (-i <driver>)"
  puts "To list all available drivers try: deltacloudd --drivers"
  exit(1)
end

if options[:drivers]
  $:.unshift File.join($top_srcdir, 'lib')
  server_dir = ENV['API_FRONTEND'] == 'cimi' ? 'cimi' : 'deltacloud'
  load File.join($top_srcdir, 'lib', server_dir, 'api.rb')
  puts "Available drivers:\n\n"
  puts Deltacloud.drivers.keys.join("\n")
  puts
  exit(0)
end


if options[:ssl]
  unless options[:ssl_key]
    puts "You need to set SSL key using '-k /path/to/keyfile.key'"
    exit(1)
  end
  unless options[:ssl_cert]
    puts "You need to set SSL certificate using '-C /path/to/certificate.crt'"
    exit(1)
  end
end

if options[:config]
  cfg = YAML::load(File.read(options[:config]))
  if cfg.keys.any? { |k| k.is_a?(Symbol) }
    puts "The config file #{options[:config]} uses symbols as keys"
    puts "  Change them to be ordinary strings"
    exit(1)
  end
  if c = cfg[ENV["API_DRIVER"]]
    ENV["API_PROVIDER"] ||= c["provider"]
    ENV["API_USER"] ||= c["user"]
    ENV["API_PASSWORD"] ||= c["password"]
  end
end

ENV["API_HOST"] = "localhost" unless ENV["API_HOST"]
ENV["API_PORT"] = "3001" unless ENV["API_PORT"]

unless options[:daemon]
  msg = "Starting Deltacloud API :: #{ENV["API_DRIVER"]} "
  msg << ":: #{ENV['API_PROVIDER']} " if ENV['API_PROVIDER']
  api_uri = ENV['API_FRONTEND'] == 'cimi' ? 'cimi/cloudEntryPoint' : 'api'
  if options[:ssl]
    msg << ":: https://#{ENV["API_HOST"]}:#{ENV["API_PORT"]}/#{api_uri}"
  else
    msg << ":: http://#{ENV["API_HOST"]}:#{ENV["API_PORT"]}/#{api_uri}"
  end
  puts msg
  puts
end

if ENV['API_USER'] && ENV['API_PASSWORD']
  puts "Warning: API_USER and API_PASSWORD set in environment"
  puts "         anybody can access this server with your credentials"
  puts
end

have_thin = options[:webrick].nil? && library_present?('thin')
have_rerun = library_present?('rerun')

unless have_thin
  require 'rack'

  # Read in config.ru and convert it to an instance of Rack::Builder
  cfgfile = File.read(File.join($top_srcdir, 'config.ru'))
  inner_app = eval("Rack::Builder.new {(" + cfgfile + "\n )}.to_app",
                   nil, 'config.ru')

  app = Rack::Builder.new {
    use Rack::CommonLogger # apache-like logging
    use Rack::Reloader if options[:env] == "development"
    unless RUBY_PLATFORM == 'java'
      set :root, $top_srcdir # Set Sinatra root since we can't chdir to ../
    end
    run inner_app
  }.to_app

  # There's a bug with string ports on JRuby so convert to int
  # http://jira.codehaus.org/browse/JRUBY-4868
  port = ENV["API_PORT"].to_i

  puts "=> Ctrl-C to shutdown server"
  Rack::Server::start(:app => app,
                      :Host => ENV["API_HOST"],
                      :Port => port,
                      :AccessLog => [])
else
  argv_opts = ARGV.clone
  argv_opts << ['start'] unless Thin::Runner.commands.include?(options[0])
  argv_opts << ['--address', ENV["API_HOST"] ]
  argv_opts << ['--port', ENV["API_PORT"] ]
  argv_opts << ['--rackup', File.join($top_srcdir, 'config.ru') ]
  argv_opts << ['-e', options[:env] ]
  argv_opts << ['--timeout', ENV["API_TIMEOUT"] || '60']
  argv_opts << ['--threaded', '-D' ]
  if options[:ssl]
    argv_opts << [ '--ssl', '--ssl-key-file', options[:ssl_key], '--ssl-cert-file', options[:ssl_cert]]
  end

  if options[:daemon]
    options[:env] = "production"
    argv_opts << [ "--daemonize", "--user", options[:user] || 'nobody', "--tag", "deltacloud-#{ENV['API_DRIVER']}"]
    argv_opts << [ "--pid", options[:pid]] if options[:pid]
    argv_opts << [ "--group", options[:group] || 'nobody' ]
    argv_opts << [ "--log", File.join(options[:logdir], "#{ENV['API_DRIVER']}.log")]
  end
  argv_opts.flatten!

  if have_rerun && options[:env] == "development"
    argv_opts.unshift "thin"
    command = argv_opts.join(" ")
    Dir::chdir($top_srcdir)
    rerun = Rerun::Runner.new(command,
      :dir => File::join($top_srcdir, "lib"))
    rerun.start
    rerun.join
  else
    thin = Thin::Runner.new(argv_opts)
    thin.run!
  end
end
