#!/usr/bin/env ruby

require 'rhc/coverage_helper'

require 'rhc-common'
require 'base64'

if ["add-alias", "remove-alias"].include? ARGV[0]
  RHC::Helpers.deprecated_command('rhc alias (add|remove)',true)
elsif "snapshot" == ARGV[0]
  RHC::Helpers.deprecated_command('rhc snapshot (save|restore)',true)
elsif "tail" == ARGV[0]
  RHC::Helpers.deprecated_command('rhc tail',true)
elsif "cartridge" == ARGV[0]
  RHC::Helpers.deprecated_command('rhc cartridge (add|remove|status|stop|start|restart|reload)',true)
else
  RHC::Helpers.deprecated_command('rhc app (create|delete|show|stop|force-stop|start|restart|reload|tidy)',true)
end

$embed_mapper = { 'add' => 'configure', 'remove' => 'deconfigure' }

#
# print help
#
def p_usage(exit_code = 255)
    libra_server = get_var('libra_server')
    rhlogin = get_var('default_rhlogin') ? "Default: #{get_var('default_rhlogin')}" : "required"
    type_keys = RHC::get_cartridge_listing(nil, ', ', libra_server, RHC::Config.default_proxy, 'standalone', false)
    puts <<USAGE

Usage: rhc app (<command> | cartridge <cartridge-action> | --help) [<args>]
Create and manage an OpenShift application.

List of commands
  create                         Create a new application on OpenShift
  show                           Display information about a user
  start                          Starts the application (includes all cartridges)
  stop                           Stops the application (includes all cartridges)
  force-stop                     Stops all application processes
  restart                        Restart the application
  reload                         Reloads application configuration
  status                         Returns application status
  destroy                        Destroys the application
  tidy                           Garbage collects the git repo and empties log/tmp dirs
  add-alias                      Add a custom domain name for the application
  remove-alias                   Remove a custom domain name for the application
  tail                           Tail the logs of an application
  snapshot       [save|restore]  Saves/Restores an application snapshot to/from a tarball at the location specified using --filepath (default: ./$APPNAME.tar.gz)
  cartridge      <action>        Manage a cartridge running in this application

List of cartridge actions
  list                           List of supported embedded cartridges
  add                            Add a cartridge to this application
  remove                         Remove a cartridge from this application
  stop                           Stop a cartridge
  start                          Start a cartridge
  restart                        Restart a cartridge
  status                         Returns cartridge status
  reload                         Reloads cartridge configuration

List of arguments
  -l|--rhlogin      rhlogin      OpenShift login (#{rhlogin})
  -p|--password     password     Password (optional, will prompt)
  -a|--app          application  Application name  (alphanumeric - max #{RHC::APP_NAME_MAX_LENGTH} chars) (required)
  -t|--type         type         Type of app to create (#{type_keys}) (required for creating an application)
  -c|--cartridge    cartridge    The embedded cartrige to manage (required for the cartridge command)
  -g|--gear-size    size         The size of the gear for this app ([small|medium], defaults to small)
  -s|--scaling                   Enable scaling for this app
  -r|--repo         path         Git Repo path (defaults to ./$app_name)
  -n|--nogit                     Only create remote space, don't pull it locally
  --no-dns                       Skip DNS check. Must be used in combination with --nogit
  -d|--debug                     Print Debug info
  -h|--help                      Show Usage info
  -b|--bypass                    Bypass warnings (applicable to application destroying only)
  -f|--filepath     filepath     Applicable in case of snapshot and log command
  -o|--opts         options      Options to pass to the server-side (linux based) tail command (applicable to tail command only) (-f is implicit.  See the linux tail man page full list of options.) (Ex: --opts '-n 100')
  --alias           alias        Specify server alias (when using add/remove-alias)
  --config          path         Path of alternate config file
  --timeout         #            Timeout, in seconds, for the session
  --enable-jenkins  [name]       Enables builds for your application with Jenkins.  You may optionally specify the name of the Jenkins application that is created (default: 'jenkins').  Note that --no-dns is ignored for the creation of the Jenkins application.
USAGE
exit exit_code
end


def validate_args(val_type=true, val_cartridge=false, val_timeout=true)

  # If provided a config path, check it
  RHC::Config.check_cpath($opt)
  
  # Pull in configs from files
  $libra_server = get_var('libra_server')
  debug = get_var('debug') == 'false' ? nil : get_var('debug')
  
  $opt['rhlogin'] = get_var('default_rhlogin') unless $opt['rhlogin']
  p_usage if !RHC::check_rhlogin($opt['rhlogin'])
  
  p_usage if !RHC::check_app($opt['app'])

  if val_type && !$opt['type']
    puts "Application Type is required"
    p_usage
  end
  
  if val_cartridge && !$opt['cartridge']
    puts "Cartridge name is required"
    p_usage
  end
  
  debug = true if $opt.has_key? 'debug'
  RHC::debug(debug)
  
  RHC::timeout($opt["timeout"], get_var('timeout')) if val_timeout
  RHC::connect_timeout($opt["timeout"], get_var('timeout')) if val_timeout

  $password = $opt['password'] ? $opt['password'] : RHC::get_password
end

def create_app
  validate_args
  
  user_info = RHC::get_user_info($libra_server, $opt['rhlogin'], $password, RHC::Config.default_proxy, false)
  app_info = user_info['app_info']
  
  if app_info[$opt['app']]
    puts "An application named '#{$opt['app']}' in namespace '#{user_info['user_info']['domains'][0]['namespace']}' already exists"
    exit 255
  end
  
  jenkins_app_name = nil
  has_jenkins = false
  if $opt['enable-jenkins']
    app_info.each do |app_name, app|
      if app['framework'] == 'jenkins-1.4'
        jenkins_app_name = app_name
        has_jenkins = true
        puts "
Found existing Jenkins application: #{jenkins_app_name}
"
        if !$opt['enable-jenkins'].empty?
          puts "Ignoring specified Jenkins app name: #{$opt['enable-jenkins']}"
        end
      end
    end
    if !has_jenkins
      if $opt['type'] =~ /^jenkins-/
        has_jenkins = true
        if $opt['no-dns']
          puts "
The --no-dns option can't be used in conjunction with --enable-jenkins 
when creating a #{$opt['type']} application.  Either remove the --no-dns
option or first install your #{$opt['type']} application with --no-dns
and then use 'rhc app cartridge add' to embed the Jenkins client. 
"
          exit 255
        end
        jenkins_app_name = $opt['app']
        puts "
The Jenkins client will be embedded into the Jenkins application 
currently being created: '#{$opt['app']}'
"
      end
    end
    if !has_jenkins
      if !$opt['enable-jenkins'].empty?
        jenkins_app_name = $opt['enable-jenkins']
      else
        jenkins_app_name = 'jenkins'
      end
    
      if !RHC::check_app(jenkins_app_name)
          p_usage
      end
  
      if jenkins_app_name == $opt['app']
        puts "You must specify a different name for your application and Jenkins ('#{$opt['app']}')."
        exit 100
      end
    
      if app_info.has_key?(jenkins_app_name)
        puts "You already have an application named '#{jenkins_app_name}'."
        puts "In order to continue you'll need to specify a different name"
        puts "with --enable-jenkins or destroy the existing application."
        exit 100
      end
    end
  end

  $opt['gear-size']='small' unless $opt['gear-size']
  
  $opt['repo'] = $opt['app'] unless $opt['repo']
  
  if @mydebug
    puts "
    Found a bug? Post to the forum and we'll get right on it.
        IRC: #openshift on freenode
        Forums: https://openshift.redhat.com/community/forums/openshift
    
    "
  end
  
  #
  # Confirm local git repo exists
  #
  unless $opt['nogit']
      if File.exists?($opt['repo'])
          puts "We will not overwrite an existing git repo. Please remove:"
          puts "  #{File.expand_path($opt['repo'])}"
          puts "Then try again."
          puts
          exit 210
      else
          begin
              # Create the parent directory for the git repo
              @git_parent = File.expand_path($opt['repo'] + "/../")
              FileUtils.mkdir_p(@git_parent)
          rescue Exception => e
              puts "Could not write to #{@git_parent}"
              puts "Reason: #{e.message}"
              puts
              puts "Please re-run from a directory you have write access to or specify -r with a"
              puts "path you have write access to"
              puts
              exit 211
          end
      end
  end
  
  if jenkins_app_name && !has_jenkins
    jenkins_app = RHC::create_app($libra_server, RHC::Config.default_proxy, user_info, jenkins_app_name, 'jenkins-1.4', $opt['rhlogin'], $password, nil, false, true, true)
    available = RHC::check_app_available(RHC::Config.default_proxy, jenkins_app[:app_name], jenkins_app[:fqdn], jenkins_app[:health_check_path], jenkins_app[:result], jenkins_app[:git_url], nil, true)
    if !available
      puts "Unable to access your new Jenkins application."
      exit 1
    end
  end
  
  #
  # Create remote application space
  #
  main_app = RHC::create_app($libra_server, RHC::Config.default_proxy, user_info, $opt['app'], $opt['type'], $opt['rhlogin'], $password, $opt['repo'], $opt['no-dns'], $opt['nogit'], false, $opt['gear-size'], $opt['scaling'])
  if jenkins_app_name
    puts "Now embedding the jenkins client into '#{$opt['app']}'..."
    RHC::ctl_app($libra_server, RHC::Config.default_proxy, $opt['app'], $opt['rhlogin'], $password, 'configure', true, 'jenkins-client-1.4', nil, false)
  end
  
  if $opt['no-dns']
    # In case of the no-dns option, the availability check is not made
    # This also results in the git information and app creation result not being displayed
    # Hence, for this case, we print out the git url and result here
    puts "git url: #{main_app[:git_url]}"
    if main_app[:result] && !main_app[:result].empty?
      puts "#{main_app[:result]}"
    end
  else
    available = RHC::check_app_available(RHC::Config.default_proxy, main_app[:app_name], main_app[:fqdn], main_app[:health_check_path], main_app[:result], main_app[:git_url], $opt['repo'], $opt['nogit'])
    if !available
      puts "Unable to access your new application."
      exit 1
    end
  end

end

def get_args(l)
  l.shift
  args = ""
  l.each do|a|
    if (a.to_s.strip.length == 0 || a.to_s.strip.match(/\s/) ); a = "'#{a}'" end    
    args += " #{a}"
  end
  args
end

def show_app
  validate_args(false)
  user_info = RHC::get_user_info($libra_server, $opt['rhlogin'], $password, RHC::Config.default_proxy, true)
  
  app_found = false
  unless user_info['app_info'].empty?
    user_info['app_info'].each do |key, val|
      if key == $opt['app']
        app_found = true
        puts ""
        puts "Application Info"
        puts "================"
        puts key
        puts "    Framework: #{val['framework']}"
        puts "     Creation: #{val['creation_time']}"
        puts "         UUID: #{val['uuid']}"
        puts "      Git URL: ssh://#{val['uuid']}@#{key}-#{user_info['user_info']['domains'][0]['namespace']}.#{user_info['user_info']['rhc_domain']}/~/git/#{key}.git/"
        puts "   Public URL: http://#{key}-#{user_info['user_info']['domains'][0]['namespace']}.#{user_info['user_info']['rhc_domain']}/"
        if val['aliases'] && !val['aliases'].empty?
          puts "      Aliases: #{val['aliases'].join(', ')}"
        end
        puts ""
        puts " Embedded: "
        if val['embedded'] && !val['embedded'].empty? 
            val['embedded'].each do |embed_key, embed_val|
                if embed_val.has_key?('info') && !embed_val['info'].empty?
                    puts "      #{embed_key} - #{embed_val['info']}"
                else
                    puts "      #{embed_key}"
                end
            end
        else
            puts "      None"
        end
        puts ""
        
        # there should be a single application with the given app name
        break
        
      end
    end
  end

  if !app_found
    puts
    puts "Could not find app '#{$opt['app']}'.  Please run 'rhc domain show' to get a list"
    puts "of your current running applications"
    puts
    exit 1
  end

end

def control_app(command)
  validate_args(false)
  
  if ($opt['alias'] and !(command =~ /-alias$/)) || (command =~ /-alias$/ and ! $opt['alias'])
    puts "When specifying alias make sure to use add-alias or remove-alias command"
    p_usage
  end

  command = "deconfigure" if command == "destroy"
  
  if !$opt["bypass"] and command == "deconfigure"
    # deconfigure is the actual hook called on 'destroy'
    # destroy is used for clarity
    puts <<WARNING
!!!! WARNING !!!! WARNING !!!! WARNING !!!!
You are about to destroy the #{$opt['app']} application.

This is NOT reversible, all remote data for this application will be removed.
WARNING

    print "Do you want to destroy this application (y/n): "
    begin
      agree = gets.chomp
      if agree != 'y'
          puts "\n"
          exit 217
      end
    rescue Interrupt
      puts "\n"
      exit 217
    end
  end

  RHC::ctl_app($libra_server, RHC::Config.default_proxy, $opt['app'], $opt['rhlogin'], $password, command, false, nil, $opt['alias'])
end

def control_cartridge(command)
  validate_args(false, true)
  
  # override command if it's in the mapper
  command = $embed_mapper[command] if $embed_mapper[command]
  framework = $opt['cartridge']
  
  RHC::ctl_app($libra_server, RHC::Config.default_proxy, $opt['app'], $opt['rhlogin'], $password, command, true, framework, $opt['alias'])
end

def show_embedded_list
    libra_server = get_var('libra_server')
    puts ""
    puts "List of supported embedded cartridges:"
    puts ""
    type_keys = RHC::get_cartridge_listing(nil, ', ', libra_server, RHC::Config.default_proxy, 'embedded', false)
    puts type_keys
    puts ""

    # we should always get something back unless there was an error
    exit 255 if type_keys.length == 0
    exit 0
end

def save_or_restore_snapshot(command)
  validate_args(false, false, true)

  user_info = RHC::get_user_info($libra_server, $opt['rhlogin'], $password, RHC::Config.default_proxy, @mydebug, false)
  
  app = $opt['app']
  $opt['filepath'] = "#{$opt['app']}.tar.gz" unless $opt['filepath']
  
  unless user_info['app_info'][app]
      puts
      puts "Could not find app '#{app}'.  Please run 'rhc domain show' to get a list"
      puts "of your current running applications"
      puts
      exit 101
  end
  
  app_uuid = user_info['app_info'][app]['uuid']
  namespace = user_info['user_info']['domains'][0]['namespace']
  rhc_domain = user_info['user_info']['rhc_domain']
  
  if command == 'save'
    status = RHC.snapshot_create rhc_domain, namespace, app, app_uuid, $opt['filepath'], @mydebug
    exit status if ! status
  else
    status = RHC.snapshot_restore rhc_domain, namespace, app, app_uuid, $opt['filepath'], @mydebug
    exit status if ! status
  end

end

begin
  argv_c = ARGV.clone
  
  if ARGV[0] =~ /^create$/
    ARGV.shift
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help",  "-h", GetoptLong::NO_ARGUMENT],
        ["--rhlogin", "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--gear-size", "-g", GetoptLong::REQUIRED_ARGUMENT],
        ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--no-dns", GetoptLong::NO_ARGUMENT],
        ["--nogit", "-n", GetoptLong::NO_ARGUMENT],
        ["--app",   "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--repo",  "-r", GetoptLong::REQUIRED_ARGUMENT],
        ["--type",  "-t", GetoptLong::REQUIRED_ARGUMENT],
        ["--enable-jenkins", GetoptLong::OPTIONAL_ARGUMENT],
        ["--scaling", "-s", GetoptLong::OPTIONAL_ARGUMENT],
        ["--config", GetoptLong::REQUIRED_ARGUMENT],
        ["--timeout", GetoptLong::REQUIRED_ARGUMENT]
    )
  elsif ARGV[0] =~ /^(show|start|stop|force-stop|restart|reload|status|destroy|tidy|add-alias|remove-alias|destroy)$/
    ARGV.shift
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help",  "-h", GetoptLong::NO_ARGUMENT],
        ["--rhlogin", "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--app",   "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--alias", GetoptLong::REQUIRED_ARGUMENT],
        ["--bypass", "-b", GetoptLong::NO_ARGUMENT],
        ["--config", GetoptLong::REQUIRED_ARGUMENT],
        ["--timeout", GetoptLong::REQUIRED_ARGUMENT]
    )
  elsif ARGV[0] =~ /^cartridge$/
    ARGV.shift
    ARGV.shift if ARGV[0] =~ /^(add|remove|stop|start|restart|status|reload|list)$/
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help", "-h", GetoptLong::NO_ARGUMENT],
        ["--rhlogin", "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--app", "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--cartridge", "-c", GetoptLong::REQUIRED_ARGUMENT],
        ["--config", GetoptLong::REQUIRED_ARGUMENT],
        ["--timeout", GetoptLong::REQUIRED_ARGUMENT]
    )
  elsif ARGV[0] =~ /^tail$/
    ARGV.shift
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help", "-h", GetoptLong::NO_ARGUMENT],
        ["--rhlogin", "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--app", "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--opts", "-o", GetoptLong::REQUIRED_ARGUMENT],
        ["--filepath", "-f", GetoptLong::REQUIRED_ARGUMENT],
        ["--config", GetoptLong::REQUIRED_ARGUMENT],
        ["--timeout", GetoptLong::REQUIRED_ARGUMENT]
    )
  elsif ARGV[0] =~ /^snapshot$/
    ARGV.shift
    ARGV.shift if ARGV[0] =~ /^(save|restore)$/
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help", "-h", GetoptLong::NO_ARGUMENT],
        ["--rhlogin", "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--app", "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--filepath", "-f", GetoptLong::REQUIRED_ARGUMENT],
        ["--config", GetoptLong::REQUIRED_ARGUMENT],
        ["--timeout", GetoptLong::REQUIRED_ARGUMENT]
    )
  else
    opts = GetoptLong.new(
      ["--help",  "-h", GetoptLong::NO_ARGUMENT]
    )
    unless ARGV[0] =~ /^(help|-h|--help)$/
      puts "Missing or invalid command!" unless ARGV[0] =~ /^(help|-h|--help)$/
      #  just exit at this point
      # printing the usage description will be handled in the rescue
      exit 255
    end
  end

  $opt = {}
  opts.each do |o, a|
    $opt[o[2..-1]] = a.to_s
  end

rescue Exception => e
  p_usage
end

p_usage 0 if $opt["help"]

case argv_c[0]
  when "create"
    create_app
  when "show"
    show_app
  when "start", "stop", "force-stop", "restart", "reload", "status", "tidy", "add-alias", "remove-alias", "destroy"
    control_app(argv_c[0])
  when "tail"
    system("rhc-tail-files #{get_args argv_c} 2>&1")
    exit $?.exitstatus
  when "snapshot"
    case argv_c[1]
    when "save", "restore"
      save_or_restore_snapshot(argv_c[1])
    else
      puts "Missing or invalid snapshot action!"
      p_usage
    end
  when "cartridge"
    case argv_c[1]
    when "add", "remove", "start", "stop", "restart", "status", "reload"
      control_cartridge(argv_c[1])
    when "list", nil
      show_embedded_list
    else
      puts "Missing or invalid cartridge action!"
      p_usage
    end
  when "-h", "--help", "help", nil
    p_usage 0
  else
    puts "Invalid command!"
    p_usage
end

exit 0
