vmc-ng/lib/vmc/cli/app.rb in vmc-0.4.0.beta.12 vs vmc-ng/lib/vmc/cli/app.rb in vmc-0.4.0.beta.13

- old
+ new

@@ -1,10 +1,10 @@ -require "vmc/cli/command" +require "vmc/cli" require "vmc/detect" module VMC - class App < Command + class App < CLI MEM_CHOICES = ["64M", "128M", "256M", "512M"] # TODO: don't hardcode; bring in from remote MEM_DEFAULTS_FRAMEWORK = { "rails3" => "256M", @@ -31,102 +31,133 @@ "ruby" => "128M", "ruby19" => "128M" } - desc "push [NAME]", "Push an application, syncing changes if it exists" + desc "List your applications" + group :apps + input :name, :desc => "Filter by name regexp" + input :runtime, :desc => "Filter by runtime regexp" + input :framework, :desc => "Filter by framework regexp" + input :url, :desc => "Filter by url regexp" + def apps(input) + apps = + with_progress("Getting applications") do + client.apps + end + + if apps.empty? and !quiet? + puts "" + puts "No applications." + return + end + + apps.each.with_index do |a, num| + display_app(a) if app_matches(a, input) + end + end + + + desc "Push an application, syncing changes if it exists" group :apps, :manage - flag(:name) { ask("Name") } - flag(:path) - flag(:url) { |default| + input(:name, :argument => true, :desc => "Application name") { + ask("Name") + } + input :path, :desc => "Path containing the application" + input(:url, :desc => "URL bound to app") { |default| ask("URL", :default => default) } - flag(:memory) { |framework, runtime| + input(:memory, :desc => "Memory limit") { |framework, runtime| ask("Memory Limit", :choices => MEM_CHOICES, :default => MEM_DEFAULTS_RUNTIME[runtime] || MEM_DEFAULTS_FRAMEWORK[framework] || "64M") } - flag(:instances) { + input(:instances, :type => :integer, + :desc => "Number of instances to run") { ask("Instances", :default => 1) } - flag(:framework) { |choices, default| + input(:framework, :desc => "Framework to use") { |choices, default| opts = {:choices => choices} opts[:default] = default if default ask("Framework", opts) } - flag(:runtime) { |choices| + input(:runtime, :desc => "Runtime to run it with") { |choices| ask("Runtime", :choices => choices) } - flag(:command) { + input(:command, :desc => "Startup command for standalone app") { ask("Startup command") } - flag(:start, :default => true) - flag(:restart, :default => true) - flag(:create_services, :type => :boolean) { + input :start, :type => :boolean, :default => true, + :desc => "Start app after pushing?" + input :restart, :type => :boolean, :default => true, + :desc => "Restart app after updating?" + input(:create_services, :type => :boolean, + :desc => "Interactively create services?") { ask "Create services for application?", :default => false } - flag(:bind_services, :type => :boolean) { + input(:bind_services, :type => :boolean, + :desc => "Interactively bind services?") { ask "Bind other services to application?", :default => false } - def push(name = nil) - path = File.expand_path(input(:path) || ".") + def push(input) + path = File.expand_path(input[:path] || ".") - name = input(:name) if input(:name) + name = input[:name] if input[:name] detector = Detector.new(client, path) frameworks = detector.all_frameworks detected, default = detector.frameworks app = client.app(name) if app.exists? upload_app(app, path) - restart(app.name) if input(:restart) + invoke :restart, :name => app.name if input[:restart] return end - app.total_instances = input(:instances) + app.total_instances = input[:instances] if detected.empty? - framework = input(:framework, frameworks.keys.sort, nil) + framework = input[:framework, frameworks.keys.sort, nil] else - framework = input(:framework, detected.keys.sort + ["other"], default) + framework = input[:framework, detected.keys.sort + ["other"], default] if framework == "other" - forget(:framework) - framework = input(:framework, frameworks.keys.sort, nil) + input.forget(:framework) + framework = input[:framework, frameworks.keys.sort, nil] end end framework_runtimes = frameworks[framework]["runtimes"].collect { |k| k["name"] } - runtime = input(:runtime, framework_runtimes.sort) + runtime = input[:runtime, framework_runtimes.sort] app.framework = framework app.runtime = runtime if framework == "standalone" - app.command = input(:command) + app.command = input[:command] - if (url = input(:url, "none")) != "none" + if (url = input[:url, "none"]) != "none" app.urls = [url] else app.urls = [] end else domain = client.target.sub(/^https?:\/\/api\.(.+)\/?/, '\1') - app.urls = [input(:url, "#{name}.#{domain}")] + app.urls = [input[:url, "#{name}.#{domain}"]] end - app.memory = megabytes(input(:memory, framework, runtime)) + app.memory = megabytes(input[:memory, framework, runtime]) bindings = [] - if input(:create_services) && !force? + if input[:create_services] && !force? services = client.system_services while true vendor = ask "What kind?", :choices => services.keys.sort meta = services[vendor] @@ -155,11 +186,11 @@ break unless ask "Create another service?", :default => false end end - if input(:bind_services) && !force? + if input[:bind_services] && !force? services = client.services.collect(&:name) while true choices = services - bindings break if choices.empty? @@ -186,32 +217,32 @@ rescue err "Upload failed. Try again with 'vmc push'." raise end - start(name) if input(:start) + invoke :start, :name => app.name if input[:start] end - desc "start APPS...", "Start an application" - group :apps, :manage - flag :name - flag :debug_mode, :aliases => "-d" - def start(*names) - if name = passed_value(:name) - names = [name] - end + desc "Start an application" + group :apps, :manage + input :names, :argument => :splat, :singular => :name, + :desc => "Applications to start" + input :debug_mode, :aliases => "-d", + :desc => "Debug mode to start in" + def start(input) + names = input[:names] fail "No applications given." if names.empty? names.each do |name| app = client.app(name) fail "Unknown application '#{name}'" unless app.exists? app = filter(:start_app, app) - switch_mode(app, input(:debug_mode)) + switch_mode(app, input[:debug_mode]) with_progress("Starting #{c(name, :name)}") do |s| if app.started? s.skip do err "Already started." @@ -221,25 +252,24 @@ app.start! end check_application(app) - if app.debug_mode && !simple_output? + if app.debug_mode && !quiet? puts "" instances(name) end end end - desc "stop APPS...", "Stop an application" - group :apps, :manage - flag :name - def stop(*names) - if name = passed_value(:name) - names = [name] - end + desc "Stop an application" + group :apps, :manage + input :names, :argument => :splat, :singular => :name, + :desc => "Applications to stop" + def stop(input) + names = input[:names] fail "No applications given." if names.empty? names.each do |name| with_progress("Stopping #{c(name, :name)}") do |s| app = client.app(name) @@ -259,34 +289,40 @@ app.stop! end end end - desc "restart APPS...", "Stop and start an application" + + desc "Stop and start an application" group :apps, :manage - flag :name - flag :debug_mode, :aliases => "-d" - def restart(*names) - stop(*names) - start(*names) + input :names, :argument => :splat, :singular => :name, + :desc => "Applications to stop" + input :debug_mode, :aliases => "-d", + :desc => "Debug mode to start in" + def restart(input) + invoke :stop, :names => input[:names] + invoke :start, :names => input[:names], + :debug_mode => input[:debug_mode] end - desc "delete APPS...", "Delete an application" + + desc "Delete an application" group :apps, :manage - flag :name - flag(:really) { |name, color| + input(:really, :type => :boolean) { |name, color| force? || ask("Really delete #{c(name, color)}?", :default => false) } - flag(:name) { |names| - ask("Delete which application?", :choices => names) + input(:names, :argument => :splat, :singular => :name, + :desc => "Applications to delete") { |names| + [ask("Delete which application?", :choices => names)] } - flag(:orphaned, :aliases => "-o", :type => :boolean, - :desc => "Delete orphaned instances") - flag(:all, :default => false) - def delete(*names) - if input(:all) - return unless input(:really, "ALL APPS", :bad) + input :orphaned, :aliases => "-o", :type => :boolean, + :desc => "Delete orphaned instances" + input :all, :default => false, + :desc => "Delete all applications" + def delete(input) + if input[:all] + return unless input[:really, "ALL APPS", :bad] apps = client.apps orphaned = find_orphaned_services(apps) @@ -294,98 +330,97 @@ with_progress("Deleting #{c(a.name, :name)}") do a.delete! end end - delete_orphaned_services(orphaned) + delete_orphaned_services(orphaned, input[:orphaned]) return end apps = client.apps + fail "No applications." if apps.empty? - if names.empty? - fail "No applications." if apps.empty? + names = input[:names, apps.collect(&:name).sort] - names = [input(:name, apps.collect(&:name).sort)] - end - to_delete = names.collect do |n| if app = apps.find { |a| a.name == n } app else fail "Unknown application '#{n}'" end end - orphaned = find_orphaned_services(to_delete) - + deleted = [] to_delete.each do |app| - really = input(:really, app.name, :name) - - forget(:really) - + really = input[:really, app.name, :name] next unless really + deleted << app + with_progress("Deleting #{c(app.name, :name)}") do app.delete! end end - delete_orphaned_services(orphaned) + unless deleted.empty? + delete_orphaned_services( + find_orphaned_services(deleted), + input[:orphaned]) + end end - desc "instances APPS...", "List an app's instances" - group :apps, :info, :hidden => true - flag :name - def instances(*names) - if name = passed_value(:name) - names = [name] - end + desc "List an app's instances" + group :apps, :info, :hidden => true + input :names, :argument => :splat, :singular => :name, + :desc => "Applications to list instances of" + def instances(input) + names = input[:names] fail "No applications given." if names.empty? names.each do |name| instances = with_progress("Getting instances for #{c(name, :name)}") do client.app(name).instances end instances.each do |i| - if simple_output? + if quiet? puts i.index else puts "" display_instance(i) end end end end - desc "scale APP", "Update the instances/memory limit for an application" + + desc "Update the instances/memory limit for an application" group :apps, :info, :hidden => true - flag :name - flag(:instances, :type => :numeric) { |default| + input :name, :argument => true, :desc => "Application to update" + input(:instances, :type => :numeric, + :desc => "Number of instances to run") { |default| ask("Instances", :default => default) } - flag(:memory) { |default| - ask("Memory Limit", - :default => human_size(default * 1024 * 1024, 0), - :choices => MEM_CHOICES) + input(:memory, :desc => "Memory limit") { |default| + ask("Memory Limit", :choices => MEM_CHOICES, + :default => human_size(default * 1024 * 1024, 0)) } - flag :restart, :default => true - def scale(name = nil) - name ||= input(:name) - + input :restart, :default => true, + :desc => "Restart app after updating?" + def scale(input) + name = input[:name] app = client.app(name) - instances = passed_value(:instances) - memory = passed_value(:memory) + instances = input.given(:instances) + memory = input.given(:memory) unless instances || memory - instances = input(:instances, app.total_instances) - memory = input(:memory, app.memory) + instances = input[:instances, app.total_instances] + memory = input[:memory, app.memory] end megs = megabytes(memory) memory_changed = megs != app.memory @@ -397,40 +432,42 @@ app.total_instances = instances.to_i if instances app.memory = megs if memory app.update! end - if memory_changed && app.started? && input(:restart) - with_progress("Restarting #{c(name, :name)}") do - app.restart! - end + if memory_changed && app.started? && input[:restart] + invoke :restart, :name => name end end - desc "logs APP", "Print out an app's logs" + + desc "Print out an app's logs" group :apps, :info, :hidden => true - flag :name - flag(:instance, :type => :numeric, :default => 0) - flag(:all, :default => false) - def logs(name = nil) - name ||= input(:name) + input :name, :argument => true, + :desc => "Application to get the logs of" + input :instance, :type => :numeric, :default => 0, + :desc => "Instance of application to get the logs of" + input :all, :default => false, + :desc => "Get logs for every instance" + def logs(input) + name = input[:name] app = client.app(name) fail "Unknown application '#{name}'" unless app.exists? instances = - if input(:all) + if input[:all] app.instances else - app.instances.select { |i| i.index == input(:instance) } + app.instances.select { |i| i.index == input[:instance] } end if instances.empty? - if input(:all) + if input[:all] fail "No instances found." else - fail "Instance #{name} \##{input(:instance)} not found." + fail "Instance #{name} \##{input[:instance]} not found." end end instances.each do |i| logs = @@ -439,11 +476,11 @@ c(name, :name) + " " + c("\##{i.index}", :instance)) do i.files("logs") end - puts "" unless simple_output? + puts "" unless quiet? logs.each do |log| body = with_progress("Reading " + b(log.join("/"))) do i.file(*log) @@ -453,77 +490,78 @@ puts "" unless body.empty? end end end - desc "file APP [PATH]", "Print out an app's file contents" - group :apps, :info, :hidden => true - flag :name - def file(name = nil, path = "/") - name ||= input(:name) + desc "Print out an app's file contents" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to inspect the files of" + input :path, :argument => true, :default => "/", + :desc => "Path of file to read" + def file(input) file = with_progress("Getting file contents") do - client.app(name).file(*path.split("/")) + client.app(input[:name]).file(*input[:path].split("/")) end - puts "" unless simple_output? + puts "" unless quiet? print file end - desc "files APP [PATH]", "Examine an app's files" + desc "Examine an app's files" group :apps, :info, :hidden => true - flag :name - def files(name = nil, path = "/") - name ||= input(:name) - + input :name, :argument => true, + :desc => "Application to inspect the files of" + input :path, :argument => true, :default => "/", + :desc => "Path of directory to list" + def files(input) files = with_progress("Getting file listing") do - client.app(name).files(*path.split("/")) + client.app(input[:name]).files(*input[:path].split("/")) end - puts "" unless simple_output? + puts "" unless quiet? files.each do |file| puts file.join("/") end end - desc "health ...APPS", "Get application health" - group :apps, :info, :hidden => true - flag :name - def health(*names) - if name = passed_value(:name) - names = [name] - end + desc "Get application health" + group :apps, :info, :hidden => true + input :names, :argument => :splat, :singular => :name, + :desc => "Application to check the status of" + def health(input) apps = with_progress("Getting application health") do - names.collect do |n| + input[:names].collect do |n| [n, app_status(client.app(n))] end end apps.each do |name, status| - unless simple_output? + unless quiet? puts "" print "#{c(name, :name)}: " end puts status end end - desc "stats APP", "Display application instance status" - group :apps, :info, :hidden => true - flag :name - def stats(name = nil) - name ||= input(:name) + desc "Display application instance status" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to get the stats for" + def stats(input) stats = - with_progress("Getting stats") do - client.app(name).stats + with_progress("Getting stats for #{c(input[:name], :name)}") do + client.app(input[:name]).stats end stats.sort_by { |k, _| k }.each do |idx, info| puts "" @@ -540,132 +578,222 @@ puts " memory: #{usage(usage["mem"] * 1024, stats["mem_quota"])}" puts " disk: #{usage(usage["disk"], stats["disk_quota"])}" end end - desc "update", "DEPRECATED", :hide => true - def update(*args) - fail "The 'update' command is no longer used; use 'push' instead." + + desc "Add a URL mapping for an app" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to add the URL to" + input :url, :argument => true, + :desc => "URL to route" + def map(input) + name = input[:name] + simple = input[:url].sub(/^https?:\/\/(.*)\/?/i, '\1') + + with_progress("Updating #{c(name, :name)}") do + app = client.app(name) + app.urls << simple + app.update! + end end - class URL < Command - desc "map APP URL", "Add a URL mapping for an app" - group :apps, :info, :hidden => true - def map(name, url) - simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1') - with_progress("Updating #{c(name, :name)}") do - app = client.app(name) - app.urls << simple - app.update! + desc "Remove a URL mapping from an app" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to remove the URL from" + input(:url, :argument => true, :desc => "URL to unmap") { |choices| + ask("Which URL?", :choices => choices) + } + def unmap(input) + name = input[:name] + app = client.app(name) + + url = input[:url, app.urls] + + simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1') + + fail "Unknown application '#{name}'" unless app.exists? + + with_progress("Updating #{c(name, :name)}") do |s| + unless app.urls.delete(simple) + s.fail do + err "URL #{url} is not mapped to this application." + return + end end + + app.update! end + end - desc "unmap APP URL", "Remove a URL mapping from an app" - group :apps, :info, :hidden => true - def unmap(name, url) - simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1') - app = client.app(name) - fail "Unknown application '#{name}'" unless app.exists? + desc "Show all environment variables set for an app" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to inspect the environment of" + def env(input) + appname = input[:name] - with_progress("Updating #{c(name, :name)}") do |s| - unless app.urls.delete(simple) + vars = + with_progress("Getting env for #{c(input[:name], :name)}") do |s| + app = client.app(appname) + + unless app.exists? s.fail do - err "URL #{url} is not mapped to this application." + err "Unknown application '#{appname}'" return end end - app.update! + app.env end + + puts "" unless quiet? + + vars.each do |pair| + name, val = pair.split("=", 2) + puts "#{c(name, :name)}: #{val}" end end - desc "url SUBCOMMAND ...ARGS", "Manage application URL bindings" - subcommand "url", URL - class Env < Command - VALID_NAME = /^[a-zA-Za-z_][[:alnum:]_]*$/ + VALID_ENV_VAR = /^[a-zA-Za-z_][[:alnum:]_]*$/ - desc "set APP [NAME] [VALUE]", "Set an environment variable" - group :apps, :info, :hidden => true - flag :restart, :default => true - def set(appname, name, value) - unless name =~ VALID_NAME - fail "Invalid variable name; must match #{VALID_NAME.inspect}" - end + desc "Set an environment variable" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to set the variable for" + input :var, :argument => true, + :desc => "Environment variable name" + input :value, :argument => :optional, + :desc => "Environment variable value" + input :restart, :default => true, + :desc => "Restart app after updating?" + def set_env(input) + appname = input[:name] + name = input[:var] - app = client.app(appname) - fail "Unknown application '#{appname}'" unless app.exists? + if value = input[:value] + name = input[:var] + elsif name["="] + name, value = name.split("=") + end - with_progress("Updating #{c(app.name, :name)}") do - app.update!("env" => - app.env.reject { |v| - v.start_with?("#{name}=") - }.push("#{name}=#{value}")) - end - - if app.started? && input(:restart) - with_progress("Restarting #{c(app.name, :name)}") do - app.restart! - end - end + unless name =~ VALID_ENV_VAR + fail "Invalid variable name; must match #{VALID_ENV_VAR.inspect}" end - desc "unset APP [NAME]", "Remove an environment variable" - group :apps, :info, :hidden => true - flag :restart, :default => true - def unset(appname, name) - app = client.app(appname) - fail "Unknown application '#{appname}'" unless app.exists? + app = client.app(appname) + fail "Unknown application '#{appname}'" unless app.exists? - with_progress("Updating #{c(app.name, :name)}") do - app.update!("env" => - app.env.reject { |v| - v.start_with?("#{name}=") - }) - end + with_progress("Updating #{c(app.name, :name)}") do + app.update!("env" => + app.env.reject { |v| + v.start_with?("#{name}=") + }.push("#{name}=#{value}")) + end - if app.started? && input(:restart) - with_progress("Restarting #{c(app.ame, :name)}") do - app.restart! - end - end + if app.started? && input[:restart] + invoke :restart, :name => app.name end + end - desc "list APP", "Show all environment variables set for an app" - group :apps, :info, :hidden => true - def list(appname) - vars = - with_progress("Getting variables") do |s| - app = client.app(appname) + alias_command :set_env, :env_set + alias_command :set_env, :add_env + alias_command :set_env, :env_add - unless app.exists? - s.fail do - err "Unknown application '#{appname}'" - return - end - end - app.env - end + desc "Remove an environment variable" + group :apps, :info, :hidden => true + input :name, :argument => true, + :desc => "Application to remove the variable from" + input :var, :argument => true, + :desc => "Environment variable name" + input :restart, :default => true, + :desc => "Restart app after updating?" + def delete_env(input) + appname = input[:name] + name = input[:var] - puts "" unless simple_output? + app = client.app(appname) + fail "Unknown application '#{appname}'" unless app.exists? - vars.each do |pair| - name, val = pair.split("=", 2) - puts "#{c(name, :name)}: #{val}" - end + with_progress("Updating #{c(app.name, :name)}") do + app.update!("env" => + app.env.reject { |v| + v.start_with?("#{name}=") + }) end + + if app.started? && input[:restart] + invoke :restart, :name => app.name + end end - desc "env SUBCOMMAND ...ARGS", "Manage application environment variables" - subcommand "env", Env + alias_command :delete_env, :env_del + + desc "DEPRECATED. Use 'push' instead." + def update(input) + fail "The 'update' command is no longer needed; use 'push' instead." + end + private + def app_matches(a, options) + if name = options[:name] + return false if a.name !~ /#{name}/ + end + + if runtime = options[:runtime] + return false if a.runtime !~ /#{runtime}/ + end + + if framework = options[:framework] + return false if a.framework !~ /#{framework}/ + end + + if url = options[:url] + return false if a.urls.none? { |u| u =~ /#{url}/ } + end + + true + end + + IS_UTF8 = !!(ENV["LC_ALL"] || ENV["LC_CTYPE"] || ENV["LANG"])["UTF-8"] + + def display_app(a) + if quiet? + puts a.name + return + end + + puts "" + + status = app_status(a) + + puts "#{c(a.name, :name)}: #{status}" + + puts " platform: #{b(a.framework)} on #{b(a.runtime)}" + + print " usage: #{b(human_size(a.memory * 1024 * 1024, 0))}" + print " #{c(IS_UTF8 ? "\xc3\x97" : "x", :dim)} #{b(a.total_instances)}" + print " instance#{a.total_instances == 1 ? "" : "s"}" + puts "" + + unless a.urls.empty? + puts " urls: #{a.urls.collect { |u| b(u) }.join(", ")}" + end + + unless a.services.empty? + puts " services: #{a.services.collect { |s| b(s) }.join(", ")}" + end + end + def upload_app(app, path) with_progress("Uploading #{c(app.name, :name)}") do app.upload(path) end end @@ -778,16 +906,16 @@ end orphaned end - def delete_orphaned_services(names) + def delete_orphaned_services(names, orphaned) return if names.empty? - puts "" unless simple_output? + puts "" unless quiet? names.select { |s| - input(:orphaned) || + orphaned || ask("Delete orphaned service #{c(s, :name)}?", :default => false) }.each do |s| with_progress("Deleting service #{c(s, :name)}") do client.service(s).delete! end