#!/usr/bin/env ruby
# Copyright 2011 Red Hat, Inc.
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

require 'rhc-common'

def p_usage
    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, @http, 'standalone', false)
    puts <<USAGE

Usage: #{$0}
Create an OpenShift Express app.

  -a|--app   application     Application name  (alphanumeric - max #{RHC::Maxdlen} chars) (required)
  -t|--type  type            Type of app to create (#{type_keys}) (required)
  -l|--rhlogin  rhlogin      Red Hat login (RHN or OpenShift login with OpenShift Express access) (#{rhlogin})
  -p|--password  password    RHLogin password  (optional, will prompt)
  -r|--repo  path            Git Repo path (defaults to ./$app_name)
  -n|--nogit                 Only create remote space, don't pull it locally
  -d|--debug                 Print Debug info
  -h|--help                  Show Usage info

USAGE
exit 255
end

begin
    opts = GetoptLong.new(
        ["--debug", "-d", GetoptLong::NO_ARGUMENT],
        ["--help",  "-h", GetoptLong::NO_ARGUMENT],
        ["--nogit", "-n", GetoptLong::NO_ARGUMENT],
        ["--rhlogin",  "-l", GetoptLong::REQUIRED_ARGUMENT],
        ["--password",  "-p", GetoptLong::REQUIRED_ARGUMENT],
        ["--app",   "-a", GetoptLong::REQUIRED_ARGUMENT],
        ["--repo",  "-r", GetoptLong::REQUIRED_ARGUMENT],
        ["--type",  "-t", GetoptLong::REQUIRED_ARGUMENT]
    )
    opt = {}
    opts.each do |o, a|
        opt[o[2..-1]] = a.to_s
    end
rescue Exception => e
  #puts e.message
    p_usage
end

# Pull in configs from files
libra_server = get_var('libra_server')
debug = get_var('debug') == 'false' ? nil : get_var('debug')

ssh_config = "#{ENV['HOME']}/.ssh/config"
ssh_config_d = "#{ENV['HOME']}/.ssh/"

if opt["help"]
    p_usage
end

if opt["debug"]
    debug = true
end

opt["rhlogin"] = get_var('default_rhlogin') unless opt["rhlogin"]

if !RHC::check_rhlogin(opt['rhlogin'])
    p_usage
end

if !RHC::check_app(opt['app'])
    p_usage
end

if !opt['type']
    puts 'Type is required'
    p_usage
end

if !opt["rhlogin"] || !opt["app"] || !opt["type"]
    p_usage
end

password = opt['password']
if !password
  password = RHC::get_password
end

opt["repo"] = opt["app"] unless opt["repo"]

puts "
Found a bug? Post to the forum and we'll get right on it.
    IRC: #openshift on freenode
    Forums: https://www.redhat.com/openshift/forums

"

#
# 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 Errno::EACCES
            puts "Could not write to #{@git_parent}"
            puts "Reason: " + $!
            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

#
# Confirm libra_id_rsa exists
#
unless File.exists?("#{ssh_config_d}/libra_id_rsa")
    puts
    puts "Could not find #{ssh_config_d}/libra_id_rsa.  Unable to continue."
    puts "Your SSH keys are created either by running ssh-keygen (password optional)"
    puts "or by having the rhc-create-domain command do it for you.  If you created"
    puts "them on your own (or want to use an existing keypair), be sure to paste"
    puts "your public key into the dashboard page at http://www.openshift.com."
    puts "The client tools expect libra_id_rsa[.pub] so in the event that you are"
    puts "using existing keys, you can symlink libra_id_rsa -> id_rsa and"
    puts "libra_id_rsa.pub -> id_rsa.pub"
    puts "Also, make sure you never give out your secret key!"
    exit 212
end

#
# Create remote application space
#

puts "Attempting to create remote application space: " + opt['app']

data = {:cartridge => opt['type'],
                :action => 'configure',
                :app_name => opt['app'],
                :rhlogin => opt['rhlogin']
                }
if debug
  data['debug'] = "true"
end
json_data = RHC::generate_json(data)

puts "Contacting https://#{libra_server}"

url = URI.parse("https://#{libra_server}/broker/cartridge")
response = RHC::http_post(@http, url, json_data, password)

if response.code == '200'
    json_resp = JSON.parse(response.body)
    RHC::print_response_success(json_resp, debug, true)
    json_data = JSON.parse(json_resp['data'])
    health_check_path = json_data['health_check_path']
else
    RHC::print_response_err(response, debug)
end

#
# At this point, we need to register a handler to guarantee app
# cleanup on any exceptions or calls to exit
#
at_exit do
    unless $!.nil? || $!.is_a?(SystemExit) && $!.success?
        json_data = RHC::generate_json(
                   {:cartridge => opt['type'],
                    :action => 'deconfigure',
                    :app_name => opt['app'],
                    :rhlogin => opt['rhlogin']
                    })
        puts "Cleaning up application"
        url = URI.parse("https://#{libra_server}/broker/cartridge")
        RHC::http_post(@http, url, json_data, password)
    end
end

#
# Check / add new host to ~/.ssh/config
#

puts "Checking ~/.ssh/config"

user_info = RHC::get_user_info(libra_server, opt['rhlogin'], password, @http, debug, false)

fqdn = "#{opt['app']}-#{user_info['user_info']['namespace']}.#{user_info['user_info']['rhc_domain']}"

found = false

begin
    File.open(ssh_config, "r") do |sline|
        while(line = sline.gets)
            if line.to_s.index("Host *.#{user_info['user_info']['rhc_domain']}") == 0
                found = true
                break
            end
        end
    end
rescue Errno::EACCES
    puts "Could not read from #{ssh_config}"
    puts "Reason: " + $!
    puts
    puts "Please correct this first.  Then run rerun."
    puts
    exit 213
rescue Errno::ENOENT
    puts "Could not find #{ssh_config}.  This is ok, continuing"
end
if found
    puts "Found #{user_info['user_info']['rhc_domain']} in ~/.ssh/config... No need to adjust"
else
    puts "    Adding #{user_info['user_info']['rhc_domain']} to ~/.ssh/config"
    begin
        f = File.open(ssh_config, "a")
        f.puts <<SSH

# Added by rhc-create-app on #{`date`}
Host *.#{user_info['user_info']['rhc_domain']}
    IdentityFile ~/.ssh/libra_id_rsa
    VerifyHostKeyDNS yes
    StrictHostKeyChecking no
    UserKnownHostsFile ~/.ssh/libra_known_hosts

SSH
        f.close
     rescue Errno::EACCES
        puts "Could not write to #{ssh_config}"
        puts "Reason: " + $!
        puts
        puts "Please correct this first.  Then run rerun."
        puts
        exit 214
    rescue Errno::ENOENT
        # Make directory and config if they do not exist
        puts "Could not find directory: " + $!
        puts "creating"
        FileUtils.mkdir_p ssh_config_d
        file = File.open(ssh_config, 'w')
        file.close
        retry
    end
end

File.chmod(0700, ssh_config_d)
File.chmod(0600, ssh_config)

#
# Confirm that the host exists in DNS
#
puts "Now your new domain name is being propagated worldwide (this might take a minute)..."

# Allow DNS to propogate
sleep 15

# Now start checking for DNS
loop = 0
sleep_time = 2
while loop < RHC::Maxretries && !RHC::hostexist?(fqdn)
    sleep sleep_time
    loop+=1
    puts "  retry # #{loop} - Waiting for DNS: #{fqdn}"
    sleep_time = RHC::delay(sleep_time)
end

sleep_time = 2
attempt = 0

#
# Pull new repo locally
#

app_name = opt['app']
namespace = user_info['user_info']['namespace']
rhc_domain = user_info['user_info']['rhc_domain']
app_uuid = user_info['app_info'][app_name]['uuid']

git_url = "ssh://#{app_uuid}@#{app_name}-#{namespace}.#{rhc_domain}/~/git/#{app_name}.git/"

# If the hostname couldn't be resolved, print out the git URL
# and exit cleanly.  This will help solve issues where DNS times
# out in APAC, etc on resolution.
if loop >= RHC::Maxretries
    puts <<WARNING

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
WARNING: We weren't able to lookup your hostname (#{fqdn}) 
in a reasonable amount of time.  This can happen periodically and will just
take an extra minute or two to propagate depending on where you are in the
world.  Once you are able to access your application in a browser, you can then
clone your git repository.

  Application URL: http://#{fqdn}

  Git Repository URL: #{git_url}

  Git Clone command: 
    git clone #{git_url} #{opt['repo']}

If you can't get your application running in the browser, you can also try
destroying and recreating the application as well using:

  rhc-ctl-app -c destroy -a #{opt['app']} -l #{opt["rhlogin"]}

If this doesn't work for you, let us know in the forums or in IRC and we'll
make sure to get you up and running.

  Forums: https://www.redhat.com/openshift/forums/express

  IRC: #openshift (on Freenode)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

WARNING
    exit 0
end

unless opt['nogit']
    puts "Pulling new repo down"

    puts "git clone --quiet #{git_url} #{opt['repo']}" if debug
    quiet = (debug ? ' ' : '--quiet ')
    git_clone = %x<git clone #{quiet} #{git_url} #{opt['repo']}>
    if $?.exitstatus != 0
        puts "Error in git clone"
        puts git_clone
        exit 216
    end
else
    puts <<IMPORTANT

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
IMPORTANT: Since the -n flag was specified, no local repo has been created.
This means you can't make changes to your published application until after
you clone the repo yourself.  See the git url below for more information.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

IMPORTANT
end

#
# At this point, we need to register a handler to guarantee git
# repo cleanup on any exceptions or calls to exit
#
at_exit do
    unless $!.nil? || $!.is_a?(SystemExit) && $!.success?
        puts "Cleaning up git repo"
        FileUtils.rm_rf opt['repo']
    end
end

#
# Test several times, doubling sleep time between attempts.
#
sleep_time = 2
attempt = 0
puts "Confirming application #{opt['app']} is available"
while attempt < RHC::Maxretries
    attempt+=1
    puts "  Attempt # #{attempt}"
    url = URI.parse("http://#{fqdn}/#{health_check_path}")
    begin
      response = @http.get_response(url)
    rescue Exception => e
      response = nil
    end
    if !response.nil? && response.code == "200" && response.body[0,1] == "1"
        puts <<LOOKSGOOD

Success!  Your application is now published here:

      http://#{fqdn}/

The remote repository is located here:

    #{git_url}

To make changes to your application, commit to #{opt['repo']}/.
Then run 'git push' to update your OpenShift Express space

LOOKSGOOD
        exit 0
    end
    if !response.nil? && debug
      puts "Server responded with #{response.code}"
      puts response.body unless response.code == '503'
    end
    puts
    puts "    sleeping #{sleep_time} seconds"
    sleep sleep_time
    sleep_time = RHC::delay(sleep_time)
end
puts "Unable to find or access the site... problems"
exit 254
