cloudfoundry/cf

View on GitHub
lib/cf/cli/app/start.rb

Summary

Maintainability
B
6 hrs
Test Coverage
require "cf/cli/app/base"

module CF::App
  class Start < Base
    APP_CHECK_LIMIT = 900

    desc "Start an application"
    group :apps, :manage
    input :apps, :desc => "Applications to start", :argument => :splat,
          :singular => :app, :from_given => by_name(:app)
    input :debug_mode, :desc => "Debug mode to start in", :aliases => "-d"
    input :all, :desc => "Start all applications", :default => false
    def start
      apps = input[:all] ? client.apps : input[:apps]
      fail "No applications given." if apps.empty?

      spaced(apps) do |app|
        app = filter(:start_app, app)

        switch_mode(app, input[:debug_mode])

        if app.started?
          err "Application #{b(app.name)} is already started."
          next
        end

        log = start_app(app)
        stream_start_log(log) if log
        check_application(app)

        if !app.debug_mode.nil? && app.debug_mode != "none" && !quiet?
          line
          invoke :instances, :app => app
        end
      end
    end

    private

    def start_app(app)
      log = nil
      with_progress("Preparing to start #{c(app.name, :name)}") do
        app.start! do |url|
          log = url
        end
      end
      log
    end

    def stream_start_log(log)
      offset = 0

      while true
        begin
          client.stream_url(log + "&tail&tail_offset=#{offset}") do |out|
            offset += out.size
            print out
          end
        rescue Timeout::Error
        end
      end
    rescue CFoundry::APIError
    end

    # set app debug mode, ensuring it's valid, and shutting it down
    def switch_mode(app, mode)
      mode = "run" if mode == "" # no value given

      return false if app.debug == mode

      if mode == "none"
        with_progress("Removing debug mode") do
          app.debug = mode
          app.stop! if app.started?
        end

        return true
      end

      with_progress("Switching mode to #{c(mode, :name)}") do |s|
        app.debug = mode
        app.stop! if app.started?
      end
    end

    def check_application(app)
      if app.debug == "suspend"
        line "Application is in suspended debugging mode."
        line "It will wait for you to attach to it before starting."
        return
      end

      print("Checking status of app '#{c(app.name, :name)}'...")

      seconds = 0
      @first_time_after_staging_succeeded = true

      begin
        instances = []
        while true
          if any_instance_flapping?(instances) || seconds == APP_CHECK_LIMIT
            err "Push unsuccessful."
            line "#{c("TIP: The system will continue to attempt restarting all requested app instances that have crashed. Try 'cf app' to monitor app status. To troubleshoot crashes, try 'cf events' and 'cf crashlogs'.", :warning)}"
            return
          end

          begin
            return unless instances = app.instances

            indented { print_instances_summary(instances) }

            if one_instance_running?(instances)
              line "#{c("Push successful! App '#{app.name}' available at #{app.host}.#{app.domain}", :good)}"
              unless all_instances_running?(instances)
                line "#{c("TIP: The system will continue to start all requested app instances. Try 'cf app' to monitor app status.", :warning)}"
              end
              return
            end
          rescue CFoundry::NotStaged
            print (".")
          end

          sleep 1
          seconds += 1
        end
      rescue CFoundry::StagingError
        err "Application failed to stage"
      end
    end

    def one_instance_running?(instances)
      instances.any? { |i| i.state == "RUNNING" }
    end

    def all_instances_running?(instances)
      instances.all? { |i| i.state == "RUNNING" }
    end

    def any_instance_flapping?(instances)
      instances.any? { |i| i.state == "FLAPPING" }
    end

    def print_instances_summary(instances)

      if @first_time_after_staging_succeeded
        line
        @first_time_after_staging_succeeded = false
      end

      counts = Hash.new { 0 }
      instances.each do |i|
        counts[i.state] += 1
      end

      states = []
      %w{RUNNING STARTING FLAPPING DOWN}.each do |state|
        if (num = counts[state]) > 0
          states << "#{b(num)} #{c(for_output(state), state_color(state))}"
        end
      end

      total = instances.count
      running = counts["RUNNING"].to_s.rjust(total.to_s.size)

      ratio = "#{running}#{d(" of ")}#{total} instances running"
      line "#{ratio} (#{states.join(", ")})"
    end

    private
    def for_output(state)
      state = "CRASHING" if state == "FLAPPING"
      state.downcase
    end
  end
end