lib/cli/commands/apps.rb in af-0.3.13.beta.5 vs lib/cli/commands/apps.rb in af-0.3.16.1
- old
+ new
@@ -1,15 +1,21 @@
require 'digest/sha1'
require 'fileutils'
+require 'pathname'
require 'tempfile'
require 'tmpdir'
require 'set'
+require "uuidtools"
+require 'socket'
module VMC::Cli::Command
class Apps < Base
include VMC::Cli::ServicesHelper
+ include VMC::Cli::ManifestHelper
+ include VMC::Cli::TunnelHelper
+ include VMC::Cli::ConsoleHelper
def list
apps = client.apps
apps.sort! {|a, b| a[:name] <=> b[:name] }
return display JSON.pretty_generate(apps || []) if @options[:json]
@@ -34,121 +40,91 @@
# Numerators are in secs
TICKER_TICKS = 25/SLEEP_TIME
HEALTH_TICKS = 5/SLEEP_TIME
TAIL_TICKS = 45/SLEEP_TIME
GIVEUP_TICKS = 120/SLEEP_TIME
- YES_SET = Set.new(["y", "Y", "yes", "YES"])
- def start(appname, push = false)
- app = client.app_info(appname)
+ def info(what, default=nil)
+ @options[what] || (@app_info && @app_info[what.to_s]) || default
+ end
- return display "Application '#{appname}' could not be found".red if app.nil?
- return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
+ def console(appname, interactive=true)
+ unless defined? Caldecott
+ display "To use `vmc rails-console', you must first install Caldecott:"
+ display ""
+ display "\tgem install caldecott"
+ display ""
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
+ display "will provide one. If you're on Windows, try DevKit."
+ display ""
+ display "This manual step will be removed in the future."
+ display ""
+ err "Caldecott is not installed."
+ end
- if @options[:debug]
- runtimes = client.runtimes_info
- return display "Cannot get runtime information." unless runtimes
+ #Make sure there is a console we can connect to first
+ conn_info = console_connection_info appname
- runtime = runtimes[app[:staging][:stack].to_sym]
- return display "Unknown runtime." unless runtime
+ port = pick_tunnel_port(@options[:port] || 20000)
- unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
- modes = runtime[:debug_modes] || []
+ raise VMC::Client::AuthError unless client.logged_in?
- display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
+ if not tunnel_pushed?
+ display "Deploying tunnel application '#{tunnel_appname}'."
+ auth = UUIDTools::UUID.random_create.to_s
+ push_caldecott(auth)
+ start_caldecott
+ else
+ auth = tunnel_auth
+ end
- if push
- display "Try `vmc start' with one of the following modes: #{modes.inspect}"
- else
- display "Available modes: #{modes.inspect}"
- end
-
- return
- end
+ if not tunnel_healthy?(auth)
+ display "Redeploying tunnel application '#{tunnel_appname}'."
+ # We don't expect caldecott not to be running, so take the
+ # most aggressive restart method.. delete/re-push
+ client.delete_app(tunnel_appname)
+ invalidate_tunnel_app_info
+ push_caldecott(auth)
+ start_caldecott
end
- banner = 'Staging Application: '
- display banner, false
+ start_tunnel(port, conn_info, auth)
+ wait_for_tunnel_start(port)
+ start_local_console(port, appname) if interactive
+ port
+ end
- t = Thread.new do
- count = 0
- while count < TAIL_TICKS do
- display '.', false
- sleep SLEEP_TIME
- count += 1
+ def start(appname=nil, push=false)
+ if appname
+ do_start(appname, push)
+ else
+ each_app do |name|
+ do_start(name, push)
end
end
+ end
- app[:state] = 'STARTED'
- app[:debug] = @options[:debug]
- client.update_app(appname, app)
+ def stop(appname=nil)
+ if appname
+ do_stop(appname)
+ else
+ reversed = []
+ each_app do |name|
+ reversed.unshift name
+ end
- Thread.kill(t)
- clear(LINE_LENGTH)
- display "#{banner}#{'OK'.green}"
-
- banner = 'Starting Application: '
- display banner, false
-
- count = log_lines_displayed = 0
- failed = false
- start_time = Time.now.to_i
-
- loop do
- display '.', false unless count > TICKER_TICKS
- sleep SLEEP_TIME
- begin
- break if app_started_properly(appname, count > HEALTH_TICKS)
- if !crashes(appname, false, start_time).empty?
- # Check for the existance of crashes
- display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
- grab_crash_logs(appname, '0', true)
- if push and !no_prompt
- display "\n"
- delete_app(appname, false) if ask "Delete the application?", :default => true
- end
- failed = true
- break
- elsif count > TAIL_TICKS
- log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
- end
- rescue => e
- err(e.message, '')
+ reversed.each do |name|
+ do_stop(name)
end
- count += 1
- if count > GIVEUP_TICKS # 2 minutes
- display "\nApplication is taking too long to start, check your logs".yellow
- break
- end
end
- exit(false) if failed
- clear(LINE_LENGTH)
- display "#{banner}#{'OK'.green}"
end
- def stop(appname)
- app = client.app_info(appname)
- return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
- display 'Stopping Application: ', false
- app[:state] = 'STOPPED'
- client.update_app(appname, app)
- display 'OK'.green
- end
-
- def restart(appname)
+ def restart(appname=nil)
stop(appname)
start(appname)
end
- def rename(appname, newname)
- app = client.app_info(appname)
- app[:name] = newname
- display 'Renaming Appliction: '
- client.update_app(newname, app)
- display 'OK'.green
- end
-
def mem(appname, memsize=nil)
app = client.app_info(appname)
mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
memsize = normalize_mem(memsize) if memsize
@@ -183,93 +159,49 @@
app = client.app_info(appname)
uris = app[:uris] || []
uris << url
app[:uris] = uris
client.update_app(appname, app)
- display "Succesfully mapped url".green
+ display "Successfully mapped url".green
end
def unmap(appname, url)
app = client.app_info(appname)
uris = app[:uris] || []
url = url.gsub(/^http(s*):\/\//i, '')
deleted = uris.delete(url)
err "Invalid url" unless deleted
app[:uris] = uris
client.update_app(appname, app)
- display "Succesfully unmapped url".green
-
+ display "Successfully unmapped url".green
end
def delete(appname=nil)
force = @options[:force]
if @options[:all]
- if no_prompt || force || ask("Delete ALL applications and services?", :default => false)
+ if no_prompt || force || ask("Delete ALL applications?", :default => false)
apps = client.apps
apps.each { |app| delete_app(app[:name], force) }
end
else
err 'No valid appname given' unless appname
delete_app(appname, force)
end
end
- def delete_app(appname, force)
- app = client.app_info(appname)
- services_to_delete = []
- app_services = app[:services]
- services_apps_hash = provisioned_services_apps_hash
- app_services.each { |service|
- del_service = force && no_prompt
- unless no_prompt || force
- del_service = ask(
- "Provisioned service [#{service}] detected, would you like to delete it?",
- :default => false
- )
-
- if del_service
- apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
- if apps_using_service.size > 0
- del_service = ask(
- "Provisioned service [#{service}] is also used by #{apps_using_service.size == 1 ? "app" : "apps"} #{apps_using_service.entries}, are you sure you want to delete it?",
- :default => false
- )
- end
- end
- end
- services_to_delete << service if del_service
- }
-
- display "Deleting application [#{appname}]: ", false
- client.delete_app(appname)
- display 'OK'.green
-
- services_to_delete.each do |s|
- delete_service_banner(s)
- end
- end
-
- def all_files(appname, path)
- instances_info_envelope = client.app_instances(appname)
- return if instances_info_envelope.is_a?(Array)
- instances_info = instances_info_envelope[:instances] || []
- instances_info.each do |entry|
- content = client.app_files(appname, path, entry[:index])
- display_logfile(path, content, entry[:index], "====> [#{entry[:index]}: #{path}] <====\n".bold)
- end
- end
-
def files(appname, path='/')
return all_files(appname, path) if @options[:all] && !@options[:instance]
instance = @options[:instance] || '0'
content = client.app_files(appname, path, instance)
display content
- rescue VMC::Client::NotFound => e
+ rescue VMC::Client::NotFound, VMC::Client::TargetError
err 'No such file or directory'
end
def logs(appname)
+ # Check if we have an app before progressing further
+ client.app_info(appname)
return grab_all_logs(appname) if @options[:all] && !@options[:instance]
instance = @options[:instance] || '0'
grab_logs(appname, instance)
end
@@ -312,199 +244,71 @@
instance = @options[:instance] || '0'
grab_crash_logs(appname, instance)
end
def instances(appname, num=nil)
- if (num)
+ if num
change_instances(appname, num)
else
get_instances(appname)
end
end
- def stats(appname)
- stats = client.app_stats(appname)
- return display JSON.pretty_generate(stats) if @options[:json]
-
- stats_table = table do |t|
- t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
- stats.each do |entry|
- index = entry[:instance]
- stat = entry[:stats]
- hp = "#{stat[:host]}:#{stat[:port]}"
- uptime = uptime_string(stat[:uptime])
- usage = stat[:usage]
- if usage
- cpu = usage[:cpu]
- mem = (usage[:mem] * 1024) # mem comes in K's
- disk = usage[:disk]
- end
- mem_quota = stat[:mem_quota]
- disk_quota = stat[:disk_quota]
- mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
- disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
- cpu = cpu ? cpu.to_s : 'NA'
- cpu = "#{cpu}% (#{stat[:cores]})"
- t << [index, cpu, mem, disk, uptime]
+ def stats(appname=nil)
+ if appname
+ display "\n", false
+ do_stats(appname)
+ else
+ each_app do |n|
+ display "\n#{n}:"
+ do_stats(n)
end
end
- display "\n"
- if stats.empty?
- display "No running instances for [#{appname}]".yellow
- else
- display stats_table
- end
end
- def update(appname)
- app = client.app_info(appname)
- if @options[:canary]
- display "[--canary] is deprecated and will be removed in a future version".yellow
- end
- path = @options[:path] || '.'
- upload_app_bits(appname, path)
- restart appname if app[:state] == 'STARTED'
- end
-
- def push(appname=nil)
- instances = @options[:instances] || 1
- exec = @options[:exec] || 'thin start'
- ignore_framework = @options[:noframework]
- no_start = @options[:nostart]
-
- path = @options[:path] || '.'
- appname ||= @options[:name]
- mem, memswitch = nil, @options[:mem]
- memswitch = normalize_mem(memswitch) if memswitch
- url = @options[:url]
-
- # Check app existing upfront if we have appname
- app_checked = false
+ def update(appname=nil)
if appname
- err "Application '#{appname}' already exists, use update" if app_exists?(appname)
- app_checked = true
+ app = client.app_info(appname)
+ if @options[:canary]
+ display "[--canary] is deprecated and will be removed in a future version".yellow
+ end
+ upload_app_bits(appname, @path)
+ restart appname if app[:state] == 'STARTED'
else
- raise VMC::Client::AuthError unless client.logged_in?
- end
+ each_app do |name|
+ display "Updating application '#{name}'..."
- # check if we have hit our app limit
- check_app_limit
-
- # check memsize here for capacity
- if memswitch && !no_start
- check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
- end
-
- unless no_prompt || @options[:path]
- unless ask('Would you like to deploy from the current directory?', :default => true)
- path = ask('Please enter in the deployment path')
+ app = client.app_info(name)
+ upload_app_bits(name, @application)
+ restart name if app[:state] == 'STARTED'
end
end
+ end
- path = File.expand_path(path)
- check_deploy_directory(path)
-
- appname ||= ask("Application Name") unless no_prompt
- err "Application Name required." if appname.nil? || appname.empty?
-
- if !app_checked and app_exists?(appname)
- err "Application '#{appname}' already exists, use update or delete."
- end
-
- default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}"
-
- unless no_prompt || url
- url = ask(
- "Application Deployed URL",
- :default => default_url
+ def push(appname=nil)
+ unless no_prompt || @options[:path]
+ proceed = ask(
+ 'Would you like to deploy from the current directory?',
+ :default => true
)
- # common error case is for prompted users to answer y or Y or yes or
- # YES to this ask() resulting in an unintended URL of y. Special case
- # this common error
- url = nil if YES_SET.member? url
- end
-
- url ||= default_url
-
- # Detect the appropriate framework.
- framework = nil
- unless ignore_framework
- framework = VMC::Cli::Framework.detect(path)
-
- if prompt_ok and framework
- framework_correct =
- ask("Detected a #{framework}, is this correct?", :default => true)
+ unless proceed
+ @path = ask('Deployment path')
end
-
- if prompt_ok && (framework.nil? || !framework_correct)
- display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
- framework = VMC::Cli::Framework.lookup(
- ask(
- "Select Application Type",
- { :indexed => true,
- :choices => VMC::Cli::Framework.known_frameworks
- }
- )
- )
- display "Selected #{framework}"
- end
- # Framework override, deprecated
- exec = framework.exec if framework && framework.exec
- else
- framework = VMC::Cli::Framework.new
end
- err "Application Type undetermined for path '#{path}'" unless framework
-
- if memswitch
- mem = memswitch
- elsif prompt_ok
- mem = ask("Memory Reservation",
- :default => framework.memory, :choices => mem_choices)
- else
- mem = framework.memory
+ pushed = false
+ each_app(false) do |name|
+ display "Pushing application '#{name}'..." if name
+ do_push(name)
+ pushed = true
end
- # Set to MB number
- mem_quota = mem_choice_to_quota(mem)
-
- # check memsize here for capacity
- check_has_capacity_for(mem_quota * instances) unless no_start
-
- display 'Creating Application: ', false
-
- manifest = {
- :name => "#{appname}",
- :staging => {
- :framework => framework.name,
- :runtime => @options[:runtime]
- },
- :uris => [url],
- :instances => instances,
- :resources => {
- :memory => mem_quota
- },
- }
-
- # Send the manifest to the cloud controller
- client.create_app(appname, manifest)
- display 'OK'.green
-
- # Services check
- unless no_prompt || @options[:noservices]
- services = client.services_info
- unless services.empty?
- proceed = ask("Would you like to bind any services to '#{appname}'?", :default => false)
- bind_services(appname, services) if proceed
- end
+ unless pushed
+ @application = @path
+ do_push(appname)
end
-
- # Stage and upload the app bits.
- upload_app_bits(appname, path)
-
- start(appname, true) unless no_start
end
def environment(appname)
app = client.app_info(appname)
env = app[:env] || []
@@ -565,185 +369,156 @@
false
end
def check_deploy_directory(path)
err 'Deployment path does not exist' unless File.exists? path
- err 'Deployment path is not a directory' unless File.directory? path
return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
end
+ def check_unreachable_links(path)
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
+
+ pwd = Pathname.pwd
+
+ abspath = File.expand_path(path)
+ unreachable = []
+ files.each do |f|
+ file = Pathname.new(f)
+ if file.symlink? && !file.realpath.to_s.start_with?(abspath)
+ unreachable << file.relative_path_from(pwd)
+ end
+ end
+
+ unless unreachable.empty?
+ root = Pathname.new(path).relative_path_from(pwd)
+ err "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
+ end
+ end
+
+ def find_sockets(path)
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
+ files && files.select { |f| File.socket? f }
+ end
+
def upload_app_bits(appname, path)
display 'Uploading Application:'
upload_file, file = "#{Dir.tmpdir}/#{appname}.zip", nil
FileUtils.rm_f(upload_file)
explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
- Dir.chdir(path) do
- # Stage the app appropriately and do the appropriate fingerprinting, etc.
- if war_file = Dir.glob('*.war').first
- VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
- else
- FileUtils.mkdir(explode_dir)
- files = Dir.glob('{*,.[^\.]*}')
- # Do not process .git files
- files.delete('.git') if files
- FileUtils.cp_r(files, explode_dir)
- end
+ if path =~ /\.(war|zip)$/
+ #single file that needs unpacking
+ VMC::Cli::ZipUtil.unpack(path, explode_dir)
+ elsif !File.directory? path
+ #single file that doesn't need unpacking
+ FileUtils.mkdir(explode_dir)
+ FileUtils.cp(path,explode_dir)
+ else
+ Dir.chdir(path) do
+ # Stage the app appropriately and do the appropriate fingerprinting, etc.
+ if war_file = Dir.glob('*.war').first
+ VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
+ elsif zip_file = Dir.glob('*.zip').first
+ VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
+ else
+ check_unreachable_links(path)
+ FileUtils.mkdir(explode_dir)
- # Send the resource list to the cloudcontroller, the response will tell us what it already has..
- unless @options[:noresources]
- display ' Checking for available resources: ', false
- fingerprints = []
- total_size = 0
- resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
- resource_files.each do |filename|
- next if (File.directory?(filename) || !File.exists?(filename))
- fingerprints << {
- :size => File.size(filename),
- :sha1 => Digest::SHA1.file(filename).hexdigest,
- :fn => filename
- }
- total_size += File.size(filename)
- end
+ files = Dir.glob('{*,.[^\.]*}')
- # Check to see if the resource check is worth the round trip
- if (total_size > (64*1024)) # 64k for now
- # Send resource fingerprints to the cloud controller
- appcloud_resources = client.check_resources(fingerprints)
- end
- display 'OK'.green
+ # Do not process .git files
+ files.delete('.git') if files
- if appcloud_resources
- display ' Processing resources: ', false
- # We can then delete what we do not need to send.
- appcloud_resources.each do |resource|
- FileUtils.rm_f resource[:fn]
- # adjust filenames sans the explode_dir prefix
- resource[:fn].sub!("#{explode_dir}/", '')
+ FileUtils.cp_r(files, explode_dir)
+
+ find_sockets(explode_dir).each do |s|
+ File.delete s
end
- display 'OK'.green
end
+ end
+ end
+ # Send the resource list to the cloudcontroller, the response will tell us what it already has..
+ unless @options[:noresources]
+ display ' Checking for available resources: ', false
+ fingerprints = []
+ total_size = 0
+ resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
+ resource_files.each do |filename|
+ next if (File.directory?(filename) || !File.exists?(filename))
+ fingerprints << {
+ :size => File.size(filename),
+ :sha1 => Digest::SHA1.file(filename).hexdigest,
+ :fn => filename
+ }
+ total_size += File.size(filename)
end
- # If no resource needs to be sent, add an empty file to ensure we have
- # a multi-part request that is expected by nginx fronting the CC.
- if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
- Dir.chdir(explode_dir) do
- File.new(".__empty__", "w")
- end
+ # Check to see if the resource check is worth the round trip
+ if (total_size > (64*1024)) # 64k for now
+ # Send resource fingerprints to the cloud controller
+ appcloud_resources = client.check_resources(fingerprints)
end
- # Perform Packing of the upload bits here.
- display ' Packing application: ', false
- VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
display 'OK'.green
- upload_size = File.size(upload_file);
- if upload_size > 1024*1024
- upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
- elsif upload_size > 0
- upload_size = (upload_size/1024.0).round.to_s + 'K'
- else
- upload_size = '0K'
+ if appcloud_resources
+ display ' Processing resources: ', false
+ # We can then delete what we do not need to send.
+ appcloud_resources.each do |resource|
+ FileUtils.rm_f resource[:fn]
+ # adjust filenames sans the explode_dir prefix
+ resource[:fn].sub!("#{explode_dir}/", '')
+ end
+ display 'OK'.green
end
- upload_str = " Uploading (#{upload_size}): "
- display upload_str, false
+ end
- FileWithPercentOutput.display_str = upload_str
- FileWithPercentOutput.upload_size = File.size(upload_file);
- file = FileWithPercentOutput.open(upload_file, 'rb')
+ # If no resource needs to be sent, add an empty file to ensure we have
+ # a multi-part request that is expected by nginx fronting the CC.
+ if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
+ Dir.chdir(explode_dir) do
+ File.new(".__empty__", "w")
+ end
+ end
+ # Perform Packing of the upload bits here.
+ display ' Packing application: ', false
+ VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
+ display 'OK'.green
- client.upload_app(appname, file, appcloud_resources)
- display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
-
- display 'Push Status: ', false
- display 'OK'.green
+ upload_size = File.size(upload_file);
+ if upload_size > 1024*1024
+ upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
+ elsif upload_size > 0
+ upload_size = (upload_size/1024.0).round.to_s + 'K'
+ else
+ upload_size = '0K'
end
- ensure
- # Cleanup if we created an exploded directory.
- FileUtils.rm_f(upload_file) if upload_file
- FileUtils.rm_rf(explode_dir) if explode_dir
- end
+ upload_str = " Uploading (#{upload_size}): "
+ display upload_str, false
- def choose_existing_service(appname, user_services)
- return unless prompt_ok
+ FileWithPercentOutput.display_str = upload_str
+ FileWithPercentOutput.upload_size = File.size(upload_file);
+ file = FileWithPercentOutput.open(upload_file, 'rb')
- display "The following provisioned services are available"
- name = ask(
- "Please select one you which to prevision",
- { :indexed => true,
- :choices => user_services.collect { |s| s[:name] }
- }
- )
+ client.upload_app(appname, file, appcloud_resources)
+ display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
- bind_service_banner(name, appname, false)
+ display 'Push Status: ', false
+ display 'OK'.green
- true
+ ensure
+ # Cleanup if we created an exploded directory.
+ FileUtils.rm_f(upload_file) if upload_file
+ FileUtils.rm_rf(explode_dir) if explode_dir
end
- def choose_new_service(appname, services)
- return unless prompt_ok
-
- display "The following system services are available"
-
- vendor = ask(
- "Please select one you wish to provision",
- { :indexed => true,
- :choices =>
- services.values.collect { |type|
- type.keys.collect(&:to_s)
- }.flatten.sort!
- }
- )
-
- default_name = random_service_name(vendor)
- service_name = ask("Specify the name of the service",
- :default => default_name)
-
- create_service_banner(vendor, service_name)
- bind_service_banner(service_name, appname)
- end
-
- def bind_services(appname, services)
- user_services = client.services
-
- selected_existing = false
- unless no_prompt || user_services.empty?
- if ask("Would you like to use an existing provisioned service?",
- :default => false)
- selected_existing = choose_existing_service(appname, user_services)
- end
- end
-
- # Create a new service and bind it here
- unless selected_existing
- choose_new_service(appname, services)
- end
- end
-
- def provisioned_services_apps_hash
- apps = client.apps
- services_apps_hash = {}
- apps.each {|app|
- app[:services].each { |svc|
- svc_apps = services_apps_hash[svc]
- unless svc_apps
- svc_apps = Set.new
- services_apps_hash[svc] = svc_apps
- end
- svc_apps.add(app[:name])
- } unless app[:services] == nil
- }
- services_apps_hash
- end
-
def check_app_limit
usage = client_info[:usage]
limits = client_info[:limits]
return unless usage and limits and limits[:apps]
if limits[:apps] == usage[:apps]
@@ -894,11 +669,12 @@
end
end
def display_logfile(path, content, instance='0', banner=nil)
banner ||= "====> #{path} <====\n\n"
- if content && !content.empty?
+
+ unless content.empty?
display banner
prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
unless prefix
display content
else
@@ -907,46 +683,49 @@
end
display ''
end
end
- def log_file_paths
- %w[logs/stderr.log logs/stdout.log logs/startup.log]
- end
-
def grab_all_logs(appname)
instances_info_envelope = client.app_instances(appname)
return if instances_info_envelope.is_a?(Array)
instances_info = instances_info_envelope[:instances] || []
instances_info.each do |entry|
grab_logs(appname, entry[:index])
end
end
def grab_logs(appname, instance)
- log_file_paths.each do |path|
+ files_under(appname, instance, "/logs").each do |path|
begin
content = client.app_files(appname, path, instance)
- rescue
+ display_logfile(path, content, instance)
+ rescue VMC::Client::NotFound, VMC::Client::TargetError
end
- display_logfile(path, content, instance)
end
end
+ def files_under(appname, instance, path)
+ client.app_files(appname, path, instance).split("\n").collect do |l|
+ "#{path}/#{l.split[0]}"
+ end
+ rescue VMC::Client::NotFound, VMC::Client::TargetError
+ []
+ end
+
def grab_crash_logs(appname, instance, was_staged=false)
# stage crash info
crashes(appname, false) unless was_staged
instance ||= '0'
map = VMC::Cli::Config.instances
instance = map[instance] if map[instance]
- ['/logs/err.log', '/logs/staging.log', 'logs/stderr.log', 'logs/stdout.log', 'logs/startup.log'].each do |path|
- begin
- content = client.app_files(appname, path, instance)
- rescue
- end
+ (files_under(appname, instance, "/logs") +
+ files_under(appname, instance, "/app/logs") +
+ files_under(appname, instance, "/app/log")).each do |path|
+ content = client.app_files(appname, path, instance)
display_logfile(path, content, instance)
end
end
def grab_startup_tail(appname, since = 0)
@@ -960,11 +739,358 @@
tail = response_lines[since, lines] || []
new_lines = tail.size
display tail.join("\n") if new_lines > 0
end
since + new_lines
+ rescue VMC::Client::NotFound, VMC::Client::TargetError
+ 0
end
- rescue
+
+ def provisioned_services_apps_hash
+ apps = client.apps
+ services_apps_hash = {}
+ apps.each {|app|
+ app[:services].each { |svc|
+ svc_apps = services_apps_hash[svc]
+ unless svc_apps
+ svc_apps = Set.new
+ services_apps_hash[svc] = svc_apps
+ end
+ svc_apps.add(app[:name])
+ } unless app[:services] == nil
+ }
+ services_apps_hash
+ end
+
+ def delete_app(appname, force)
+ app = client.app_info(appname)
+ services_to_delete = []
+ app_services = app[:services]
+ services_apps_hash = provisioned_services_apps_hash
+ app_services.each { |service|
+ del_service = force && no_prompt
+ unless no_prompt || force
+ del_service = ask(
+ "Provisioned service [#{service}] detected, would you like to delete it?",
+ :default => false
+ )
+
+ if del_service
+ apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
+ if apps_using_service.size > 0
+ del_service = ask(
+ "Provisioned service [#{service}] is also used by #{apps_using_service.size == 1 ? "app" : "apps"} #{apps_using_service.entries}, are you sure you want to delete it?",
+ :default => false
+ )
+ end
+ end
+ end
+ services_to_delete << service if del_service
+ }
+
+ display "Deleting application [#{appname}]: ", false
+ client.delete_app(appname)
+ display 'OK'.green
+
+ services_to_delete.each do |s|
+ delete_service_banner(s)
+ end
+ end
+
+ def do_start(appname, push=false)
+ app = client.app_info(appname)
+ return display "Application '#{appname}' could not be found".red if app.nil?
+ return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
+
+
+
+ if @options[:debug]
+ runtimes = client.runtimes_info
+ return display "Cannot get runtime information." unless runtimes
+
+ runtime = runtimes[app[:staging][:stack].to_sym]
+ return display "Unknown runtime." unless runtime
+
+ unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
+ modes = runtime[:debug_modes] || []
+
+ display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
+
+ if push
+ display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
+ else
+ display "Available modes: #{modes.inspect}"
+ end
+
+ return
+ end
+ end
+
+ banner = "Staging Application '#{appname}': "
+ display banner, false
+
+ t = Thread.new do
+ count = 0
+ while count < TAIL_TICKS do
+ display '.', false
+ sleep SLEEP_TIME
+ count += 1
+ end
+ end
+
+ app[:state] = 'STARTED'
+ app[:debug] = @options[:debug]
+ app[:console] = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model]).console
+ client.update_app(appname, app)
+
+ Thread.kill(t)
+ clear(LINE_LENGTH)
+ display "#{banner}#{'OK'.green}"
+
+ banner = "Starting Application '#{appname}': "
+ display banner, false
+
+ count = log_lines_displayed = 0
+ failed = false
+ start_time = Time.now.to_i
+
+ loop do
+ display '.', false unless count > TICKER_TICKS
+ sleep SLEEP_TIME
+
+ break if app_started_properly(appname, count > HEALTH_TICKS)
+
+ if !crashes(appname, false, start_time).empty?
+ # Check for the existance of crashes
+ display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
+ grab_crash_logs(appname, '0', true)
+ if push and !no_prompt
+ display "\n"
+ delete_app(appname, false) if ask "Delete the application?", :default => true
+ end
+ failed = true
+ break
+ elsif count > TAIL_TICKS
+ log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
+ end
+
+ count += 1
+ if count > GIVEUP_TICKS # 2 minutes
+ display "\nApplication is taking too long to start, check your logs".yellow
+ break
+ end
+ end
+ exit(false) if failed
+ clear(LINE_LENGTH)
+ display "#{banner}#{'OK'.green}"
+ end
+
+ def do_stop(appname)
+ app = client.app_info(appname)
+ return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
+ display "Stopping Application '#{appname}': ", false
+ app[:state] = 'STOPPED'
+ client.update_app(appname, app)
+ display 'OK'.green
+ end
+
+ def do_push(appname=nil)
+ unless @app_info || no_prompt
+ @manifest = { "applications" => { @path => { "name" => appname } } }
+
+ interact
+
+ if ask("Would you like to save this configuration?", :default => false)
+ save_manifest
+ end
+
+ resolve_manifest(@manifest)
+
+ @app_info = @manifest["applications"][@path]
+ end
+
+ instances = info(:instances, 1)
+ exec = info(:exec, 'thin start')
+
+ ignore_framework = @options[:noframework]
+ no_start = @options[:nostart]
+
+ appname ||= info(:name)
+ url = info(:url) || info(:urls)
+ mem, memswitch = nil, info(:mem)
+ memswitch = normalize_mem(memswitch) if memswitch
+ command = info(:command)
+ runtime = info(:runtime)
+
+ # Check app existing upfront if we have appname
+ app_checked = false
+ if appname
+ err "Application '#{appname}' already exists, use update" if app_exists?(appname)
+ app_checked = true
+ else
+ raise VMC::Client::AuthError unless client.logged_in?
+ end
+
+ # check if we have hit our app limit
+ check_app_limit
+ # check memsize here for capacity
+ if memswitch && !no_start
+ check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
+ end
+
+ appname ||= ask("Application Name") unless no_prompt
+ err "Application Name required." if appname.nil? || appname.empty?
+
+ check_deploy_directory(@application)
+
+ if !app_checked and app_exists?(appname)
+ err "Application '#{appname}' already exists, use update or delete."
+ end
+
+ if ignore_framework
+ framework = VMC::Cli::Framework.new
+ elsif f = info(:framework)
+ info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
+
+ framework = VMC::Cli::Framework.create(f["name"], info)
+ exec = framework.exec if framework && framework.exec
+ else
+ framework = detect_framework(prompt_ok)
+ end
+
+ err "Application Type undetermined for path '#{@application}'" unless framework
+
+ if not runtime
+ default_runtime = framework.default_runtime @application
+ runtime = detect_runtime(default_runtime, !no_prompt) if framework.prompt_for_runtime?
+ end
+ command = ask("Start Command") if !command && framework.require_start_command?
+
+ default_url = "None"
+ default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if framework.require_url?
+
+
+ unless no_prompt || url || !framework.require_url?
+ url = ask(
+ "Application Deployed URL",
+ :default => default_url
+ )
+
+ # common error case is for prompted users to answer y or Y or yes or
+ # YES to this ask() resulting in an unintended URL of y. Special case
+ # this common error
+ url = nil if YES_SET.member? url
+ end
+ url = nil if url == "None"
+ default_url = nil if default_url == "None"
+ url ||= default_url
+
+ if memswitch
+ mem = memswitch
+ elsif prompt_ok
+ mem = ask("Memory Reservation",
+ :default => framework.memory(runtime),
+ :choices => mem_choices)
+ else
+ mem = framework.memory runtime
+ end
+
+ # Set to MB number
+ mem_quota = mem_choice_to_quota(mem)
+
+ # check memsize here for capacity
+ check_has_capacity_for(mem_quota * instances) unless no_start
+
+ display 'Creating Application: ', false
+
+ manifest = {
+ :name => "#{appname}",
+ :staging => {
+ :framework => framework.name,
+ :runtime => runtime
+ },
+ :uris => Array(url),
+ :instances => instances,
+ :resources => {
+ :memory => mem_quota
+ }
+ }
+ manifest[:staging][:command] = command if command
+
+ # Send the manifest to the cloud controller
+ client.create_app(appname, manifest)
+ display 'OK'.green
+
+
+ existing = Set.new(client.services.collect { |s| s[:name] })
+
+ if @app_info && services = @app_info["services"]
+ services.each do |name, info|
+ unless existing.include? name
+ create_service_banner(info["type"], name, true)
+ end
+
+ bind_service_banner(name, appname)
+ end
+ end
+
+ # Stage and upload the app bits.
+ upload_app_bits(appname, @application)
+
+ start(appname, true) unless no_start
+ end
+
+ def do_stats(appname)
+ stats = client.app_stats(appname)
+ return display JSON.pretty_generate(stats) if @options[:json]
+
+ stats_table = table do |t|
+ t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
+ stats.each do |entry|
+ index = entry[:instance]
+ stat = entry[:stats]
+ hp = "#{stat[:host]}:#{stat[:port]}"
+ uptime = uptime_string(stat[:uptime])
+ usage = stat[:usage]
+ if usage
+ cpu = usage[:cpu]
+ mem = (usage[:mem] * 1024) # mem comes in K's
+ disk = usage[:disk]
+ end
+ mem_quota = stat[:mem_quota]
+ disk_quota = stat[:disk_quota]
+ mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
+ disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
+ cpu = cpu ? cpu.to_s : 'NA'
+ cpu = "#{cpu}% (#{stat[:cores]})"
+ t << [index, cpu, mem, disk, uptime]
+ end
+ end
+
+ if stats.empty?
+ display "No running instances for [#{appname}]".yellow
+ else
+ display stats_table
+ end
+ end
+
+ def all_files(appname, path)
+ instances_info_envelope = client.app_instances(appname)
+ return if instances_info_envelope.is_a?(Array)
+ instances_info = instances_info_envelope[:instances] || []
+ instances_info.each do |entry|
+ begin
+ content = client.app_files(appname, path, entry[:index])
+ display_logfile(
+ path,
+ content,
+ entry[:index],
+ "====> [#{entry[:index]}: #{path}] <====\n".bold
+ )
+ rescue VMC::Client::NotFound, VMC::Client::TargetError
+ end
+ end
+ end
end
class FileWithPercentOutput < ::File
class << self
attr_accessor :display_str, :upload_size