class RHC::Commands::App

Constants

DEFAULT_DELAY_THROTTLE
MAX_RETRIES

Public Instance Methods

configure(app_name) click to toggle source
# File lib/rhc/commands/app.rb, line 480
def configure(app_name)
  rest_app = find_app

  app_options = {}
  app_options[:auto_deploy] = options.auto_deploy if !options.auto_deploy.nil?
  app_options[:keep_deployments] = options.keep_deployments if options.keep_deployments
  app_options[:deployment_branch] = options.deployment_branch if options.deployment_branch
  app_options[:deployment_type] = options.deployment_type if options.deployment_type

  if app_options.present?
    paragraph do
      say "Configuring application '#{app_name}' ... "
      rest_app.configure(app_options)
      success "done"
    end
  end

  paragraph { display_app(find_app, nil, [:auto_deploy, :keep_deployments, :deployment_type, :deployment_branch]) }

  paragraph { say "Your application '#{rest_app.name}' is #{app_options.empty? ? '' : 'now '}configured as listed above." }
  paragraph { say "Use 'rhc show-app #{rest_app.name} --configuration' to check your configuration values any time." } if app_options.present?

  0
end
create(name, cartridges) click to toggle source
# File lib/rhc/commands/app.rb, line 68
def create(name, cartridges)
  check_config!

  check_name!(name)

  arg_envs, cartridges = cartridges.partition{|item| item.match(env_var_regex_pattern)}

  rest_domain = check_domain!
  rest_app = nil
  repo_dir = nil

  if options.from_app
    raise RHC::AppCloneNotSupportedException, "The server does not support creating apps based on others (rhc create-app --from-app)." if (!rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][name]') || !rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][url]'))
    raise ArgumentError, "Option --from-code is incompatible with --from-app. When creating an app based on another resource you can either specify a Git repository URL with --from-code or an existing app name with --from-app." if options.from_code     
    raise ArgumentError, "Option --no-dns is incompatible with --from-app. We need to propagate the new app DNS to be able to configure it." if options.dns == false
    raise ArgumentError, "Do not specify cartridges when creating an app based on another one. All cartridges will be copied from the original app." if !(cartridges || []).empty?

    from_app = find_app(:app => options.from_app)

    arg_envs = from_app.environment_variables.collect {|env| "#{env.name}=#{env.value}"} + arg_envs
    cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect{|c| c.custom? ? c.url : c.name}
  end

  cartridges = check_cartridges(cartridges, &require_one_web_cart)

  options.default          :dns => true,
    :git => true

  raise ArgumentError, "You have named both your main application and your Jenkins application '#{name}'. In order to continue you'll need to specify a different name with --enable-jenkins or choose a different application name." if jenkins_app_name == name && enable_jenkins?

  cart_names = cartridges.collect do |c|
    c.usage_rate? ? "#{c.short_name} (addtl. costs may apply)" : c.short_name
  end.join(', ')

  env = collect_env_vars(arg_envs.concat(Array(options.env)))
  if env.present? && !rest_domain.supports_add_application_with_env_vars?
    env = []
    warn "Server does not support environment variables."
  end

  scaling = options.scaling
  region = options.region

  raise RHC::RegionsAndZonesNotSupportedException if region.present? && !rest_client.supports_regions_and_zones?

  if from_app
    scaling = from_app.scalable if scaling.nil?
    region = from_app.region if region.nil?

    cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect do |cartridge|
      {
        :name => (cartridge.name if !cartridge.custom?), 
        :url => (cartridge.url if cartridge.custom?),
        :gear_size => options.gear_size || cartridge.gear_profile,
        :additional_gear_storage => (cartridge.additional_gear_storage if cartridge.additional_gear_storage > 0),
        :scales_from => (cartridge.scales_from if scaling && cartridge.scalable?),
        :scales_to => (cartridge.scales_to if scaling && cartridge.scalable?)
      }.reject{|k,v| v.nil? }
    end
  end

  paragraph do
    header "Application Options"
    say table([["Domain:", options.namespace],
           ["Cartridges:", cart_names],
          (["Source Code:", options.from_code] if options.from_code),
          (["From app:", from_app.name] if from_app),
           ["Gear Size:", options.gear_size || (from_app ? "Copied from '#{from_app.name}'" : "default")],
           ["Scaling:", (scaling ? "yes" : "no") + (from_app && options.scaling.nil? ? " (copied from '#{from_app.name}')" : '')],
          (["Environment Variables:", env.map{|item| "#{item.name}=#{item.value}"}.join(', ')] if env.present?),
          (["Region:", region + (from_app && options.region.nil? ? " (copied from '#{from_app.name}')" : '')] if region),
          ].compact
         )
  end

  paragraph do
    say "Creating application '#{name}' ... "

    # create the main app
    rest_app = create_app(name, cartridges, rest_domain, options.gear_size, scaling, options.from_code, env, options.auto_deploy, options.keep_deployments, options.deployment_branch, options.deployment_type, region)
    success "done"

    paragraph{ indent{ success rest_app.messages.map(&:strip) } }
  end

  build_app_exists = rest_app.building_app

  if enable_jenkins?

    unless build_app_exists
      paragraph do
        say "Setting up a Jenkins application ... "

        begin
          build_app_exists = add_jenkins_app(rest_domain)

          success "done"
          paragraph{ indent{ success build_app_exists.messages.map(&:strip) } }

        rescue Exception => e
          warn "not complete"
          add_issue("Jenkins failed to install - #{e}",
                    "Installing jenkins and jenkins-client",
                    "rhc create-app jenkins",
                    "rhc add-cartridge jenkins-client -a #{rest_app.name}")
        end
      end
    end

    paragraph do
      messages = []
      add_jenkins_client_to(rest_app, messages)
      paragraph{ indent{ success messages.map(&:strip) } }
    end if build_app_exists
  end

  debug "Checking SSH keys through the wizard"
  check_sshkeys! unless options.no_keys

  if options.dns
    paragraph do
      say "Waiting for your DNS name to be available ... "
      if dns_propagated? rest_app.host
        success "done"
      else
        warn "failure"
        add_issue("We were unable to lookup your hostname (#{rest_app.host}) in a reasonable amount of time and can not clone your application.",
                  "Clone your git repo",
                  "rhc git-clone #{rest_app.name}")

        output_issues(rest_app)
        return 0
      end
    end
  end

  if from_app
    say "Setting deployment configuration ... "
    rest_app.configure({:auto_deploy => from_app.auto_deploy, :keep_deployments => from_app.keep_deployments , :deployment_branch => from_app.deployment_branch, :deployment_type => from_app.deployment_type})
    success 'done'

    snapshot_filename = temporary_snapshot_filename(from_app.name)
    save_snapshot(from_app, snapshot_filename)
    restore_snapshot(rest_app, snapshot_filename)
    File.delete(snapshot_filename) if File.exist?(snapshot_filename)

    paragraph { warn "The application '#{from_app.name}' has aliases set which were not copied. Please configure the aliases of your new application manually." } unless from_app.aliases.empty?
  end

  if options.git
    section(:now => true, :top => 1, :bottom => 1) do
      begin
        if has_git?
          repo_dir = git_clone_application(rest_app)
        else
          warn "You do not have git installed, so your application's git repo will not be cloned"
        end
      rescue RHC::GitException => e
        warn "#{e}"
        unless RHC::Helpers.windows? and windows_nslookup_bug?(rest_app)
          add_issue("We were unable to clone your application's git repo - #{e}",
                    "Clone your git repo",
                    "rhc git-clone #{rest_app.name}")
        end
      end
    end
  end

  output_issues(rest_app) if issues?

  paragraph do
    say "Your application '#{rest_app.name}' is now available."
    paragraph do
      indent do
        say table [
            ['URL:', rest_app.app_url],
            ['SSH to:', rest_app.ssh_string],
            ['Git remote:', rest_app.git_url],
            (['Cloned to:', repo_dir] if repo_dir)
          ].compact
      end
    end
  end
  paragraph{ say "Run 'rhc show-app #{name}' for more details about your app." }

  0
end
delete(app) click to toggle source
# File lib/rhc/commands/app.rb, line 265
def delete(app)
  rest_app = find_app

  confirm_action "#{color("This is a non-reversible action! Your application code and data will be permanently deleted if you continue!", :yellow)}\n\nAre you sure you want to delete the application '#{app}'?"

  say "Deleting application '#{rest_app.name}' ... "
  rest_app.destroy
  success "deleted"

  paragraph{ rest_app.messages.each{ |s| success s } }

  0
end
deploy(ref) click to toggle source
# File lib/rhc/commands/app.rb, line 463
def deploy(ref)
  rest_app = find_app

  raise RHC::DeploymentsNotSupportedException.new if !rest_app.supports? "DEPLOY"

  deploy_artifact(rest_app, ref, options.hot_deploy, options.force_clean_build)

  0
end
force_stop(app) click to toggle source
# File lib/rhc/commands/app.rb, line 322
def force_stop(app)
  app_action :stop, true

  results { say "#{app} force stopped" }
  0
end
reload(app) click to toggle source
# File lib/rhc/commands/app.rb, line 342
def reload(app)
  app_action :reload

  results { say "#{app} config reloaded" }
  0
end
restart(app) click to toggle source
# File lib/rhc/commands/app.rb, line 332
def restart(app)
  app_action :restart

  results { say "#{app} restarted" }
  0
end
scale_down(app) click to toggle source
# File lib/rhc/commands/app.rb, line 312
def scale_down(app)
  app_action :scale_down

  results { say "#{app} scaled down" }
  0
end
scale_up(app) click to toggle source
# File lib/rhc/commands/app.rb, line 302
def scale_up(app)
  app_action :scale_up

  results { say "#{app} scaled up" }
  0
end
show(app_name) click to toggle source
# File lib/rhc/commands/app.rb, line 389
def show(app_name)

  if options.state
    find_app(:with_gear_groups => true).each do |gg|
      say "Cartridge #{gg.cartridges.collect { |c| c['name'] }.join(', ')} is #{gear_group_state(gg.gears.map{ |g| g['state'] })}"
    end

  elsif options.gears && options.gears != true
    groups = find_app(:with_gear_groups => true)

    case options.gears
    when 'quota'
      opts = {:as => :gear, :split_cells_on => /\s*\t/, :header => ['Gear', 'Cartridges', 'Used', 'Limit'], :align => [nil, nil, :right, :right]}
      table_from_gears('echo "$(du --block-size=1 -s 2>/dev/null | cut -f 1)"', groups, opts) do |gear, data, group|
        [gear['id'], group.cartridges.collect{ |c| c['name'] }.join(' '), (human_size(data.chomp) rescue 'error'), human_size(group.quota)]
      end
    when 'ssh'
      groups.each{ |group| group.gears.each{ |g| say (ssh_string(g['ssh_url']) or raise NoPerGearOperations) } }
    else
      run_on_gears(ssh_command_for_op(options.gears), groups)
    end

  elsif options.gears
    domain, app = discover_domain_and_app
    gear_info = rest_client.find_application_gear_groups_endpoints(domain, app).map do |group|
      group.gears.map do |gear|
        [
          gear['id'],
          gear['state'] == 'started' ? color(gear['state'], :green) : color(gear['state'], :yellow),
          (gear['endpoints'].blank? ? group.cartridges : gear['endpoints']).collect{ |c| c['cartridge_name'] || c['name'] }.join(' '),
          group.gear_profile,              
          gear['region'],
          gear['zone'],
          ssh_string(gear['ssh_url'])
        ]
      end
    end.flatten(1)

    explicit_regions = gear_info.select{|i| !i[4].nil?}.present?
    explicit_zones = gear_info.select{|i| !i[5].nil?}.present?

    say table(gear_info.map(&:compact), :header => ['ID', 'State', 'Cartridges', 'Size', explicit_regions ? 'Region' : nil, explicit_zones ? 'Zone' : nil, 'SSH URL'].compact)

  elsif options.configuration
    display_app_configurations(find_app)
    paragraph { say "Use 'rhc configure-app' to change the configuration values of this application." }

  else
    app = find_app(:include => :cartridges)
    display_app(app, app.cartridges, nil, options.verbose)
  end

  0
end
start(app) click to toggle source
# File lib/rhc/commands/app.rb, line 282
def start(app)
  app_action :start

  results { say "#{app} started" }
  0
end
stop(app) click to toggle source
# File lib/rhc/commands/app.rb, line 292
def stop(app)
  app_action :stop

  results { say "#{app} stopped" }
  0
end
tidy(app) click to toggle source
# File lib/rhc/commands/app.rb, line 352
def tidy(app)
  app_action :tidy

  results { say "#{app} cleaned up" }
  0
end

Private Instance Methods

add_issue(reason, commands_header, *commands) click to toggle source

Issues collector collects a set of recoverable issues and steps to fix them for output at the end of a complex command

# File lib/rhc/commands/app.rb, line 756
def add_issue(reason, commands_header, *commands)
  @issues ||= []
  issue = {:reason => reason,
           :commands_header => commands_header,
           :commands => commands}
  @issues << issue
end
add_jenkins_app(rest_domain) click to toggle source
# File lib/rhc/commands/app.rb, line 598
def add_jenkins_app(rest_domain)
  create_app(jenkins_app_name, jenkins_cartridge_name, rest_domain)
end
add_jenkins_cartridge(rest_app) click to toggle source
# File lib/rhc/commands/app.rb, line 602
def add_jenkins_cartridge(rest_app)
  rest_app.add_cartridge(jenkins_client_cartridge_name)
end
add_jenkins_client_to(rest_app, messages) click to toggle source
# File lib/rhc/commands/app.rb, line 606
def add_jenkins_client_to(rest_app, messages)
  say "Setting up Jenkins build ... "
  successful, attempts, exit_code, exit_message = false, 1, 157, nil
  while (!successful && exit_code == 157 && attempts < MAX_RETRIES)
    begin
      cartridge = add_jenkins_cartridge(rest_app)
      successful = true

      success "done"
      messages.concat(cartridge.messages)

    rescue RHC::Rest::ServerErrorException => e
      if (e.code == 157)
        # error downloading Jenkins /jnlpJars/jenkins-cli.jar
        attempts += 1
        debug "Jenkins server could not be contacted, sleep and then retry: attempt #{attempts}\n    #{e.message}"
        Kernel.sleep(10)
      end
      exit_code = e.code
      exit_message = e.message
    rescue Exception => e
      # timeout and other exceptions
      exit_code = 1
      exit_message = e.message
    end
  end
  unless successful
    warn "not complete"
    add_issue("Jenkins client failed to install - #{exit_message}",
              "Install the jenkins client",
              "rhc add-cartridge jenkins-client -a #{rest_app.name}")
  end
end
app_action(action, *args) click to toggle source
# File lib/rhc/commands/app.rb, line 570
def app_action(action, *args)
  rest_app = find_app
  result = rest_app.send action, *args
  result
end
check_config!() click to toggle source
# File lib/rhc/commands/app.rb, line 545
def check_config!
  return if not interactive? or (!options.clean && config.has_local_config?) or (options.server && (options.rhlogin || options.token))
  RHC::EmbeddedWizard.new(config, options).run
end
check_domain!() click to toggle source
# File lib/rhc/commands/app.rb, line 550
def check_domain!
  if options.namespace
    rest_client.find_domain(options.namespace)
  else
    if rest_client.domains.empty?
      raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'rhc create-domain <namespace>' before creating applications." unless interactive?
      RHC::DomainWizard.new(config, options, rest_client).run
    end
    domain = rest_client.domains.first
    raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'rhc create-domain <namespace>' before creating applications." unless domain
    options.namespace = domain.name
    domain
  end
end
check_name!(name) click to toggle source
# File lib/rhc/commands/app.rb, line 536
def check_name!(name)
  return unless name.blank?

  paragraph{ say "When creating an application, you must provide a name and a cartridge from the list below:" }
  paragraph{ list_cartridges(standalone_cartridges) }

  raise ArgumentError, "Please specify the name of the application and the web cartridge to install"
end
check_sshkeys!() click to toggle source
# File lib/rhc/commands/app.rb, line 531
def check_sshkeys!
  return unless interactive?
  RHC::SSHWizard.new(rest_client, config, options).run
end
create_app(name, cartridges, rest_domain, gear_size=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil, deployment_type=nil, region=nil) click to toggle source
# File lib/rhc/commands/app.rb, line 576
def create_app(name, cartridges, rest_domain, gear_size=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil, deployment_type=nil, region=nil)
  app_options = {:cartridges => Array(cartridges)}
  app_options[:gear_profile] = gear_size if gear_size
  app_options[:scale] = scale if scale
  app_options[:initial_git_url] = from_code if from_code
  app_options[:debug] = true if @debug
  app_options[:environment_variables] = environment_variables.map{|i| i.to_hash}.group_by{|i| i[:name]}.values.map(&:last) if environment_variables.present?
  app_options[:auto_deploy] = auto_deploy if !auto_deploy.nil?
  app_options[:keep_deployments] = keep_deployments if keep_deployments
  app_options[:deployment_branch] = deployment_branch if deployment_branch
  app_options[:deployment_type] = deployment_type if deployment_type
  app_options[:region] = region if region
  debug "Creating application '#{name}' with these options - #{app_options.inspect}"
  rest_domain.add_application(name, app_options)
rescue RHC::Rest::Exception => e
  if e.code == 109
    paragraph{ say "Valid cartridge types:" }
    paragraph{ list_cartridges(standalone_cartridges) }
  end
  raise
end
dns_propagated?(host, sleep_time=2) click to toggle source
# File lib/rhc/commands/app.rb, line 640
def dns_propagated?(host, sleep_time=2)
  #
  # Confirm that the host exists in DNS
  #
  debug "Start checking for application dns @ '#{host}'"

  found = false

  # Allow DNS to propagate
  Kernel.sleep 5

  # Now start checking for DNS
  host_found = hosts_file_contains?(host) or
  1.upto(MAX_RETRIES) { |i|
    host_found = host_exists?(host)
    break found if host_found

    say "    retry # #{i} - Waiting for DNS: #{host}"
    Kernel.sleep sleep_time.to_i
    sleep_time *= DEFAULT_DELAY_THROTTLE
  }

  debug "End checking for application dns @ '#{host} - found=#{host_found}'"

  host_found
end
enable_jenkins?() click to toggle source
# File lib/rhc/commands/app.rb, line 667
def enable_jenkins?
  # legacy issue, commander 4.0.x will place the option in the hash with nil value (BZ878407)
  options.__hash__.has_key?(:enable_jenkins)
end
format_issues(indent) click to toggle source
# File lib/rhc/commands/app.rb, line 764
def format_issues(indent)
  return nil unless issues?

  indentation = " " * indent
  reasons = ""
  steps = ""

  @issues.each_with_index do |issue, i|
    reasons << "#{indentation}#{i+1}. #{issue[:reason].strip}\n"
    steps << "#{indentation}#{i+1}. #{issue[:commands_header].strip}\n"
    issue[:commands].each { |cmd| steps << "#{indentation}  $ #{cmd}\n" }
  end

  [reasons, steps]
end
gear_group_state(states) click to toggle source
# File lib/rhc/commands/app.rb, line 565
def gear_group_state(states)
  return states[0] if states.length == 1 || states.uniq.length == 1
  "#{states.select{ |s| s == 'started' }.count}/#{states.length} started"
end
issues?() click to toggle source
# File lib/rhc/commands/app.rb, line 780
def issues?
  not @issues.nil?
end
jenkins_app_name() click to toggle source
# File lib/rhc/commands/app.rb, line 672
def jenkins_app_name
  if options.enable_jenkins.is_a? String
    options.enable_jenkins
  end || "jenkins"
end
jenkins_cartridge_name() click to toggle source
# File lib/rhc/commands/app.rb, line 678
def jenkins_cartridge_name
  jenkins_cartridges.last.name
end
jenkins_client_cartridge_name() click to toggle source
# File lib/rhc/commands/app.rb, line 682
def jenkins_client_cartridge_name
  jenkins_client_cartridges.last.name
end
output_issues(rest_app) click to toggle source
# File lib/rhc/commands/app.rb, line 725
      def output_issues(rest_app)
        reasons, steps = format_issues(4)
        warn <<WARNING_OUTPUT
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
WARNING:  Your application was created successfully but had problems during
          configuration. Below is a list of the issues and steps you can
          take to complete the configuration of your application.

  Application URL: #{rest_app.app_url}

  Issues:
#{reasons}
  Steps to complete your configuration:
#{steps}
  If you continue to experience problems after completing these steps,
  you can try destroying and recreating the application:

    $ rhc app delete #{rest_app.name} --confirm

  Please contact us if you are unable to successfully create your
  application:

    Support - https://www.openshift.com/support

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

WARNING_OUTPUT
      end
require_one_web_cart() click to toggle source
# File lib/rhc/commands/app.rb, line 514
def require_one_web_cart
  lambda{ |carts|
    match, ambiguous = carts.partition{ |c| not c.is_a?(Array) }
    selected_web = match.any?{ |c| not c.only_in_existing? }
    possible_web = ambiguous.flatten.any?{ |c| not c.only_in_existing? }
    if not (selected_web or possible_web)
      section(:bottom => 1){ list_cartridges(standalone_cartridges) }
      raise RHC::CartridgeNotFoundException, "Every application needs a web cartridge to handle incoming web requests. Please provide the short name of one of the carts listed above."
    end
    if selected_web
      carts.map! &other_carts_only
    elsif possible_web && ambiguous.length == 1
      carts.map! &web_carts_only
    end
  }
end
run_nslookup(host) click to toggle source
# File lib/rhc/commands/app.rb, line 686
def run_nslookup(host)
  # :nocov:
  %xnslookup #{host}`
  $?.exitstatus == 0
  # :nocov:
end
run_ping(host) click to toggle source
# File lib/rhc/commands/app.rb, line 693
def run_ping(host)
  # :nocov:
  %xping #{host} -n 2`
  $?.exitstatus == 0
  # :nocov:
end
temporary_snapshot_filename(app_name) click to toggle source
# File lib/rhc/commands/app.rb, line 784
def temporary_snapshot_filename(app_name)
  "#{Dir.tmpdir}/#{app_name}_temp_clone.tar.gz"
end
windows_nslookup_bug?(rest_app) click to toggle source
# File lib/rhc/commands/app.rb, line 700
      def windows_nslookup_bug?(rest_app)
        windows_nslookup = run_nslookup(rest_app.host)
        windows_ping = run_ping(rest_app.host)

        if windows_nslookup and !windows_ping # this is related to BZ #826769
          issue = <<WINSOCKISSUE
We were unable to lookup your hostname (#{rest_app.host})
in a reasonable amount of time.  This can happen periodically and may
take up to 10 extra minutes to propagate depending on where you are in the
world. This may also be related to an issue with Winsock on Windows [1][2].
We recommend you wait a few minutes then clone your git repository manually.

[1] http://support.microsoft.com/kb/299357
[2] http://support.microsoft.com/kb/811259
WINSOCKISSUE
          add_issue(issue,
                    "Clone your git repo",
                    "rhc git-clone #{rest_app.name}")

          return true
        end

        false
      end