From df63c0927a50f92695f5aa2556b885c64c6d4f0f Mon Sep 17 00:00:00 2001 From: Jennifer Hickey Date: Tue, 13 Mar 2012 14:20:23 -0700 Subject: [PATCH] Add standalone framework - Add new standalone framework - Allow users to choose runtime from list if framework is standalone - Allow default runtime to be specified by framework - Pass start command to server on app creation - Allow default memory to be specified by runtime/framework combo - Support pushing a single file, including unpack and repack of zip and WAR - Allow "None" as URL choice for standalone apps Change-Id: Icdc1ae9098a9365e3ec26b3bf2e5f215437a56a1 --- Rakefile | 2 + lib/cli/commands/apps.rb | 217 +++++++++++++++------------- lib/cli/frameworks.rb | 159 ++++++++++++++++---- lib/cli/manifest_helper.rb | 72 +++++++-- spec/assets/standalone_app_info.txt | 9 ++ spec/assets/tests | 2 +- spec/unit/command_apps_spec.rb | 156 ++++++++++++++++++++ spec/unit/frameworks_spec.rb | 156 +++++++++++++++++--- 8 files changed, 611 insertions(+), 162 deletions(-) create mode 100644 spec/assets/standalone_app_info.txt diff --git a/Rakefile b/Rakefile index 3512b462..9bedcfdd 100644 --- a/Rakefile +++ b/Rakefile @@ -30,6 +30,8 @@ TESTS_TO_BUILD = ["#{TESTS_PATH}/java_web/java_tiny_app", "#{TESTS_PATH}/lift/hello_lift", "#{TESTS_PATH}/spring/roo-guestbook", "#{TESTS_PATH}/spring/spring-osgi-hello", + "#{TESTS_PATH}/standalone/java_app", + "#{TESTS_PATH}/standalone/python_app" ] desc "Build the tests. If the git hash associated with the test assets has not changed, nothing is built. To force a build, invoke 'rake build[--force]'" diff --git a/lib/cli/commands/apps.rb b/lib/cli/commands/apps.rb index bff4b578..ba14b3a3 100644 --- a/lib/cli/commands/apps.rb +++ b/lib/cli/commands/apps.rb @@ -371,7 +371,6 @@ def app_exists?(appname) 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 @@ -410,101 +409,112 @@ def upload_app_bits(appname, path) 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 - check_unreachable_links(path) - FileUtils.mkdir(explode_dir) + 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) - files = Dir.glob('{*,.[^\.]*}') + files = Dir.glob('{*,.[^\.]*}') - # Do not process .git files - files.delete('.git') if files + # Do not process .git files + files.delete('.git') if files - FileUtils.cp_r(files, explode_dir) + FileUtils.cp_r(files, explode_dir) - find_sockets(explode_dir).each do |s| - File.delete s + find_sockets(explode_dir).each do |s| + File.delete s + end 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 - - # 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 - - 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 + # 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 + # 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 - # 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") + 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 - # 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' + 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 + end + # Perform Packing of the upload bits here. + display ' Packing application: ', false + VMC::Cli::ZipUtil.pack(explode_dir, upload_file) + display 'OK'.green - upload_str = " Uploading (#{upload_size}): " - display upload_str, false + 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 - FileWithPercentOutput.display_str = upload_str - FileWithPercentOutput.upload_size = File.size(upload_file); - file = FileWithPercentOutput.open(upload_file, 'rb') + upload_str = " Uploading (#{upload_size}): " + display upload_str, false - client.upload_app(appname, file, appcloud_resources) - display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty? + FileWithPercentOutput.display_str = upload_str + FileWithPercentOutput.upload_size = File.size(upload_file); + file = FileWithPercentOutput.open(upload_file, 'rb') - display 'Push Status: ', false - display 'OK'.green - end + client.upload_app(appname, file, appcloud_resources) + display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty? - ensure - # Cleanup if we created an exploded directory. - FileUtils.rm_f(upload_file) if upload_file - FileUtils.rm_rf(explode_dir) if explode_dir + display 'Push Status: ', false + display 'OK'.green + + 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 check_app_limit @@ -908,6 +918,8 @@ def do_push(appname=nil) 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 @@ -934,9 +946,30 @@ def do_push(appname=nil) err "Application '#{appname}' already exists, use update or delete." end - default_url = "#{appname}.#{target_base}" + 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 + unless no_prompt || url || !framework.require_url? url = ask( "Application Deployed URL", :default => default_url @@ -947,29 +980,18 @@ def do_push(appname=nil) # 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 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.new(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 memswitch mem = memswitch elsif prompt_ok mem = ask("Memory Reservation", - :default => framework.memory, :choices => mem_choices) + :default => framework.memory(runtime), + :choices => mem_choices) else - mem = framework.memory + mem = framework.memory runtime end # Set to MB number @@ -984,14 +1006,15 @@ def do_push(appname=nil) :name => "#{appname}", :staging => { :framework => framework.name, - :runtime => info(:runtime) + :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) diff --git a/lib/cli/frameworks.rb b/lib/cli/frameworks.rb index 4a77ba79..a280d626 100644 --- a/lib/cli/frameworks.rb +++ b/lib/cli/frameworks.rb @@ -11,6 +11,7 @@ class Framework 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}], 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}], 'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}], + 'Standalone' => ['standalone', { :mem => '64M', :description => 'Standalone Application'}], 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}], 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}], 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}], @@ -27,16 +28,33 @@ def known_frameworks end def lookup(name) - return Framework.new(*FRAMEWORKS[name]) + return create(*FRAMEWORKS[name]) end def lookup_by_framework(name) FRAMEWORKS.each do |key,fw| - return Framework.new(fw[0], fw[1]) if fw[0] == name + return create(fw[0],fw[1]) if fw[0] == name + end + end + + def create(name,opts) + if name == "standalone" + return StandaloneFramework.new(name, opts) + else + return Framework.new(name,opts) end end def detect(path, available_frameworks) + if !File.directory? path + if path.end_with?('.war') + return detect_framework_from_war path + elsif available_frameworks.include?(["standalone"]) + return Framework.lookup('Standalone') + else + return nil + end + end Dir.chdir(path) do # Rails if File.exist?('config/environment.rb') @@ -46,30 +64,13 @@ def detect(path, available_frameworks) elsif File.exist?('config.ru') && available_frameworks.include?(["rack"]) return Framework.lookup('Rack') - # Java - elsif Dir.glob('*.war').first || File.exist?('WEB-INF/web.xml') - war_file = Dir.glob('*.war').first + # Java Web Apps + elsif Dir.glob('*.war').first + return detect_framework_from_war(Dir.glob('*.war').first) - if war_file - contents = ZipUtil.entry_lines(war_file) - else - contents = Dir['**/*'].join("\n") - end + elsif File.exist?('WEB-INF/web.xml') + return detect_framework_from_war - # Spring/Lift Variations - if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/ - return Framework.lookup('Grails') - elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/ - return Framework.lookup('Lift') - elsif contents =~ /WEB-INF\/classes\/org\/springframework/ - return Framework.lookup('Spring') - elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/ - return Framework.lookup('Spring') - elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/ - return Framework.lookup('Spring') - else - return Framework.lookup('JavaWeb') - end # Simple Ruby Apps elsif !Dir.glob('*.rb').empty? matched_file = nil @@ -81,17 +82,16 @@ def detect(path, available_frameworks) end end if matched_file + # Sinatra apps f = Framework.lookup('Sinatra') f.exec = "ruby #{matched_file}" return f end - # Node.js elsif !Dir.glob('*.js').empty? if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js') return Framework.lookup('Node') end - # PHP elsif !Dir.glob('*.php').empty? return Framework.lookup('PHP') @@ -108,19 +108,42 @@ def detect(path, available_frameworks) # Python elsif !Dir.glob('wsgi.py').empty? return Framework.lookup('WSGI') - end + + # Default to Standalone if no other match was made + return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"]) end - nil end + private + def detect_framework_from_war(war_file=nil) + if war_file + contents = ZipUtil.entry_lines(war_file) + else + #assume we are working with current dir + contents = Dir['**/*'].join("\n") + end + + # Spring/Lift Variations + if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/ + return Framework.lookup('Grails') + elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/ + return Framework.lookup('Lift') + elsif contents =~ /WEB-INF\/classes\/org\/springframework/ + return Framework.lookup('Spring') + elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/ + return Framework.lookup('Spring') + elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/ + return Framework.lookup('Spring') + else + return Framework.lookup('JavaWeb') + end + end end - attr_reader :name, :description, :memory, :console + attr_reader :name, :description, :console attr_accessor :exec - alias :mem :memory - def initialize(framework=nil, opts={}) @name = framework || DEFAULT_FRAMEWORK @memory = opts[:mem] || DEFAULT_MEM @@ -132,6 +155,80 @@ def initialize(framework=nil, opts={}) def to_s description end + + def require_url? + true + end + + def require_start_command? + false + end + + def prompt_for_runtime? + false + end + + def default_runtime(path) + nil + end + + def memory(runtime=nil) + @memory + end + + alias :mem :memory + end + + class StandaloneFramework < Framework + def require_url? + false + end + + def require_start_command? + true + end + + def prompt_for_runtime? + true + end + + def default_runtime(path) + if !File.directory? path + if path =~ /\.(jar|class)$/ + return "java" + elsif path =~ /\.(rb)$/ + return "ruby18" + elsif path =~ /\.(zip)$/ + return detect_runtime_from_zip path + end + else + Dir.chdir(path) do + return "ruby18" if not Dir.glob('**/*.rb').empty? + if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty? + return "java" + elsif Dir.glob('*.zip').first + zip_file = Dir.glob('*.zip').first + return detect_runtime_from_zip zip_file + end + end + end + return nil + end + + def memory(runtime=nil) + default_mem = @memory + default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php" + default_mem = '512M' if runtime == "java" + default_mem + end + + private + def detect_runtime_from_zip(zip_file) + contents = ZipUtil.entry_lines(zip_file) + if contents =~ /\.(jar)$/ + return "java" + end + end end end diff --git a/lib/cli/manifest_helper.rb b/lib/cli/manifest_helper.rb index 9764d9dc..59e958e1 100644 --- a/lib/cli/manifest_helper.rb +++ b/lib/cli/manifest_helper.rb @@ -82,22 +82,11 @@ def configure_app(many=false) name = manifest("name") || set(ask("Application Name", :default => manifest("name")), "name") - url_template = manifest("url") || DEFAULTS["url"] - url_resolved = url_template.dup - resolve_lexically(url_resolved) - - url = ask("Application Deployed URL", :default => url_resolved) - - url = url_template if url == url_resolved - - # 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 = DEFAULTS["url"] if YES_SET.member? url - set url, "url" - unless manifest "framework" + if manifest "framework" + framework = VMC::Cli::Framework.lookup_by_framework manifest("framework","name") + else framework = detect_framework set framework.name, "framework", "name" set( @@ -110,11 +99,44 @@ def configure_app(many=false) ) end + default_runtime = manifest "runtime" + if not default_runtime + default_runtime = framework.default_runtime(@application) + set(detect_runtime(default_runtime), "runtime") if framework.prompt_for_runtime? + end + default_command = manifest "command" + set ask("Start Command", :default => default_command), "command" if framework.require_start_command? + + url_template = manifest("url") || DEFAULTS["url"] + url_resolved = url_template.dup + resolve_lexically(url_resolved) + + if !framework.require_url? + url_resolved = "None" + end + url = ask("Application Deployed URL", :default => url_resolved) + + if url == url_resolved && url != "None" + url = url_template + end + + # 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 = url_resolved if YES_SET.member? url + + if(url == "None") + url = nil + end + + set url, "url" + + default_mem = manifest("mem") + default_mem = framework.memory(manifest("runtime")) if not default_mem set ask( "Memory reservation", :default => - manifest("mem") || - manifest("framework", "info", "mem") || + default_mem || DEFAULTS["mem"], :choices => ["128M", "256M", "512M", "1G", "2G"] ), "mem" @@ -184,6 +206,24 @@ def detect_framework(prompt_ok = true) framework end + # Detect the appropriate runtime. + def detect_runtime(default, prompt_ok=true) + runtime = nil + runtime_keys=[] + runtimes_info.keys.each {|runtime_key| runtime_keys << runtime_key.dup } + runtime_keys.sort! + if prompt_ok + runtime = ask( + "Select Runtime", + :indexed => true, + :default => default, + :choices => runtime_keys + ) + display "Selected #{runtime}" + end + runtime + end + def bind_services(user_services, chosen = 0) svcname = ask( "Which one?", diff --git a/spec/assets/standalone_app_info.txt b/spec/assets/standalone_app_info.txt new file mode 100644 index 00000000..d1c00ae1 --- /dev/null +++ b/spec/assets/standalone_app_info.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Server: nginx/0.7.65 +Date: Fri, 04 Mar 2011 02:56:21 GMT +Content-Type: application/json +Connection: keep-alive +Keep-Alive: timeout=20 +Content-Length: 243 + +{"resources":{"memory":64},"uris":["foo.vcap.me"],"staging":{"runtime":"ruby18" ,"framework":"standalone"},"state":"STARTED","instances":1,"name":"foo","meta":{"version":1,"created":1299207348},"services":[],"runningInstances":1} \ No newline at end of file diff --git a/spec/assets/tests b/spec/assets/tests index 6a32f9c4..4b937e6a 160000 --- a/spec/assets/tests +++ b/spec/assets/tests @@ -1 +1 @@ -Subproject commit 6a32f9c4faa99385a32d7b5d3529a0ac446100a6 +Subproject commit 4b937e6a951f674dc88f466d255de5d9a20f38d3 diff --git a/spec/unit/command_apps_spec.rb b/spec/unit/command_apps_spec.rb index 9092dac8..89e72b0c 100644 --- a/spec/unit/command_apps_spec.rb +++ b/spec/unit/command_apps_spec.rb @@ -88,4 +88,160 @@ expect { command.update('foo')}.to raise_error(/Can't deploy application containing links/) end + it 'should not fail when there is an attempt to update an app using a single file' do + @client = VMC::Client.new(@local_target, @auth_token) + + login_path = "#{@local_target}/users/#{@user}/tokens" + stub_request(:post, login_path).to_return(File.new(spec_asset('login_success.txt'))) + info_path = "#{@local_target}/#{VMC::INFO_PATH}" + stub_request(:get, info_path).to_return(File.new(spec_asset('info_authenticated.txt'))) + + app = spec_asset('tests/standalone/simple_ruby_app/simple.rb') + options = { + :name => 'foo', + :uris => ['foo.vcap.me'], + :instances => 1, + :staging => { :framework => 'standalone', :runtime => 'ruby18', :command=>"ruby simple.rb" }, + :path => app, + :resources => { :memory => 128 } + } + command = VMC::Cli::Command::Apps.new(options) + command.client(@client) + + app_path = "#{@local_target}/#{VMC::APPS_PATH}/foo" + stub_request(:get, app_path).to_return(File.new(spec_asset('standalone_app_info.txt'))) + + resource_path = "#{@local_target}/#{VMC::RESOURCES_PATH}" + stub_request(:post, resource_path).to_return(File.new(spec_asset('resources_return.txt'))) + + app_upload_path = "#{@local_target}/#{VMC::APPS_PATH}/foo/application" + stub_request(:post, app_upload_path) + + stub_request(:put, app_path) + + # Both 'vmc push ..' and 'vmc update ..' ultimately end up calling upload_app_bits + command.update('foo') + + a_request(:post, app_upload_path).should have_been_made.once + a_request(:put, app_path).should have_been_made.once + + end + + it 'should not fail when there is an attempt to update an app using a single WAR file' do + @client = VMC::Client.new(@local_target, @auth_token) + + login_path = "#{@local_target}/users/#{@user}/tokens" + stub_request(:post, login_path).to_return(File.new(spec_asset('login_success.txt'))) + info_path = "#{@local_target}/#{VMC::INFO_PATH}" + stub_request(:get, info_path).to_return(File.new(spec_asset('info_authenticated.txt'))) + + app = spec_asset('tests/spring/spring-osgi-hello/target/hello.war') + options = { + :name => 'foo', + :uris => ['foo.vcap.me'], + :instances => 1, + :staging => { :framework => 'spring'}, + :path => app, + :resources => { :memory => 512 } + } + command = VMC::Cli::Command::Apps.new(options) + command.client(@client) + + app_path = "#{@local_target}/#{VMC::APPS_PATH}/foo" + stub_request(:get, app_path).to_return(File.new(spec_asset('standalone_app_info.txt'))) + + resource_path = "#{@local_target}/#{VMC::RESOURCES_PATH}" + stub_request(:post, resource_path).to_return(File.new(spec_asset('resources_return.txt'))) + + app_upload_path = "#{@local_target}/#{VMC::APPS_PATH}/foo/application" + stub_request(:post, app_upload_path) + + stub_request(:put, app_path) + + # Both 'vmc push ..' and 'vmc update ..' ultimately end up calling upload_app_bits + command.update('foo') + + a_request(:post, app_upload_path).should have_been_made.once + a_request(:put, app_path).should have_been_made.once + + end + + it 'should not fail when there is an attempt to update an app using a single zip file' do + @client = VMC::Client.new(@local_target, @auth_token) + + login_path = "#{@local_target}/users/#{@user}/tokens" + stub_request(:post, login_path).to_return(File.new(spec_asset('login_success.txt'))) + info_path = "#{@local_target}/#{VMC::INFO_PATH}" + stub_request(:get, info_path).to_return(File.new(spec_asset('info_authenticated.txt'))) + + app = spec_asset('tests/standalone/java_app/target/zip/standalone-java-app-1.0.0.BUILD-SNAPSHOT-jar.zip') + options = { + :name => 'foo', + :uris => ['foo.vcap.me'], + :instances => 1, + :staging => { :framework => 'standalone', :runtime => 'java', :command=>"java HelloCloud" }, + :path => app, + :resources => { :memory => 128 } + } + command = VMC::Cli::Command::Apps.new(options) + command.client(@client) + + app_path = "#{@local_target}/#{VMC::APPS_PATH}/foo" + stub_request(:get, app_path).to_return(File.new(spec_asset('standalone_app_info.txt'))) + + resource_path = "#{@local_target}/#{VMC::RESOURCES_PATH}" + stub_request(:post, resource_path).to_return(File.new(spec_asset('resources_return.txt'))) + + app_upload_path = "#{@local_target}/#{VMC::APPS_PATH}/foo/application" + stub_request(:post, app_upload_path) + + stub_request(:put, app_path) + + # Both 'vmc push ..' and 'vmc update ..' ultimately end up calling upload_app_bits + command.update('foo') + + a_request(:post, app_upload_path).should have_been_made.once + a_request(:put, app_path).should have_been_made.once + + end + + it 'should not fail when there is an attempt to update an app using a dir containing a zip file' do + @client = VMC::Client.new(@local_target, @auth_token) + + login_path = "#{@local_target}/users/#{@user}/tokens" + stub_request(:post, login_path).to_return(File.new(spec_asset('login_success.txt'))) + info_path = "#{@local_target}/#{VMC::INFO_PATH}" + stub_request(:get, info_path).to_return(File.new(spec_asset('info_authenticated.txt'))) + + app = spec_asset('tests/standalone/java_app/target/zip') + options = { + :name => 'foo', + :uris => ['foo.vcap.me'], + :instances => 1, + :staging => { :framework => 'standalone', :runtime => 'java', :command=>"java HelloCloud" }, + :path => app, + :resources => { :memory => 128 } + } + command = VMC::Cli::Command::Apps.new(options) + command.client(@client) + + app_path = "#{@local_target}/#{VMC::APPS_PATH}/foo" + stub_request(:get, app_path).to_return(File.new(spec_asset('standalone_app_info.txt'))) + + resource_path = "#{@local_target}/#{VMC::RESOURCES_PATH}" + stub_request(:post, resource_path).to_return(File.new(spec_asset('resources_return.txt'))) + + app_upload_path = "#{@local_target}/#{VMC::APPS_PATH}/foo/application" + stub_request(:post, app_upload_path) + + stub_request(:put, app_path) + + # Both 'vmc push ..' and 'vmc update ..' ultimately end up calling upload_app_bits + command.update('foo') + + a_request(:post, app_upload_path).should have_been_made.once + a_request(:put, app_path).should have_been_made.once + + end + end diff --git a/spec/unit/frameworks_spec.rb b/spec/unit/frameworks_spec.rb index 4b13530d..2823c3dd 100644 --- a/spec/unit/frameworks_spec.rb +++ b/spec/unit/frameworks_spec.rb @@ -11,89 +11,211 @@ it 'should be able to detect a Java web app war' do app = spec_asset('tests/java_web/java_tiny_app/target') - framework(app).should =~ /Java Web/ + framework(app).to_s.should =~ /Java Web/ end it 'should be able to detect an exploded Java web app' do app = spec_asset('tests/java_web/java_tiny_app/target') - framework(get_war_file(app), true).should =~ /Java Web/ + framework(get_war_file(app), true).to_s.should =~ /Java Web/ end it 'should be able to detect a Spring web app war' do app = spec_asset('tests/spring/roo-guestbook/target') - framework(app).should =~ /Spring/ + framework(app).to_s.should =~ /Spring/ end it 'should be able to detect an exploded Spring web app' do app = spec_asset('tests/spring/roo-guestbook/target/') - framework(get_war_file(app), true).should =~ /Spring/ + framework(get_war_file(app), true).to_s.should =~ /Spring/ end it 'should be able to detect a Spring web app war that uses OSGi-style jars' do app = spec_asset('tests/spring/spring-osgi-hello/target') - framework(app).should =~ /Spring/ + framework(app).to_s.should =~ /Spring/ end it 'should be able to detect an exploded Spring web app that uses OSGi-style jars' do app = spec_asset('tests/spring/spring-osgi-hello/target') - framework(get_war_file(app), true).should =~ /Spring/ + framework(get_war_file(app), true).to_s.should =~ /Spring/ end it 'should be able to detect a Lift web app war' do app = spec_asset('tests/lift/hello_lift/target') - framework(app).should =~ /Lift/ + framework(app).to_s.should =~ /Lift/ + end + + it 'should be able to detect a Lift web app war file' do + app = spec_asset('tests/lift/hello_lift/target/scala_lift-1.0.war') + framework(app).to_s.should =~ /Lift/ end it 'should be able to detect an exploded Lift web app' do app = spec_asset('tests/lift/hello_lift/target') - framework(get_war_file(app), true).should =~ /Lift/ + framework(get_war_file(app), true).to_s.should =~ /Lift/ end it 'should be able to detect a Grails web app war' do pending "Availability of a fully functional maven plugin for grails" app = spec_asset('tests/grails/guestbook/target') - framework(app).should =~ /Grails/ + framework(app).to_s.should =~ /Grails/ end it 'should be able to detect an exploded Grails web app' do pending "Availability of a fully functional maven plugin for grails" app = spec_asset('tests/grails/guestbook/target') - framework(get_war_file(app), true).should =~ /Grails/ + framework(get_war_file(app), true).to_s.should =~ /Grails/ end it 'should be able to detect a Rails3 app' do app = spec_asset('tests/rails3/hello_vcap') - framework(app).should =~ /Rails/ + framework(app).to_s.should =~ /Rails/ end it 'should be able to detect a Sinatra app' do app = spec_asset('tests/sinatra/hello_vcap') - framework(app).should =~ /Sinatra/ + framework(app).to_s.should =~ /Sinatra/ end it 'should be able to detect a Rack app' do app = spec_asset('tests/rack/app_rack_service') - framework(app,false,[["rack"]]).should =~ /Rack/ + framework(app,false,[["rack"]]).to_s.should =~ /Rack/ end it 'should fall back to Sinatra detection if Rack framework not supported' do app = spec_asset('tests/rack/app_rack_service') - framework(app,false).should =~ /Sinatra/ + framework(app,false).to_s.should =~ /Sinatra/ end it 'should be able to detect a Node.js app' do app = spec_asset('tests/node/hello_vcap') - framework(app).should=~ /Node.js/ + framework(app).to_s.should=~ /Node.js/ + end + + describe 'standalone app support' do + it 'should fall back to Standalone app from single non-WAR file' do + app = spec_asset("tests/standalone/java_app/target/" + + "standalone-java-app-1.0.0.BUILD-SNAPSHOT.jar") + framework(app,false,[["standalone"]]).to_s.should=~ /Standalone/ + end + + it 'should fall back to nil if Standalone framework not supported for single non-WAR file' do + app = spec_asset("tests/standalone/java_app/target/" + + "standalone-java-app-1.0.0.BUILD-SNAPSHOT.jar") + framework(app).should == nil + end + + it 'should fall back to Standalone app if dir does not match other frameworks' do + app = spec_asset('tests/standalone/python_app') + framework(app,false,[["standalone"]]).to_s.should=~ /Standalone/ + end + + it 'should fall back to nil if Standalone framework not supported for dir' do + app = spec_asset('tests/standalone/python_app') + framework(app).should == nil + end + + it 'should detect default Java runtime with a single jar' do + app = spec_asset("tests/standalone/java_app/target/" + + "standalone-java-app-1.0.0.BUILD-SNAPSHOT.jar") + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Java runtime with a zip of jars' do + app = spec_asset("tests/standalone/java_app/target/zip/" + + "standalone-java-app-1.0.0.BUILD-SNAPSHOT-jar.zip") + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Java runtime with a dir containing zip of jar files' do + app = spec_asset('tests/standalone/java_app/target/zip') + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Java runtime with a dir containing jar files' do + app = spec_asset('tests/standalone/java_app/target') + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Java runtime with a single class' do + app = spec_asset('tests/standalone/java_app/target/classes/HelloCloud.class') + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Java runtime with a dir containing class files' do + app = spec_asset('tests/standalone/java_app/target/classes') + framework(app,false,[["standalone"]]).default_runtime(app).should == "java" + end + + it 'should detect default Ruby runtime with a single rb file' do + app = spec_asset('tests/standalone/ruby_app/main.rb') + framework(app,false,[["standalone"]]).default_runtime(app).should == "ruby18" + end + + it 'should detect default Ruby runtime with a dir containing rb files' do + app = spec_asset('tests/standalone/simple_ruby_app') + framework(app,false,[["standalone"]]).default_runtime(app).should == "ruby18" + end + + it 'should return nil for default runtime if framework is not standalone' do + app = spec_asset('tests/lift/hello_lift/target') + framework(app,false,[["standalone"]]).default_runtime(app).should == nil + end + + it 'should return nil for default runtime if zip does not contain jars' do + app = spec_asset("tests/standalone/python_app/target/zip/" + + "standalone-python-1.0.0.BUILD-SNAPSHOT-script.zip") + framework(app,false,[["standalone"]]).default_runtime(app).should == nil + end + + it 'should return nil for default runtime if dir contains zip with no jars' do + app = spec_asset('tests/standalone/python_app/target/zip') + framework(app,false,[["standalone"]]).default_runtime(app).should == nil + end + + it 'should return nil for default runtime if file does not match any rules' do + app = spec_asset('tests/standalone/python_app') + framework(app,false,[["standalone"]]).default_runtime(app).should == nil + end + + it 'should return expected default memory for standalone Java apps' do + app = spec_asset('tests/standalone/java_app/target') + framework(app,false,[["standalone"]]).memory("java").should == '512M' + end + + it 'should return expected default memory for standalone Ruby 1.8 apps' do + app = spec_asset('tests/standalone/ruby_app/main.rb') + framework(app,false,[["standalone"]]).memory("ruby18").should == '128M' + end + + it 'should return expected default memory for standalone Ruby 1.9 apps' do + app = spec_asset('tests/standalone/ruby_app/main.rb') + framework(app,false,[["standalone"]]).memory("ruby19").should == '128M' + end + + it 'should return expected default memory for standalone PHP apps' do + app = spec_asset('tests/standalone/php_app') + framework(app,false,[["standalone"]]).memory("php").should == '128M' + end + + it 'should return expected default memory for standalone apps with other runtimes' do + app = spec_asset('tests/standalone/python_app') + framework(app,false,[["standalone"]]).memory("python").should == '64M' + end + + it 'should return expected default memory for non-standalone apps' do + app = spec_asset('tests/rails3/hello_vcap') + framework(app).mem.should == '256M' + end end def framework app, explode=false, available_frameworks=[] unless explode == true - return VMC::Cli::Framework.detect(app, available_frameworks).to_s + return VMC::Cli::Framework.detect(app, available_frameworks) end Dir.mktmpdir {|dir| exploded_dir = File.join(dir, "exploded") VMC::Cli::ZipUtil.unpack(app, exploded_dir) - VMC::Cli::Framework.detect(exploded_dir, available_frameworks).to_s + VMC::Cli::Framework.detect(exploded_dir, available_frameworks) } end