module RHC

Constants

API
APP_NAME_MAX_LENGTH
CLEAR_LINE

reset lines r moves the cursor to the beginning of line ANSI escape code to clear line from cursor to end of line "e" is an alternative to "033" cf. en.wikipedia.org/wiki/ANSI_escape_code

DEBUG_INGORE_KEYS
DEFAULT_DELAY
DEFAULT_MAX_LENGTH
MAX_RETRIES
PATTERN_VERSION

Public Class Methods

check_app(app) click to toggle source
# File lib/rhc-common.rb, line 150
def self.check_app(app)
  check_field(app, 'application', APP_NAME_MAX_LENGTH)
end
check_app_available(net_http, app_name, fqdn, health_check_path, result, git_url, repo_dir, no_git) click to toggle source
# File lib/rhc-common.rb, line 633
  def self.check_app_available(net_http, app_name, fqdn, health_check_path, result, git_url, repo_dir, no_git)
      #
      # Test several times, doubling sleep time between attempts.
      #
      sleep_time = 2
      attempt = 0
      puts "Confirming application '#{app_name}' is available" if @mydebug
      while attempt < MAX_RETRIES
          attempt += 1
          if @mydebug
            puts "  Attempt # #{attempt}"
          else
            print CLEAR_LINE + "Confirming application '#{app_name}' is available:  Attempt # #{attempt}"
          end
          $stdout.flush
          url = URI.parse("http://#{fqdn}/#{health_check_path}")
          
          sleep(2.0)
          begin
            response = net_http.get_response(url)
          rescue Exception => e
            response = nil
          end
          if !response.nil? && response.code == "200" && response.body[0,1] == "1"
            puts CLEAR_LINE + "Confirming application '#{app_name}' is available:  Success!"
            puts ""
            puts "#{app_name} published:  http://#{fqdn}/"
            puts "git url:  #{git_url}"

            if @mydebug
              unless no_git
                puts "To make changes to '#{app_name}', commit to #{repo_dir}/."
              else
                puts "To make changes to '#{app_name}', you must first clone it with:
      git clone #{git_url}

"
                puts "Then run 'git push' to update your OpenShift space."
              end
            end
            if result && !result.empty?
              puts "#{result}"
            end
            return true
          end
          if !response.nil? && @mydebug
            puts "Server responded with #{response.code}"
            puts response.body unless response.code == '503'
          end
          puts "    sleeping #{sleep_time} seconds" if @mydebug
          sleep sleep_time
          sleep_time = delay(sleep_time)
      end
      return false
  end
check_field(field, type, max=0, val_regex=/[^0-9a-zA-Z]/, regex_failed_error='contains non-alphanumeric characters!') click to toggle source
# File lib/rhc-common.rb, line 163
def self.check_field(field, type, max=0, val_regex=%r[^0-9a-zA-Z]/, 
                     regex_failed_error='contains non-alphanumeric characters!')
  if field
    if field =~ val_regex
      puts "#{type} " + regex_failed_error
      return false
    end
    if max != 0 && field.length > max
      puts "maximum #{type} size is #{max} characters"
      return false
    end
  else
    puts "#{type} is required"
    return false
  end
  true
end
check_key(keyname) click to toggle source
# File lib/rhc-common.rb, line 158
def self.check_key(keyname)
  check_field(keyname, 'key name', DEFAULT_MAX_LENGTH, %r[^0-9_a-zA-Z]/, 
              'contains invalid characters! Only alpha-numeric characters and underscores allowed.')
end
check_namespace(namespace) click to toggle source
# File lib/rhc-common.rb, line 154
def self.check_namespace(namespace)
  check_field(namespace, 'namespace', DEFAULT_MAX_LENGTH)
end
check_rhlogin(rhlogin) click to toggle source

Invalid chars (") ($) (^) (<) (>) (|) (%) (/) (;) (:) (,) () (*) (=) (~)

# File lib/rhc-common.rb, line 137
def self.check_rhlogin(rhlogin)
  if rhlogin
    if rhlogin =~ %r["\$\^<>\|%\/;:,\\\*=~]/
      puts 'RHLogin may not contain any of these characters: (\") ($) (^) (<) (>) (|) (%) (/) (;) (:) (,) (\) (*) (=) (~)'
      return false
    end
  else
    puts "RHLogin is required"
    return false
  end
  true
end
check_version() click to toggle source
# File lib/rhc-common.rb, line 79
def self.check_version
  if @@api_version =~ PATTERN_VERSION
    if API != @@api_version
      puts "\nNOTICE: Client API version (#{API}) does not match the server (#{@@api_version}).\nThough requests may succeed, you should consider updating your client tools.\n\n"
    end
  end
end
connect_timeout(*vals) click to toggle source
# File lib/rhc-common.rb, line 56
def self.connect_timeout(*vals)
  vals.each do |val|
    if val
      unless val.to_i > 0
        puts 'Timeout must be specified as a number greater than 0'
        exit 1
      end
      @connect_timeout = [val.to_i, @connect_timeout].max
      return @connect_timeout
    end
  end
end
create_app(libra_server, net_http, user_info, app_name, app_type, rhlogin, password, repo_dir=nil, no_dns=false, no_git=false, is_embedded_jenkins=false, gear_size='small',scale=false) click to toggle source
# File lib/rhc-common.rb, line 408
  def self.create_app(libra_server, net_http, user_info, app_name, app_type, rhlogin, password, repo_dir=nil, no_dns=false, no_git=false, is_embedded_jenkins=false, gear_size='small',scale=false)

    # Need to have a fake HTTPResponse object for passing to print_reponse_err
    Struct.new('FakeResponse',:body,:code,:content_type)

    domains = user_info['user_info']['domains']
    if domains.empty?
      emessage = "Please create a domain with 'rhc domain create -n <namespace>' before creating applications."
      print_response_err(Struct::FakeResponse.new(emessage,403))
    end
    namespace = domains[0]['namespace']
    puts "Creating application: #{app_name} in #{namespace}"
    data = {:cartridge => app_type,
            :action => 'configure',
            :node_profile => gear_size,
            :app_name => app_name,
            :rhlogin => rhlogin
           }
    if @mydebug
      data[:debug] = true
    end    

    # Need to use the new REST API for scaling apps
    #  We'll need to then get the new application using the existing
    #  API in order to access the rest of the logic in this function
    if scale
      end_point = "https://#{libra_server}/broker/rest/api"
      client = Rhc::Rest::Client.new(end_point, rhlogin, password)

      domain = client.find_domain(user_info['user_info']['domains'][0]['namespace']).first

      namespace = domain.id
      # Catch errors
      begin
        application = domain.add_application(app_name,{:cartridge => app_type, :scale => true, :gear_profile => gear_size})

        # Variables that are needed for the rest of the function
        app_uuid = application.uuid
        result = "Successfully created application: #{app_name}"

        # Since health_check_path is not returned, we need to fudge it for now
        health_check_path =
          case app_type
          when %r^php/
            "health_check.php"
          when %r^perl/
            "health_check.pl"
          else
            "health"
          end

        puts "DEBUG: '#{app_name}' creation returned success." if @mydebug
      rescue Rhc::Rest::ResourceAccessException => e
        print_response_err(Struct::FakeResponse.new(e.message,e.code))
      rescue Rhc::Rest::ValidationException => e
        print_response_err(Struct::FakeResponse.new(e.message,406))
      rescue Rhc::Rest::ServerErrorException => e 
        if e.message =~ %r^Failed to create application .* due to:Scalable app cannot be of type/ 
          puts "Can not create a scaling app of type #{app_type}, either disable scaling or choose another app type"
          exit 1
        else
          raise e 
        end
      end
    else
      json_data = generate_json(data)

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

      if response.code == '200'
        json_resp = JSON.parse(response.body)
        print_response_success(json_resp)
        json_data = JSON.parse(json_resp['data'])
        health_check_path = json_data['health_check_path']
        app_uuid = json_data['uuid']
        result = json_resp['result']
        puts "DEBUG: '#{app_name}' creation returned success." if @mydebug
      else
        print_response_err(response)
      end
    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?
        puts "Cleaning up application"
        destroy_app(libra_server, net_http, app_name, rhlogin, password)
      end
    end

    rhc_domain = user_info['user_info']['rhc_domain']

    fqdn = "#{app_name}-#{namespace}.#{rhc_domain}"

    loop = 0
    #
    # Confirm that the host exists in DNS
    #
    unless no_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
      sleep_time = 2
      while loop < MAX_RETRIES && !hostexist?(fqdn)
          sleep sleep_time
          loop+=1
          print CLEAR_LINE + "    retry # #{loop} - Waiting for DNS: #{fqdn}"
          $stdout.flush
          sleep_time = delay(sleep_time)
      end
    end
    
    # if we have executed print statements, then move to the next line
    if loop > 0
      puts
    end
    
    # construct the Git URL
    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 >= MAX_RETRIES
        puts "
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
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} #{repo_dir}

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

  rhc app destroy -a #{app_name} -l #{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)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

"
        exit 0
    end
    
    #
    # Pull new repo locally
    #
    
    unless no_git
        puts "Pulling new repo down" if @mydebug
    
        puts "git clone --quiet #{git_url} #{repo_dir}" if @mydebug
        quiet = (@mydebug ? ' ' : '--quiet ')
        git_clone = %x<git clone #{quiet} #{git_url} #{repo_dir}>
        if $?.exitstatus != 0
            puts "Error in git clone"
            puts git_clone
            exit 216
        end
    else
      if is_embedded_jenkins
        # if this is a jenkins client application to be embedded, 
        # then print this message only in debug mode
        if @mydebug
          puts "
Note: There is a git repo for your Jenkins application '#{app_name}'
but it isn't being downloaded as part of this process.  In most cases
it isn't needed but you can always clone it later.

"
        end
      else         
        puts "
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
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.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

"
      end
    end
    
    #
    # At this point, we need to register a handler to guarantee git
    # repo cleanup on any exceptions or calls to exit
    #
    unless no_git
      at_exit do
          unless $!.nil? || $!.is_a?(SystemExit) && $!.success?
              puts "Cleaning up git repo"
              FileUtils.rm_rf repo_dir
          end
      end
    end
    return {:app_name => app_name,
            :fqdn => fqdn,
            :health_check_path => health_check_path,
            :git_url => git_url,
            :repo_dir => repo_dir,
            :result => result
           }
  end
ctl_app(libra_server, net_http, app_name, rhlogin, password, action, embedded=false, framework=nil, server_alias=nil, print_result=true) click to toggle source
# File lib/rhc-common.rb, line 810
def self.ctl_app(libra_server, net_http, app_name, rhlogin, password, action, embedded=false, framework=nil, server_alias=nil, print_result=true)
  data = {:action => action,
          :app_name => app_name,
          :rhlogin => rhlogin
         }
  
  data[:server_alias] = server_alias if server_alias
  if framework
    data[:cartridge] = framework
  end
  
  if @mydebug
    data[:debug] = true
  end
  
  json_data = generate_json(data)

  url = nil
  if embedded
    url = URI.parse("https://#{libra_server}/broker/embed_cartridge")
  else
    url = URI.parse("https://#{libra_server}/broker/cartridge")
  end
  response = http_post(net_http, url, json_data, password)
  
  if response.code == '200'
    json_resp = JSON.parse(response.body)
    print_response_success(json_resp, print_result || @mydebug)
  else
      print_response_err(response)
  end
  JSON.parse(response.body)
end
debug(bool) click to toggle source
# File lib/rhc-common.rb, line 69
def self.debug(bool)
  @mydebug = bool
end
delay(time, adj=DEFAULT_DELAY) click to toggle source
# File lib/rhc-common.rb, line 87
def self.delay(time, adj=DEFAULT_DELAY)
  (time*=adj).to_int
end
destroy_app(libra_server, net_http, app_name, rhlogin, password) click to toggle source
# File lib/rhc-common.rb, line 690
def self.destroy_app(libra_server, net_http, app_name, rhlogin, password)
  json_data = generate_json(
                     {:action => 'deconfigure',
                      :app_name => app_name,
                      :rhlogin => rhlogin
                      })
  url = URI.parse("https://#{libra_server}/broker/cartridge")
  http_post(net_http, url, json_data, password)
end
generate_json(data) click to toggle source
# File lib/rhc-common.rb, line 91
def self.generate_json(data)
    data['api'] = API
    json = JSON.generate(data)
    json
end
get_cartridge_listing(carts, sep, libra_server, net_http, cart_type="standalone", print_result=nil) click to toggle source
# File lib/rhc-common.rb, line 130
def self.get_cartridge_listing(carts, sep, libra_server, net_http, cart_type="standalone", print_result=nil)
  carts = get_cartridges_list(libra_server, net_http, cart_type, print_result) if carts.nil?
  carts.join(sep)
end
get_cartridges_list(libra_server, net_http, cart_type="standalone", print_result=nil) click to toggle source
# File lib/rhc-common.rb, line 97
def self.get_cartridges_list(libra_server, net_http, cart_type="standalone", print_result=nil)
  puts "Obtaining list of cartridges (please excuse the delay)..."
  data = {'cart_type' => cart_type}
  if @mydebug
    data[:debug] = true
  end
  print_post_data(data)
  json_data = generate_json(data)

  url = URI.parse("https://#{libra_server}/broker/cartlist")
  response = http_post(net_http, url, json_data, "none")

  unless response.code == '200'
    print_response_err(response)
    return []
  end
  begin
    json_resp = JSON.parse(response.body)
  rescue JSON::ParserError
    exit 1
  end
  update_server_api_v(json_resp)
  if print_result
    print_response_success(json_resp)
  end
  begin
    carts = (JSON.parse(json_resp['data']))['carts']
  rescue JSON::ParserError
    exit 1
  end
  carts
end
get_password() click to toggle source
# File lib/rhc-common.rb, line 276
def self.get_password
  password = nil
  begin
    print "Password: "
    system "stty -echo"
    password = gets.chomp
  rescue Interrupt
    puts "\n"
    exit 1
  ensure
    system "stty echo"
  end
  puts "\n"
  password
end
get_ssh_keys(libra_server, rhlogin, password, net_http) click to toggle source
# File lib/rhc-common.rb, line 242
def self.get_ssh_keys(libra_server, rhlogin, password, net_http)
  data = {'rhlogin' => rhlogin, 'action' => 'list-keys'}
  if @mydebug
    data[:debug] = true
  end
  print_post_data(data)
  json_data = generate_json(data)

  url = URI.parse("https://#{libra_server}/broker/ssh_keys")
  response = http_post(net_http, url, json_data, password)

  unless response.code == '200'
    if response.code == '401'
      puts "Invalid user credentials"
      exit 97
    else
      print_response_err(response)
    end
    exit 1
  end
  begin
    json_resp = JSON.parse(response.body)
  rescue JSON::ParserError
    exit 1
  end
  update_server_api_v(json_resp)
  begin
    ssh_keys = (JSON.parse(json_resp['data'].to_s))
  rescue JSON::ParserError
    exit 1
  end
  ssh_keys
end
get_user_info(libra_server, rhlogin, password, net_http, print_result, not_found_message=nil) click to toggle source
# File lib/rhc-common.rb, line 198
def self.get_user_info(libra_server, rhlogin, password, net_http, print_result, not_found_message=nil)
  data = {'rhlogin' => rhlogin}
  if @mydebug
    data[:debug] = true
  end
  print_post_data(data)
  json_data = generate_json(data)

  url = URI.parse("https://#{libra_server}/broker/userinfo")
  response = http_post(net_http, url, json_data, password)

  unless response.code == '200'
    if response.code == '404'
      if not_found_message
        puts not_found_message
      else
        puts "A user with rhlogin '#{rhlogin}' does not have a registered domain.  Be sure to run 'rhc domain create' before using the other rhc tools."
      end
      exit 99
    elsif response.code == '401'
      puts "Invalid user credentials"
      exit 97
    else
      print_response_err(response)
    end
    exit 1
  end
  begin
    json_resp = JSON.parse(response.body)
  rescue JSON::ParserError
    exit 1
  end
  update_server_api_v(json_resp)
  if print_result
    print_response_success(json_resp)
  end
  begin
    user_info = JSON.parse(json_resp['data'].to_s)
  rescue JSON::ParserError
    exit 1
  end
  user_info
end
hostexist?(host) click to toggle source

Check if host exists

# File lib/rhc-common.rb, line 402
def self.hostexist?(host)
    dns = Resolv::DNS.new
    resp = dns.getresources(host, Resolv::DNS::Resource::IN::A)
    return resp.any?
end
http_post(http, url, json_data, password) click to toggle source
# File lib/rhc-common.rb, line 292
def self.http_post(http, url, json_data, password)
  req = http::Post.new(url.path)

  puts "Contacting #{url.scheme}://#{url.host}" if @mydebug
  req.set_form_data({'json_data' => json_data, 'password' => password})
  http = http.new(url.host, url.port)
  http.open_timeout = @connect_timeout
  http.read_timeout = @read_timeout
  if url.scheme == "https"
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
  begin
    response = http.start {|http| http.request(req)}
    if response.code == '404' && response.content_type == 'text/html'
      # TODO probably want to remove this at some point
      puts "!!!! WARNING !!!! WARNING !!!! WARNING !!!!"
      puts "RHCloud server not found.  You might want to try updating your rhc client tools."
      exit 218
    end
    response
  rescue Exception => e
    puts "There was a problem communicating with the server. Response message: #{e.message}"
    puts "If you were disconnected it is possible the operation finished without being able to report success."
    puts "You can use 'rhc domain show' and 'rhc app status' to learn about the status of your user and application(s)."
    exit 219
  end
end
list_ports(rhc_domain, namespace, app_name, app_uuid, debug=true) click to toggle source

Runs rhc-list-ports on server to check available ports :stderr return user-friendly port name, :stdout returns 127.0.0.1:8080 format

# File lib/rhc-common.rb, line 702
def self.list_ports(rhc_domain, namespace, app_name, app_uuid, debug=true)

  ip_and_port_simple_regex = %r[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}/

  ssh_host = "#{app_name}-#{namespace}.#{rhc_domain}"

  ssh_cmd = "ssh -t #{app_uuid}@#{ssh_host} 'rhc-list-ports'"

  hosts_and_ports = []
  hosts_and_ports_descriptions = []
  scaled_uuids = []

  puts ssh_cmd if debug

  Open3.popen3(ssh_cmd) { |stdin, stdout, stderr| 

    stdout.each { |line|
      line = line.chomp
      if line.downcase =~ %rscale/
        scaled_uuid = line[5..-1]
        scaled_uuids << scaled_uuid
      else
        if ip_and_port_simple_regex.match(line)
          hosts_and_ports << line
        end
      end
    }

    stderr.each { |line|
      line = line.chomp
      if line.downcase =~ %rpermission denied/
        puts line
        exit 1
      end
      
      
      if line.index(ip_and_port_simple_regex)
        hosts_and_ports_descriptions << line
      end
    }

  }

  scaled_uuids.each { |uuid|
    list_scaled_ports(rhc_domain, namespace, app_name, uuid, hosts_and_ports, hosts_and_ports_descriptions, debug)
  }
  
  #hosts_and_ports_descriptions = stderr.gets.chomp.split(/\n/)
  #hosts_and_ports = stdout.gets.chomp.split(/\n/)

  # Net::SSH.start(ssh_host, app_uuid) do |ssh| 

  #   ssh.exec!("rhc-list-ports") do |channel, stream, data|

  #     array = data.split(/\n/)

  #     if stream == :stderr 
  #       hosts_and_ports_descriptions = array
  #     elsif stream == :stdout 
  #       hosts_and_ports = array
  #     end

  #   end

  # end

  return hosts_and_ports, hosts_and_ports_descriptions

end
list_scaled_ports(rhc_domain, namespace, app_name, app_uuid, hosts_and_ports, hosts_and_ports_descriptions, debug=true) click to toggle source

Runs rhc-list-ports on server to check available ports :stderr return user-friendly port name, :stdout returns 127.0.0.1:8080 format

# File lib/rhc-common.rb, line 774
def self.list_scaled_ports(rhc_domain, namespace, app_name, app_uuid, hosts_and_ports, hosts_and_ports_descriptions, debug=true)

  ip_and_port_simple_regex = %r[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}/

  ssh_host = "#{app_name}-#{namespace}.#{rhc_domain}"

  ssh_cmd = "ssh -t #{app_uuid}@#{ssh_host} 'rhc-list-ports'"

  puts ssh_cmd if debug

  Open3.popen3(ssh_cmd) { |stdin, stdout, stderr| 

    stdout.each { |line|
      line = line.chomp
   
      if ip_and_port_simple_regex.match(line)
        hosts_and_ports << line
      end
    }

    stderr.each { |line|
      line = line.chomp

      if line.downcase =~ %rpermission denied/
        puts line
        exit 1
      end
      
      if line.index(ip_and_port_simple_regex)
        hosts_and_ports_descriptions << line
      end
    }
  }

end
print_json_body(json_resp, print_result=true) click to toggle source
print_post_data(h) click to toggle source
# File lib/rhc-common.rb, line 181
def self.print_post_data(h)
  if (@mydebug)
    puts 'Submitting form:'
    h.each do |k,v|
      if k.to_s != 'password'
        puts "#{k.to_s}: #{v.to_s}"
      else
        print 'password: '
        for i in (1..v.length)
          print 'X'
        end
        puts ''
      end
    end
  end
end
print_response_err(response) click to toggle source
print_response_message(message) click to toggle source
print_response_messages(json_resp) click to toggle source
print_response_success(json_resp, print_result=false) click to toggle source
timeout(*vals) click to toggle source
# File lib/rhc-common.rb, line 43
def self.timeout(*vals)
  vals.each do |val|
    if val
      unless val.to_i > 0
        puts 'Timeout must be specified as a number greater than 0'
        exit 1
      end
      @read_timeout = [val.to_i, @read_timeout].max
      return @read_timeout
    end
  end
end
update_server_api_v(dict) click to toggle source
# File lib/rhc-common.rb, line 73
def self.update_server_api_v(dict)
  if !dict['api'].nil? && (dict['api'] =~ PATTERN_VERSION)
    @@api_version = dict['api']
  end
end