Merge lp:~logan/ubuntu/quantal/ruby-vmc/new-upstream into lp:ubuntu/quantal/ruby-vmc

Proposed by Logan Rosen on 2012-06-13
Status: Merged
Merged at revision: 6
Proposed branch: lp:~logan/ubuntu/quantal/ruby-vmc/new-upstream
Merge into: lp:ubuntu/quantal/ruby-vmc
Diff against target: 5823 lines (+3220/-1281)
63 files modified
.pc/applied-patches (+0/-1)
.pc/modify-include-path/bin/vmc (+0/-6)
README.md (+12/-2)
Rakefile (+86/-2)
bin/vmc (+2/-2)
caldecott_helper/Gemfile (+10/-0)
caldecott_helper/Gemfile.lock (+48/-0)
caldecott_helper/server.rb (+43/-0)
config/clients.yml (+17/-0)
config/micro/offline.conf (+2/-0)
config/micro/paths.yml (+22/-0)
config/micro/refresh_ip.rb (+20/-0)
debian/changelog (+6/-0)
lib/cli.rb (+19/-2)
lib/cli/commands/admin.rb (+31/-8)
lib/cli/commands/apps.rb (+658/-469)
lib/cli/commands/base.rb (+169/-21)
lib/cli/commands/manifest.rb (+56/-0)
lib/cli/commands/micro.rb (+115/-0)
lib/cli/commands/misc.rb (+5/-4)
lib/cli/commands/services.rb (+112/-16)
lib/cli/commands/user.rb (+13/-8)
lib/cli/config.rb (+85/-22)
lib/cli/console_helper.rb (+160/-0)
lib/cli/core_ext.rb (+4/-1)
lib/cli/frameworks.rb (+201/-33)
lib/cli/manifest_helper.rb (+302/-0)
lib/cli/runner.rb (+65/-19)
lib/cli/services_helper.rb (+19/-9)
lib/cli/tunnel_helper.rb (+332/-0)
lib/cli/usage.rb (+16/-4)
lib/cli/version.rb (+1/-1)
lib/cli/zip_util.rb (+1/-1)
lib/vmc/client.rb (+62/-26)
lib/vmc/const.rb (+8/-7)
lib/vmc/micro.rb (+56/-0)
lib/vmc/micro/switcher/base.rb (+97/-0)
lib/vmc/micro/switcher/darwin.rb (+19/-0)
lib/vmc/micro/switcher/dummy.rb (+15/-0)
lib/vmc/micro/switcher/linux.rb (+16/-0)
lib/vmc/micro/switcher/windows.rb (+31/-0)
lib/vmc/micro/vmrun.rb (+158/-0)
metadata.yml (+126/-73)
spec/assets/app_info.txt (+0/-9)
spec/assets/app_listings.txt (+0/-9)
spec/assets/bad_create_app.txt (+0/-9)
spec/assets/delete_app.txt (+0/-9)
spec/assets/global_service_listings.txt (+0/-9)
spec/assets/good_create_app.txt (+0/-9)
spec/assets/good_create_service.txt (+0/-9)
spec/assets/info_authenticated.txt (+0/-27)
spec/assets/info_return.txt (+0/-15)
spec/assets/info_return_bad.txt (+0/-16)
spec/assets/login_fail.txt (+0/-9)
spec/assets/login_success.txt (+0/-9)
spec/assets/sample_token.txt (+0/-1)
spec/assets/service_already_exists.txt (+0/-9)
spec/assets/service_listings.txt (+0/-9)
spec/assets/service_not_found.txt (+0/-9)
spec/assets/user_info.txt (+0/-9)
spec/spec_helper.rb (+0/-11)
spec/unit/cli_opts_spec.rb (+0/-73)
spec/unit/client_spec.rb (+0/-284)
To merge this branch: bzr merge lp:~logan/ubuntu/quantal/ruby-vmc/new-upstream
Reviewer Review Type Date Requested Status
Michael Terry Approve on 2012-06-14
Ubuntu branches 2012-06-13 Pending
Review via email: mp+109967@code.launchpad.net
To post a comment you must log in.
Michael Terry (mterry) wrote :

Looks fine, thanks! Again, UNRELEASED->quantal.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file '.pc/applied-patches'
2--- .pc/applied-patches 2011-06-17 13:38:56 +0000
3+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
4@@ -1,1 +0,0 @@
5-modify-include-path
6
7=== removed directory '.pc/modify-include-path'
8=== removed directory '.pc/modify-include-path/bin'
9=== removed file '.pc/modify-include-path/bin/vmc'
10--- .pc/modify-include-path/bin/vmc 2011-06-17 13:38:56 +0000
11+++ .pc/modify-include-path/bin/vmc 1970-01-01 00:00:00 +0000
12@@ -1,6 +0,0 @@
13-#!/usr/bin/env ruby
14-
15-require File.expand_path('../../lib/cli', __FILE__)
16-
17-VMC::Cli::Runner.run(ARGV.dup)
18-
19
20=== modified file 'README.md'
21--- README.md 2011-06-17 13:38:56 +0000
22+++ README.md 2012-06-13 01:37:18 +0000
23@@ -31,7 +31,6 @@
24 stop <appname> Stop the application
25 restart <appname> Restart the application
26 delete <appname> Delete the application
27- rename <appname> <newname> Rename the application
28
29 Application Updates
30 update <appname> [--path] Update the application bits
31@@ -62,6 +61,8 @@
32 bind-service <servicename> <appname> Bind a service to an application
33 unbind-service <servicename> <appname> Unbind service from the application
34 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
35+ tunnel <servicename> [--port] Create a local tunnel to a service
36+ tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client
37
38 Administration
39 user Display user account information
40@@ -74,6 +75,15 @@
41 runtimes Display the supported runtimes of the target system
42 frameworks Display the recognized frameworks of the target system
43
44+ Micro Cloud Foundry
45+ micro status Display Micro Cloud Foundry VM status
46+ mciro offline Configure Micro Cloud Foundry VM for offline mode
47+ micro online Configure Micro Cloud Foundry VM for online mode
48+ [--vmx file] Path to micro.vmx
49+ [--vmrun executable] Path to vmrun executable
50+ [--password cleartext] Cleartext password for guest VM vcap user
51+ [--save] Save cleartext password in ~/.vmc_micro
52+
53 Misc
54 aliases List aliases
55 alias <alias[=]command> Create an alias for a command
56@@ -86,7 +96,7 @@
57
58 ## Simple Story (for Ruby apps)
59
60- vmc target api.vcloudlabs.com
61+ vmc target api.cloudfoundry.com
62 vmc login
63 bundle package
64 vmc push
65
66=== modified file 'Rakefile'
67--- Rakefile 2011-06-17 13:38:56 +0000
68+++ Rakefile 2012-06-13 01:37:18 +0000
69@@ -2,8 +2,7 @@
70 require 'spec/rake/spectask'
71
72 desc "Run specs"
73-task :spec do
74- sh('bundle install')
75+task :spec => :build do
76 Spec::Rake::SpecTask.new('spec') do |t|
77 t.spec_opts = %w(-fs -c)
78 t.spec_files = FileList['spec/**/*_spec.rb']
79@@ -15,3 +14,88 @@
80 desc "Synonym for spec"
81 task :tests => :spec
82 task :default => :spec
83+
84+def tests_path
85+ if @tests_path == nil
86+ @tests_path = File.join(Dir.pwd, "spec/assets/tests")
87+ end
88+ @tests_path
89+end
90+TESTS_PATH = tests_path
91+
92+BUILD_ARTIFACT = File.join(Dir.pwd, "spec/assets/.build")
93+
94+TESTS_TO_BUILD = ["#{TESTS_PATH}/java_web/java_tiny_app",
95+# "#{TESTS_PATH}/grails/guestbook",
96+ "#{TESTS_PATH}/lift/hello_lift",
97+ "#{TESTS_PATH}/spring/roo-guestbook",
98+ "#{TESTS_PATH}/spring/spring-osgi-hello",
99+ "#{TESTS_PATH}/standalone/java_app",
100+ "#{TESTS_PATH}/standalone/python_app"
101+ ]
102+
103+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]'"
104+task :build, [:force] do |t, args|
105+ sh('bundle install')
106+ sh('git submodule update --init')
107+ puts "\nBuilding tests"
108+ if build_required? args.force
109+ ENV['MAVEN_OPTS']="-XX:MaxPermSize=256M"
110+ TESTS_TO_BUILD.each do |test|
111+ puts "\tBuilding '#{test}'"
112+ Dir.chdir test do
113+ sh('mvn package -DskipTests') do |success, exit_code|
114+ unless success
115+ clear_build_artifact
116+ do_mvn_clean('-q')
117+ fail "\tFailed to build #{test} - aborting build"
118+ end
119+ end
120+ end
121+ puts "\tCompleted building '#{test}'"
122+ end
123+ save_git_hash
124+ else
125+ puts "Built artifacts in sync with test assets - no build required"
126+ end
127+end
128+
129+desc "Clean the build artifacts"
130+task :clean do
131+ puts "\nCleaning tests"
132+ clear_build_artifact
133+ TESTS_TO_BUILD.each do |test|
134+ puts "\tCleaning '#{test}'"
135+ Dir.chdir test do
136+ do_mvn_clean
137+ end
138+ puts "\tCompleted cleaning '#{test}'"
139+ end
140+end
141+
142+def build_required? (force_build=nil)
143+ if File.exists?(BUILD_ARTIFACT) == false or (force_build and force_build == "--force")
144+ return true
145+ end
146+ Dir.chdir(tests_path) do
147+ saved_git_hash = IO.readlines(BUILD_ARTIFACT)[0].split[0]
148+ git_hash = `git rev-parse --short=8 --verify HEAD`
149+ saved_git_hash.to_s.strip != git_hash.to_s.strip
150+ end
151+end
152+
153+def save_git_hash
154+ Dir.chdir(tests_path) do
155+ git_hash = `git rev-parse --short=8 --verify HEAD`
156+ File.open(BUILD_ARTIFACT, 'w') {|f| f.puts("#{git_hash}")}
157+ end
158+end
159+
160+def clear_build_artifact
161+ puts "\tClearing build artifact #{BUILD_ARTIFACT}"
162+ File.unlink BUILD_ARTIFACT if File.exists? BUILD_ARTIFACT
163+end
164+
165+def do_mvn_clean options=nil
166+ sh("mvn clean #{options}")
167+end
168
169=== modified file 'bin/vmc'
170--- bin/vmc 2011-06-17 13:38:56 +0000
171+++ bin/vmc 2012-06-13 01:37:18 +0000
172@@ -1,6 +1,6 @@
173-#!/usr/bin/ruby1.9.1
174+#!/usr/bin/env ruby
175
176-require File.expand_path('/usr/lib/ruby/vendor_ruby/cli', __FILE__)
177+require File.expand_path('../../lib/cli', __FILE__)
178
179 VMC::Cli::Runner.run(ARGV.dup)
180
181
182=== added directory 'caldecott_helper'
183=== added file 'caldecott_helper/Gemfile'
184--- caldecott_helper/Gemfile 1970-01-01 00:00:00 +0000
185+++ caldecott_helper/Gemfile 2012-06-13 01:37:18 +0000
186@@ -0,0 +1,10 @@
187+source "http://rubygems.org"
188+
189+gem 'rack', '~> 1.2.0'
190+gem 'caldecott', '= 0.0.3'
191+gem 'bundler'
192+gem 'em-websocket'
193+gem 'async_sinatra'
194+gem 'thin'
195+gem 'json'
196+gem 'uuidtools'
197
198=== added file 'caldecott_helper/Gemfile.lock'
199--- caldecott_helper/Gemfile.lock 1970-01-01 00:00:00 +0000
200+++ caldecott_helper/Gemfile.lock 2012-06-13 01:37:18 +0000
201@@ -0,0 +1,48 @@
202+GEM
203+ remote: http://rubygems.org/
204+ specs:
205+ addressable (2.2.6)
206+ async_sinatra (0.5.0)
207+ rack (>= 1.2.1)
208+ sinatra (>= 1.0)
209+ caldecott (0.0.3)
210+ addressable (= 2.2.6)
211+ async_sinatra (= 0.5.0)
212+ em-http-request (= 0.3.0)
213+ em-websocket (= 0.3.1)
214+ json (= 1.6.1)
215+ uuidtools (= 2.1.2)
216+ daemons (1.1.4)
217+ em-http-request (0.3.0)
218+ addressable (>= 2.0.0)
219+ escape_utils
220+ eventmachine (>= 0.12.9)
221+ em-websocket (0.3.1)
222+ addressable (>= 2.1.1)
223+ eventmachine (>= 0.12.9)
224+ escape_utils (0.2.4)
225+ eventmachine (0.12.10)
226+ json (1.6.1)
227+ rack (1.2.4)
228+ sinatra (1.2.7)
229+ rack (~> 1.1)
230+ tilt (>= 1.2.2, < 2.0)
231+ thin (1.2.11)
232+ daemons (>= 1.0.9)
233+ eventmachine (>= 0.12.6)
234+ rack (>= 1.0.0)
235+ tilt (1.3.3)
236+ uuidtools (2.1.2)
237+
238+PLATFORMS
239+ ruby
240+
241+DEPENDENCIES
242+ async_sinatra
243+ bundler
244+ caldecott (= 0.0.3)
245+ em-websocket
246+ json
247+ rack (~> 1.2.0)
248+ thin
249+ uuidtools
250
251=== added file 'caldecott_helper/server.rb'
252--- caldecott_helper/server.rb 1970-01-01 00:00:00 +0000
253+++ caldecott_helper/server.rb 2012-06-13 01:37:18 +0000
254@@ -0,0 +1,43 @@
255+#!/usr/bin/env ruby
256+# Copyright (c) 2009-2011 VMware, Inc.
257+$:.unshift(File.dirname(__FILE__) + '/lib')
258+
259+require 'rubygems'
260+require 'bundler/setup'
261+
262+require 'caldecott'
263+require 'sinatra'
264+require 'json'
265+require 'eventmachine'
266+
267+port = ENV['VMC_APP_PORT']
268+port ||= 8081
269+
270+# add vcap specific stuff to Caldecott
271+class VcapHttpTunnel < Caldecott::Server::HttpTunnel
272+ get '/info' do
273+ { "version" => '0.0.4' }.to_json
274+ end
275+
276+ def self.get_tunnels
277+ super
278+ end
279+
280+ get '/services' do
281+ services_env = ENV['VMC_SERVICES']
282+ return "no services env" if services_env.nil? or services_env.empty?
283+ services_env
284+ end
285+
286+ get '/services/:service' do |service_name|
287+ services_env = ENV['VMC_SERVICES']
288+ not_found if services_env.nil?
289+
290+ services = JSON.parse(services_env)
291+ service = services.find { |s| s["name"] == service_name }
292+ not_found if service.nil?
293+ service["options"].to_json
294+ end
295+end
296+
297+VcapHttpTunnel.run!(:port => port, :auth_token => ENV["CALDECOTT_AUTH"])
298
299=== added directory 'config'
300=== added file 'config/clients.yml'
301--- config/clients.yml 1970-01-01 00:00:00 +0000
302+++ config/clients.yml 2012-06-13 01:37:18 +0000
303@@ -0,0 +1,17 @@
304+redis:
305+ redis-cli: -h ${host} -p ${port} -a ${password}
306+
307+mysql:
308+ mysql: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name}
309+ mysqldump: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name} > ${Output file}
310+
311+mongodb:
312+ mongo: --host ${host} --port ${port} -u ${user} -p ${password} ${name}
313+ mongodump: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name}
314+ mongorestore: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name} ${Directory or filename to restore from}
315+
316+postgresql:
317+ psql:
318+ command: -h ${host} -p ${port} -d ${name} -U ${user} -w
319+ environment:
320+ - PGPASSWORD='${password}'
321
322=== added directory 'config/micro'
323=== added file 'config/micro/offline.conf'
324--- config/micro/offline.conf 1970-01-01 00:00:00 +0000
325+++ config/micro/offline.conf 2012-06-13 01:37:18 +0000
326@@ -0,0 +1,2 @@
327+no-resolv
328+log-queries
329
330=== added file 'config/micro/paths.yml'
331--- config/micro/paths.yml 1970-01-01 00:00:00 +0000
332+++ config/micro/paths.yml 2012-06-13 01:37:18 +0000
333@@ -0,0 +1,22 @@
334+darwin:
335+ vmrun:
336+ - "/Applications/VMware Fusion.app/Contents/Library/"
337+ - "/Applications/Fusion.app/Contents/Library/"
338+ vmx:
339+ - "~/Documents/Virtual Machines.localized/"
340+ - "~/Documents/Virtual Machines/"
341+ - "~/Desktop/"
342+
343+linux:
344+ vmrun:
345+ - "/usr/bin/"
346+ vmx:
347+ - "~/"
348+
349+windows:
350+ vmrun:
351+ - "c:\\Program Files (x86)\\"
352+ - "c:\\Program Files\\"
353+ vmx:
354+ - "~\\Documents\\"
355+ - "~\\Desktop\\"
356
357=== added file 'config/micro/refresh_ip.rb'
358--- config/micro/refresh_ip.rb 1970-01-01 00:00:00 +0000
359+++ config/micro/refresh_ip.rb 2012-06-13 01:37:18 +0000
360@@ -0,0 +1,20 @@
361+#!/var/vcap/bosh/bin/ruby
362+require 'socket'
363+
364+A_ROOT_SERVER = '198.41.0.4'
365+
366+begin
367+retries ||= 0
368+route ||= A_ROOT_SERVER
369+orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
370+ip_address = UDPSocket.open {|s| s.connect(route, 1); s.addr.last }
371+rescue Errno::ENETUNREACH
372+ # happens on boot when dhcp hasn't completed when we get here
373+ sleep 3
374+ retries += 1
375+ retry if retries < 10
376+ensure
377+ Socket.do_not_reverse_lookup = orig
378+end
379+
380+File.open("/tmp/ip.txt", 'w') { |file| file.write(ip_address) }
381
382=== modified file 'debian/changelog'
383--- debian/changelog 2011-08-09 14:45:42 +0000
384+++ debian/changelog 2012-06-13 01:37:18 +0000
385@@ -1,3 +1,9 @@
386+ruby-vmc (0.3.18-0ubuntu1) UNRELEASED; urgency=low
387+
388+ * New upstream release (LP: #998111).
389+
390+ -- Logan Rosen <logatronico@gmail.com> Tue, 12 Jun 2012 21:31:33 -0400
391+
392 ruby-vmc (0.3.10-0ubuntu10) oneiric; urgency=low
393
394 [ Marc Cluet ]
395
396=== modified file 'lib/cli.rb'
397--- lib/cli.rb 2011-06-17 13:38:56 +0000
398+++ lib/cli.rb 2012-06-13 01:37:18 +0000
399@@ -1,25 +1,42 @@
400+require "rbconfig"
401
402 ROOT = File.expand_path(File.dirname(__FILE__))
403+WINDOWS = !!(RbConfig::CONFIG['host_os'] =~ /mingw|mswin32|cygwin/)
404
405 module VMC
406-
407 autoload :Client, "#{ROOT}/vmc/client"
408+ autoload :Micro, "#{ROOT}/vmc/micro"
409+
410+ module Micro
411+ module Switcher
412+ autoload :Base, "#{ROOT}/vmc/micro/switcher/base"
413+ autoload :Darwin, "#{ROOT}/vmc/micro/switcher/darwin"
414+ autoload :Dummy, "#{ROOT}/vmc/micro/switcher/dummy"
415+ autoload :Linux, "#{ROOT}/vmc/micro/switcher/linux"
416+ autoload :Windows, "#{ROOT}/vmc/micro/switcher/windows"
417+ end
418+ autoload :VMrun, "#{ROOT}/vmc/micro/vmrun"
419+ end
420
421 module Cli
422-
423 autoload :Config, "#{ROOT}/cli/config"
424 autoload :Framework, "#{ROOT}/cli/frameworks"
425 autoload :Runner, "#{ROOT}/cli/runner"
426 autoload :ZipUtil, "#{ROOT}/cli/zip_util"
427 autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
428+ autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
429+ autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
430+ autoload :ConsoleHelper, "#{ROOT}/cli/console_helper"
431
432 module Command
433 autoload :Base, "#{ROOT}/cli/commands/base"
434 autoload :Admin, "#{ROOT}/cli/commands/admin"
435 autoload :Apps, "#{ROOT}/cli/commands/apps"
436+ autoload :Micro, "#{ROOT}/cli/commands/micro"
437 autoload :Misc, "#{ROOT}/cli/commands/misc"
438 autoload :Services, "#{ROOT}/cli/commands/services"
439 autoload :User, "#{ROOT}/cli/commands/user"
440+ autoload :Manifest, "#{ROOT}/cli/commands/manifest"
441 end
442
443 end
444
445=== modified file 'lib/cli/commands/admin.rb'
446--- lib/cli/commands/admin.rb 2011-06-17 13:38:56 +0000
447+++ lib/cli/commands/admin.rb 2012-06-13 01:37:18 +0000
448@@ -2,13 +2,32 @@
449
450 class Admin < Base
451
452+ def list_users
453+ users = client.users
454+ users.sort! {|a, b| a[:email] <=> b[:email] }
455+ return display JSON.pretty_generate(users || []) if @options[:json]
456+
457+ display "\n"
458+ return display "No Users" if users.nil? || users.empty?
459+
460+ users_table = table do |t|
461+ t.headings = 'Email', 'Admin', 'Apps'
462+ users.each do |user|
463+ t << [user[:email], user[:admin], user[:apps].map {|x| x[:name]}.join(', ')]
464+ end
465+ end
466+ display users_table
467+ end
468+
469+ alias :users :list_users
470+
471 def add_user(email=nil)
472- email = @options[:email] unless email
473+ email ||= @options[:email]
474+ email ||= ask("Email") unless no_prompt
475 password = @options[:password]
476- email = ask("Email: ") unless no_prompt || email
477 unless no_prompt || password
478- password = ask("Password: ") {|q| q.echo = '*'}
479- password2 = ask("Verify Password: ") {|q| q.echo = '*'}
480+ password = ask("Password", :echo => "*")
481+ password2 = ask("Verify Password", :echo => "*")
482 err "Passwords did not match, try again" if password != password2
483 end
484 err "Need a valid email" unless email
485@@ -34,11 +53,14 @@
486
487 if (apps && !apps.empty?)
488 unless no_prompt
489- proceed = ask("\nDeployed applications and associated services will be DELETED, continue? [yN]: ")
490- err "Aborted" if proceed.upcase != 'Y'
491+ proceed = ask(
492+ "\nDeployed applications and associated services will be DELETED, continue?",
493+ :default => false
494+ )
495+ err "Aborted" unless proceed
496 end
497- cmd = Apps.new(@options)
498- apps.each { |app| cmd.delete_app(app[:name], true) }
499+ cmd = Apps.new(@options.merge({ :force => true }))
500+ apps.each { |app| cmd.delete(app[:name]) }
501 end
502
503 services = client.services
504@@ -48,6 +70,7 @@
505 end
506
507 display 'Deleting User: ', false
508+ client.proxy = nil
509 client.delete_user(user_email)
510 display 'OK'.green
511 end
512
513=== modified file 'lib/cli/commands/apps.rb'
514--- lib/cli/commands/apps.rb 2011-06-17 13:38:56 +0000
515+++ lib/cli/commands/apps.rb 2012-06-13 01:37:18 +0000
516@@ -1,16 +1,23 @@
517 require 'digest/sha1'
518 require 'fileutils'
519+require 'pathname'
520 require 'tempfile'
521 require 'tmpdir'
522 require 'set'
523+require "uuidtools"
524+require 'socket'
525
526 module VMC::Cli::Command
527
528 class Apps < Base
529 include VMC::Cli::ServicesHelper
530+ include VMC::Cli::ManifestHelper
531+ include VMC::Cli::TunnelHelper
532+ include VMC::Cli::ConsoleHelper
533
534 def list
535 apps = client.apps
536+ apps.sort! {|a, b| a[:name] <=> b[:name] }
537 return display JSON.pretty_generate(apps || []) if @options[:json]
538
539 display "\n"
540@@ -35,108 +42,97 @@
541 HEALTH_TICKS = 5/SLEEP_TIME
542 TAIL_TICKS = 45/SLEEP_TIME
543 GIVEUP_TICKS = 120/SLEEP_TIME
544- YES_SET = Set.new(["y", "Y", "yes", "YES"])
545-
546- def start(appname, push = false)
547- app = client.app_info(appname)
548-
549- return display "Application '#{appname}' could not be found".red if app.nil?
550- return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
551-
552- banner = 'Staging Application: '
553- display banner, false
554-
555- t = Thread.new do
556- count = 0
557- while count < TAIL_TICKS do
558- display '.', false
559- sleep SLEEP_TIME
560- count += 1
561- end
562- end
563-
564- app[:state] = 'STARTED'
565- client.update_app(appname, app)
566-
567- Thread.kill(t)
568- clear(LINE_LENGTH)
569- display "#{banner}#{'OK'.green}"
570-
571- banner = 'Starting Application: '
572- display banner, false
573-
574- count = log_lines_displayed = 0
575- failed = false
576- start_time = Time.now.to_i
577-
578- loop do
579- display '.', false unless count > TICKER_TICKS
580- sleep SLEEP_TIME
581- begin
582- break if app_started_properly(appname, count > HEALTH_TICKS)
583- if !crashes(appname, false, start_time).empty?
584- # Check for the existance of crashes
585- display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
586- grab_crash_logs(appname, '0', true)
587- if push
588- display "\n"
589- should_delete = ask 'Should I delete the application? (Y/n)? ' unless no_prompt
590- delete_app(appname, false) unless no_prompt || should_delete.upcase == 'N'
591- end
592- failed = true
593- break
594- elsif count > TAIL_TICKS
595- log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
596- end
597- rescue => e
598- err(e.message, '')
599- end
600- count += 1
601- if count > GIVEUP_TICKS # 2 minutes
602- display "\nApplication is taking too long to start, check your logs".yellow
603- break
604- end
605- end
606- exit(false) if failed
607- clear(LINE_LENGTH)
608- display "#{banner}#{'OK'.green}"
609- end
610-
611- def stop(appname)
612- app = client.app_info(appname)
613- return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
614- display 'Stopping Application: ', false
615- app[:state] = 'STOPPED'
616- client.update_app(appname, app)
617- display 'OK'.green
618- end
619-
620- def restart(appname)
621+
622+ def info(what, default=nil)
623+ @options[what] || (@app_info && @app_info[what.to_s]) || default
624+ end
625+
626+ def console(appname, interactive=true)
627+ unless defined? Caldecott
628+ display "To use `vmc rails-console', you must first install Caldecott:"
629+ display ""
630+ display "\tgem install caldecott"
631+ display ""
632+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
633+ display "will provide one. If you're on Windows, try DevKit."
634+ display ""
635+ display "This manual step will be removed in the future."
636+ display ""
637+ err "Caldecott is not installed."
638+ end
639+
640+ #Make sure there is a console we can connect to first
641+ conn_info = console_connection_info appname
642+
643+ port = pick_tunnel_port(@options[:port] || 20000)
644+
645+ raise VMC::Client::AuthError unless client.logged_in?
646+
647+ if not tunnel_pushed?
648+ display "Deploying tunnel application '#{tunnel_appname}'."
649+ auth = UUIDTools::UUID.random_create.to_s
650+ push_caldecott(auth)
651+ start_caldecott
652+ else
653+ auth = tunnel_auth
654+ end
655+
656+ if not tunnel_healthy?(auth)
657+ display "Redeploying tunnel application '#{tunnel_appname}'."
658+ # We don't expect caldecott not to be running, so take the
659+ # most aggressive restart method.. delete/re-push
660+ client.delete_app(tunnel_appname)
661+ invalidate_tunnel_app_info
662+ push_caldecott(auth)
663+ start_caldecott
664+ end
665+
666+ start_tunnel(port, conn_info, auth)
667+ wait_for_tunnel_start(port)
668+ start_local_console(port, appname) if interactive
669+ port
670+ end
671+
672+ def start(appname=nil, push=false)
673+ if appname
674+ do_start(appname, push)
675+ else
676+ each_app do |name|
677+ do_start(name, push)
678+ end
679+ end
680+ end
681+
682+ def stop(appname=nil)
683+ if appname
684+ do_stop(appname)
685+ else
686+ reversed = []
687+ each_app do |name|
688+ reversed.unshift name
689+ end
690+
691+ reversed.each do |name|
692+ do_stop(name)
693+ end
694+ end
695+ end
696+
697+ def restart(appname=nil)
698 stop(appname)
699 start(appname)
700 end
701
702- def rename(appname, newname)
703- app = client.app_info(appname)
704- app[:name] = newname
705- display 'Renaming Appliction: '
706- client.update_app(newname, app)
707- display 'OK'.green
708- end
709-
710 def mem(appname, memsize=nil)
711 app = client.app_info(appname)
712 mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
713 memsize = normalize_mem(memsize) if memsize
714
715- unless memsize
716- choose do |menu|
717- menu.layout = :one_line
718- menu.prompt = "Update Memory Reservation? [Current:#{current_mem}] "
719- menu.default = current_mem
720- mem_choices.each { |choice| menu.choice(choice) { memsize = choice } }
721- end
722- end
723+ memsize ||= ask(
724+ "Update Memory Reservation?",
725+ :default => current_mem,
726+ :choices => mem_choices
727+ )
728
729 mem = mem_choice_to_quota(mem)
730 memsize = mem_choice_to_quota(memsize)
731@@ -165,7 +161,7 @@
732 uris << url
733 app[:uris] = uris
734 client.update_app(appname, app)
735- display "Succesfully mapped url".green
736+ display "Successfully mapped url".green
737 end
738
739 def unmap(appname, url)
740@@ -176,18 +172,13 @@
741 err "Invalid url" unless deleted
742 app[:uris] = uris
743 client.update_app(appname, app)
744- display "Succesfully unmapped url".green
745-
746+ display "Successfully unmapped url".green
747 end
748
749 def delete(appname=nil)
750 force = @options[:force]
751 if @options[:all]
752- should_delete = force && no_prompt ? 'Y' : 'N'
753- unless no_prompt || force
754- should_delete = ask 'Delete ALL Applications and Services? (y/N)? '
755- end
756- if should_delete.upcase == 'Y'
757+ if no_prompt || force || ask("Delete ALL applications?", :default => false)
758 apps = client.apps
759 apps.each { |app| delete_app(app[:name], force) }
760 end
761@@ -197,48 +188,18 @@
762 end
763 end
764
765- def delete_app(appname, force)
766- app = client.app_info(appname)
767- services_to_delete = []
768- app_services = app[:services]
769- app_services.each { |service|
770- del_service = force && no_prompt ? 'Y' : 'N'
771- unless no_prompt || force
772- del_service = ask("Provisioned service [#{service}] detected, would you like to delete it? [yN]: ")
773- end
774- services_to_delete << service if del_service.upcase == 'Y'
775- }
776- display "Deleting application [#{appname}]: ", false
777- client.delete_app(appname)
778- display 'OK'.green
779-
780- services_to_delete.each do |s|
781- display "Deleting service [#{s}]: ", false
782- client.delete_service(s)
783- display 'OK'.green
784- end
785- end
786-
787- def all_files(appname, path)
788- instances_info_envelope = client.app_instances(appname)
789- return if instances_info_envelope.is_a?(Array)
790- instances_info = instances_info_envelope[:instances] || []
791- instances_info.each do |entry|
792- content = client.app_files(appname, path, entry[:index])
793- display_logfile(path, content, entry[:index], "====> [#{entry[:index]}: #{path}] <====\n".bold)
794- end
795- end
796-
797 def files(appname, path='/')
798 return all_files(appname, path) if @options[:all] && !@options[:instance]
799 instance = @options[:instance] || '0'
800 content = client.app_files(appname, path, instance)
801 display content
802- rescue VMC::Client::NotFound => e
803+ rescue VMC::Client::NotFound, VMC::Client::TargetError
804 err 'No such file or directory'
805 end
806
807 def logs(appname)
808+ # Check if we have an app before progressing further
809+ client.app_info(appname)
810 return grab_all_logs(appname) if @options[:all] && !@options[:instance]
811 instance = @options[:instance] || '0'
812 grab_logs(appname, instance)
813@@ -285,194 +246,67 @@
814 end
815
816 def instances(appname, num=nil)
817- if (num)
818+ if num
819 change_instances(appname, num)
820 else
821 get_instances(appname)
822 end
823 end
824
825- def stats(appname)
826- stats = client.app_stats(appname)
827- return display JSON.pretty_generate(stats) if @options[:json]
828-
829- stats_table = table do |t|
830- t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
831- stats.each do |entry|
832- index = entry[:instance]
833- stat = entry[:stats]
834- hp = "#{stat[:host]}:#{stat[:port]}"
835- uptime = uptime_string(stat[:uptime])
836- usage = stat[:usage]
837- if usage
838- cpu = usage[:cpu]
839- mem = (usage[:mem] * 1024) # mem comes in K's
840- disk = usage[:disk]
841- end
842- mem_quota = stat[:mem_quota]
843- disk_quota = stat[:disk_quota]
844- mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
845- disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
846- cpu = cpu ? cpu.to_s : 'NA'
847- cpu = "#{cpu}% (#{stat[:cores]})"
848- t << [index, cpu, mem, disk, uptime]
849+ def stats(appname=nil)
850+ if appname
851+ display "\n", false
852+ do_stats(appname)
853+ else
854+ each_app do |n|
855+ display "\n#{n}:"
856+ do_stats(n)
857 end
858 end
859- display "\n"
860- if stats.empty?
861- display "No running instances for [#{appname}]".yellow
862- else
863- display stats_table
864- end
865 end
866
867- def update(appname)
868- app = client.app_info(appname)
869- if @options[:canary]
870- display "[--canary] is deprecated and will be removed in a future version".yellow
871+ def update(appname=nil)
872+ if appname
873+ app = client.app_info(appname)
874+ if @options[:canary]
875+ display "[--canary] is deprecated and will be removed in a future version".yellow
876+ end
877+ upload_app_bits(appname, @path)
878+ restart appname if app[:state] == 'STARTED'
879+ else
880+ each_app do |name|
881+ display "Updating application '#{name}'..."
882+
883+ app = client.app_info(name)
884+ upload_app_bits(name, @application)
885+ restart name if app[:state] == 'STARTED'
886+ end
887 end
888- path = @options[:path] || '.'
889- upload_app_bits(appname, path)
890- restart appname if app[:state] == 'STARTED'
891 end
892
893 def push(appname=nil)
894- instances = @options[:instances] || 1
895- exec = @options[:exec] || 'thin start'
896- ignore_framework = @options[:noframework]
897- no_start = @options[:nostart]
898-
899- path = @options[:path] || '.'
900- appname = @options[:name] unless appname
901- url = @options[:url]
902- mem, memswitch = nil, @options[:mem]
903- memswitch = normalize_mem(memswitch) if memswitch
904-
905- # Check app existing upfront if we have appname
906- app_checked = false
907- if appname
908- err "Application '#{appname}' already exists, use update" if app_exists?(appname)
909- app_checked = true
910- else
911- raise VMC::Client::AuthError unless client.logged_in?
912- end
913-
914- # check if we have hit our app limit
915- check_app_limit
916-
917- # check memsize here for capacity
918- if memswitch && !no_start
919- check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
920- end
921-
922 unless no_prompt || @options[:path]
923- proceed = ask('Would you like to deploy from the current directory? [Yn]: ')
924- if proceed.upcase == 'N'
925- path = ask('Please enter in the deployment path: ')
926- end
927- end
928-
929- path = File.expand_path(path)
930- check_deploy_directory(path)
931-
932- appname = ask("Application Name: ") unless no_prompt || appname
933- err "Application Name required." if appname.nil? || appname.empty?
934-
935- unless app_checked
936- err "Application '#{appname}' already exists, use update or delete." if app_exists?(appname)
937- end
938-
939- unless no_prompt || url
940- url = ask("Application Deployed URL: '#{appname}.#{VMC::Cli::Config.suggest_url}'? ")
941-
942- # common error case is for prompted users to answer y or Y or yes or YES to this ask() resulting in an
943- # unintended URL of y. Special case this common error
944- if YES_SET.member?(url)
945- #silently revert to the stock url
946- url = "#{appname}.#{VMC::Cli::Config.suggest_url}"
947- end
948- end
949-
950- url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if url.nil? || url.empty?
951-
952- # Detect the appropriate framework.
953- framework = nil
954- unless ignore_framework
955- framework = VMC::Cli::Framework.detect(path)
956- framework_correct = ask("Detected a #{framework}, is this correct? [Yn]: ") if prompt_ok && framework
957- framework_correct ||= 'y'
958- if prompt_ok && (framework.nil? || framework_correct.upcase == 'N')
959- display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
960- framework = nil if framework_correct.upcase == 'N'
961- choose do |menu|
962- menu.layout = :one_line
963- menu.prompt = "Select Application Type: "
964- menu.default = framework
965- VMC::Cli::Framework.known_frameworks.each do |f|
966- menu.choice(f) { framework = VMC::Cli::Framework.lookup(f) }
967- end
968- end
969- display "Selected #{framework}"
970- end
971- # Framework override, deprecated
972- exec = framework.exec if framework && framework.exec
973- else
974- framework = VMC::Cli::Framework.new
975- end
976-
977- err "Application Type undetermined for path '#{path}'" unless framework
978- unless memswitch
979- mem = framework.memory
980- if prompt_ok
981- choose do |menu|
982- menu.layout = :one_line
983- menu.prompt = "Memory Reservation [Default:#{mem}] "
984- menu.default = mem
985- mem_choices.each { |choice| menu.choice(choice) { mem = choice } }
986- end
987- end
988- else
989- mem = memswitch
990- end
991-
992- # Set to MB number
993- mem_quota = mem_choice_to_quota(mem)
994-
995- # check memsize here for capacity
996- check_has_capacity_for(mem_quota * instances) unless no_start
997-
998- display 'Creating Application: ', false
999-
1000- manifest = {
1001- :name => "#{appname}",
1002- :staging => {
1003- :framework => framework.name,
1004- :runtime => @options[:runtime]
1005- },
1006- :uris => [url],
1007- :instances => instances,
1008- :resources => {
1009- :memory => mem_quota
1010- },
1011- }
1012-
1013- # Send the manifest to the cloud controller
1014- client.create_app(appname, manifest)
1015- display 'OK'.green
1016-
1017- # Services check
1018- unless no_prompt || @options[:noservices]
1019- services = client.services_info
1020- unless services.empty?
1021- proceed = ask("Would you like to bind any services to '#{appname}'? [yN]: ")
1022- bind_services(appname, services) if proceed.upcase == 'Y'
1023- end
1024- end
1025-
1026- # Stage and upload the app bits.
1027- upload_app_bits(appname, path)
1028-
1029- start(appname, true) unless no_start
1030+ proceed = ask(
1031+ 'Would you like to deploy from the current directory?',
1032+ :default => true
1033+ )
1034+
1035+ unless proceed
1036+ @path = ask('Deployment path')
1037+ end
1038+ end
1039+
1040+ pushed = false
1041+ each_app(false) do |name|
1042+ display "Pushing application '#{name}'..." if name
1043+ do_push(name)
1044+ pushed = true
1045+ end
1046+
1047+ unless pushed
1048+ @application = @path
1049+ do_push(appname)
1050+ end
1051 end
1052
1053 def environment(appname)
1054@@ -483,7 +317,7 @@
1055 etable = table do |t|
1056 t.headings = 'Variable', 'Value'
1057 env.each do |e|
1058- k,v = e.split('=')
1059+ k,v = e.split('=', 2)
1060 t << [k, v]
1061 end
1062 end
1063@@ -494,7 +328,7 @@
1064 def environment_add(appname, k, v=nil)
1065 app = client.app_info(appname)
1066 env = app[:env] || []
1067- k,v = k.split('=') unless v
1068+ k,v = k.split('=', 2) unless v
1069 env << "#{k}=#{v}"
1070 display "Adding Environment Variable [#{k}=#{v}]: ", false
1071 app[:env] = env
1072@@ -537,11 +371,35 @@
1073
1074 def check_deploy_directory(path)
1075 err 'Deployment path does not exist' unless File.exists? path
1076- err 'Deployment path is not a directory' unless File.directory? path
1077 return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
1078 err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
1079 end
1080
1081+ def check_unreachable_links(path)
1082+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
1083+
1084+ pwd = Pathname.pwd
1085+
1086+ abspath = File.expand_path(path)
1087+ unreachable = []
1088+ files.each do |f|
1089+ file = Pathname.new(f)
1090+ if file.symlink? && !file.realpath.to_s.start_with?(abspath)
1091+ unreachable << file.relative_path_from(pwd)
1092+ end
1093+ end
1094+
1095+ unless unreachable.empty?
1096+ root = Pathname.new(path).relative_path_from(pwd)
1097+ err "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
1098+ end
1099+ end
1100+
1101+ def find_sockets(path)
1102+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
1103+ files && files.select { |f| File.socket? f }
1104+ end
1105+
1106 def upload_app_bits(appname, path)
1107 display 'Uploading Application:'
1108
1109@@ -551,147 +409,112 @@
1110 explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
1111 FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
1112
1113- Dir.chdir(path) do
1114- # Stage the app appropriately and do the appropriate fingerprinting, etc.
1115- if war_file = Dir.glob('*.war').first
1116- VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
1117- else
1118- FileUtils.mkdir(explode_dir)
1119- files = Dir.glob('{*,.[^\.]*}')
1120- FileUtils.cp_r(files, explode_dir)
1121- end
1122-
1123- # Send the resource list to the cloudcontroller, the response will tell us what it already has..
1124- unless @options[:noresources]
1125- display ' Checking for available resources: ', false
1126- fingerprints = []
1127- total_size = 0
1128- resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
1129- resource_files.each do |filename|
1130- next if (File.directory?(filename) || !File.exists?(filename))
1131- fingerprints << {
1132- :size => File.size(filename),
1133- :sha1 => Digest::SHA1.file(filename).hexdigest,
1134- :fn => filename
1135- }
1136- total_size += File.size(filename)
1137- end
1138-
1139- # Check to see if the resource check is worth the round trip
1140- if (total_size > (64*1024)) # 64k for now
1141- # Send resource fingerprints to the cloud controller
1142- appcloud_resources = client.check_resources(fingerprints)
1143- end
1144- display 'OK'.green
1145-
1146- if appcloud_resources
1147- display ' Processing resources: ', false
1148- # We can then delete what we do not need to send.
1149- appcloud_resources.each do |resource|
1150- FileUtils.rm_f resource[:fn]
1151- # adjust filenames sans the explode_dir prefix
1152- resource[:fn].sub!("#{explode_dir}/", '')
1153+ if path =~ /\.(war|zip)$/
1154+ #single file that needs unpacking
1155+ VMC::Cli::ZipUtil.unpack(path, explode_dir)
1156+ elsif !File.directory? path
1157+ #single file that doesn't need unpacking
1158+ FileUtils.mkdir(explode_dir)
1159+ FileUtils.cp(path,explode_dir)
1160+ else
1161+ Dir.chdir(path) do
1162+ # Stage the app appropriately and do the appropriate fingerprinting, etc.
1163+ if war_file = Dir.glob('*.war').first
1164+ VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
1165+ elsif zip_file = Dir.glob('*.zip').first
1166+ VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
1167+ else
1168+ check_unreachable_links(path)
1169+ FileUtils.mkdir(explode_dir)
1170+
1171+ files = Dir.glob('{*,.[^\.]*}')
1172+
1173+ # Do not process .git files
1174+ files.delete('.git') if files
1175+
1176+ FileUtils.cp_r(files, explode_dir)
1177+
1178+ find_sockets(explode_dir).each do |s|
1179+ File.delete s
1180 end
1181- display 'OK'.green
1182- end
1183-
1184- end
1185-
1186- # Perform Packing of the upload bits here.
1187- unless VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
1188- display ' Packing application: ', false
1189- VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
1190- display 'OK'.green
1191-
1192- upload_size = File.size(upload_file);
1193- if upload_size > 1024*1024
1194- upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
1195- elsif upload_size > 0
1196- upload_size = (upload_size/1024.0).round.to_s + 'K'
1197- end
1198- else
1199- upload_size = '0K'
1200- end
1201-
1202- upload_str = " Uploading (#{upload_size}): "
1203- display upload_str, false
1204-
1205- unless VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
1206- FileWithPercentOutput.display_str = upload_str
1207- FileWithPercentOutput.upload_size = File.size(upload_file);
1208- file = FileWithPercentOutput.open(upload_file, 'rb')
1209- end
1210-
1211- client.upload_app(appname, file, appcloud_resources)
1212- display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
1213-
1214- display 'Push Status: ', false
1215+ end
1216+ end
1217+ end
1218+
1219+ # Send the resource list to the cloudcontroller, the response will tell us what it already has..
1220+ unless @options[:noresources]
1221+ display ' Checking for available resources: ', false
1222+ fingerprints = []
1223+ total_size = 0
1224+ resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
1225+ resource_files.each do |filename|
1226+ next if (File.directory?(filename) || !File.exists?(filename))
1227+ fingerprints << {
1228+ :size => File.size(filename),
1229+ :sha1 => Digest::SHA1.file(filename).hexdigest,
1230+ :fn => filename
1231+ }
1232+ total_size += File.size(filename)
1233+ end
1234+
1235+ # Check to see if the resource check is worth the round trip
1236+ if (total_size > (64*1024)) # 64k for now
1237+ # Send resource fingerprints to the cloud controller
1238+ appcloud_resources = client.check_resources(fingerprints)
1239+ end
1240 display 'OK'.green
1241- end
1242-
1243- ensure
1244- # Cleanup if we created an exploded directory.
1245- FileUtils.rm_f(upload_file) if upload_file
1246- FileUtils.rm_rf(explode_dir) if explode_dir
1247- end
1248-
1249- def choose_existing_service(appname, user_services)
1250- return unless prompt_ok
1251- selected = false
1252- choose do |menu|
1253- menu.header = "The following provisioned services are available:"
1254- menu.prompt = 'Please select one you wish to provision: '
1255- menu.select_by = :index_or_name
1256- user_services.each do |s|
1257- menu.choice(s[:name]) do
1258- display "Binding Service: ", false
1259- client.bind_service(s[:name], appname)
1260- display 'OK'.green
1261- selected = true
1262- end
1263- end
1264- end
1265- selected
1266- end
1267-
1268- def choose_new_service(appname, services)
1269- return unless prompt_ok
1270- choose do |menu|
1271- menu.header = "The following system services are available:"
1272- menu.prompt = 'Please select one you wish to provision: '
1273- menu.select_by = :index_or_name
1274- service_choices = []
1275- services.each do |service_type, value|
1276- value.each do |vendor, version|
1277- service_choices << vendor
1278- end
1279- end
1280- service_choices.sort! {|a, b| a.to_s <=> b.to_s }
1281- service_choices.each do |vendor|
1282- menu.choice(vendor) do
1283- default_name = random_service_name(vendor)
1284- service_name = ask("Specify the name of the service [#{default_name}]: ")
1285- service_name = default_name if service_name.empty?
1286- create_service_banner(vendor, service_name)
1287- bind_service_banner(service_name, appname)
1288- end
1289- end
1290- end
1291- end
1292-
1293- def bind_services(appname, services)
1294- user_services = client.services
1295- selected_existing = false
1296- unless no_prompt || user_services.empty?
1297- use_existing = ask "Would you like to use an existing provisioned service [yN]? "
1298- if use_existing.upcase == 'Y'
1299- selected_existing = choose_existing_service(appname, user_services)
1300- end
1301- end
1302- # Create a new service and bind it here
1303- unless selected_existing
1304- choose_new_service(appname, services)
1305- end
1306+
1307+ if appcloud_resources
1308+ display ' Processing resources: ', false
1309+ # We can then delete what we do not need to send.
1310+ appcloud_resources.each do |resource|
1311+ FileUtils.rm_f resource[:fn]
1312+ # adjust filenames sans the explode_dir prefix
1313+ resource[:fn].sub!("#{explode_dir}/", '')
1314+ end
1315+ display 'OK'.green
1316+ end
1317+
1318+ end
1319+
1320+ # If no resource needs to be sent, add an empty file to ensure we have
1321+ # a multi-part request that is expected by nginx fronting the CC.
1322+ if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
1323+ Dir.chdir(explode_dir) do
1324+ File.new(".__empty__", "w")
1325+ end
1326+ end
1327+ # Perform Packing of the upload bits here.
1328+ display ' Packing application: ', false
1329+ VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
1330+ display 'OK'.green
1331+
1332+ upload_size = File.size(upload_file);
1333+ if upload_size > 1024*1024
1334+ upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
1335+ elsif upload_size > 0
1336+ upload_size = (upload_size/1024.0).round.to_s + 'K'
1337+ else
1338+ upload_size = '0K'
1339+ end
1340+
1341+ upload_str = " Uploading (#{upload_size}): "
1342+ display upload_str, false
1343+
1344+ FileWithPercentOutput.display_str = upload_str
1345+ FileWithPercentOutput.upload_size = File.size(upload_file);
1346+ file = FileWithPercentOutput.open(upload_file, 'rb')
1347+
1348+ client.upload_app(appname, file, appcloud_resources)
1349+ display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
1350+
1351+ display 'Push Status: ', false
1352+ display 'OK'.green
1353+
1354+ ensure
1355+ # Cleanup if we created an exploded directory.
1356+ FileUtils.rm_f(upload_file) if upload_file
1357+ FileUtils.rm_rf(explode_dir) if explode_dir
1358 end
1359
1360 def check_app_limit
1361@@ -768,9 +591,19 @@
1362 return display "No running instances for [#{appname}]".yellow if instances_info.empty?
1363
1364 instances_table = table do |t|
1365- t.headings = 'Index', 'State', 'Start Time'
1366+ show_debug = instances_info.any? { |e| e[:debug_port] }
1367+
1368+ headings = ['Index', 'State', 'Start Time']
1369+ headings << 'Debug IP' if show_debug
1370+ headings << 'Debug Port' if show_debug
1371+
1372+ t.headings = headings
1373+
1374 instances_info.each do |entry|
1375- t << [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]
1376+ row = [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]
1377+ row << entry[:debug_ip] if show_debug
1378+ row << entry[:debug_port] if show_debug
1379+ t << row
1380 end
1381 end
1382 display "\n"
1383@@ -822,18 +655,24 @@
1384 case health(app)
1385 when 'N/A'
1386 # Health manager not running.
1387- err "\Application '#{appname}'s state is undetermined, not enough information available." if error_on_health
1388+ err "\nApplication '#{appname}'s state is undetermined, not enough information available." if error_on_health
1389 return false
1390 when 'RUNNING'
1391 return true
1392 else
1393- return false
1394+ if app[:meta][:debug] == "suspend"
1395+ display "\nApplication [#{appname}] has started in a mode that is waiting for you to trigger startup."
1396+ return true
1397+ else
1398+ return false
1399+ end
1400 end
1401 end
1402
1403 def display_logfile(path, content, instance='0', banner=nil)
1404 banner ||= "====> #{path} <====\n\n"
1405- if content && !content.empty?
1406+
1407+ unless content.empty?
1408 display banner
1409 prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
1410 unless prefix
1411@@ -846,10 +685,6 @@
1412 end
1413 end
1414
1415- def log_file_paths
1416- %w[logs/stderr.log logs/stdout.log logs/startup.log]
1417- end
1418-
1419 def grab_all_logs(appname)
1420 instances_info_envelope = client.app_instances(appname)
1421 return if instances_info_envelope.is_a?(Array)
1422@@ -860,13 +695,21 @@
1423 end
1424
1425 def grab_logs(appname, instance)
1426- log_file_paths.each do |path|
1427+ files_under(appname, instance, "/logs").each do |path|
1428 begin
1429 content = client.app_files(appname, path, instance)
1430- rescue
1431+ display_logfile(path, content, instance)
1432+ rescue VMC::Client::NotFound, VMC::Client::TargetError
1433 end
1434- display_logfile(path, content, instance)
1435- end
1436+ end
1437+ end
1438+
1439+ def files_under(appname, instance, path)
1440+ client.app_files(appname, path, instance).split("\n").collect do |l|
1441+ "#{path}/#{l.split[0]}"
1442+ end
1443+ rescue VMC::Client::NotFound, VMC::Client::TargetError
1444+ []
1445 end
1446
1447 def grab_crash_logs(appname, instance, was_staged=false)
1448@@ -877,11 +720,10 @@
1449 map = VMC::Cli::Config.instances
1450 instance = map[instance] if map[instance]
1451
1452- ['/logs/err.log', '/logs/staging.log', 'logs/stderr.log', 'logs/stdout.log', 'logs/startup.log'].each do |path|
1453- begin
1454- content = client.app_files(appname, path, instance)
1455- rescue
1456- end
1457+ (files_under(appname, instance, "/logs") +
1458+ files_under(appname, instance, "/app/logs") +
1459+ files_under(appname, instance, "/app/log")).each do |path|
1460+ content = client.app_files(appname, path, instance)
1461 display_logfile(path, content, instance)
1462 end
1463 end
1464@@ -899,8 +741,355 @@
1465 display tail.join("\n") if new_lines > 0
1466 end
1467 since + new_lines
1468- end
1469- rescue
1470+ rescue VMC::Client::NotFound, VMC::Client::TargetError
1471+ 0
1472+ end
1473+
1474+ def provisioned_services_apps_hash
1475+ apps = client.apps
1476+ services_apps_hash = {}
1477+ apps.each {|app|
1478+ app[:services].each { |svc|
1479+ svc_apps = services_apps_hash[svc]
1480+ unless svc_apps
1481+ svc_apps = Set.new
1482+ services_apps_hash[svc] = svc_apps
1483+ end
1484+ svc_apps.add(app[:name])
1485+ } unless app[:services] == nil
1486+ }
1487+ services_apps_hash
1488+ end
1489+
1490+ def delete_app(appname, force)
1491+ app = client.app_info(appname)
1492+ services_to_delete = []
1493+ app_services = app[:services]
1494+ services_apps_hash = provisioned_services_apps_hash
1495+ app_services.each { |service|
1496+ del_service = force && no_prompt
1497+ unless no_prompt || force
1498+ del_service = ask(
1499+ "Provisioned service [#{service}] detected, would you like to delete it?",
1500+ :default => false
1501+ )
1502+
1503+ if del_service
1504+ apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
1505+ if apps_using_service.size > 0
1506+ del_service = ask(
1507+ "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?",
1508+ :default => false
1509+ )
1510+ end
1511+ end
1512+ end
1513+ services_to_delete << service if del_service
1514+ }
1515+
1516+ display "Deleting application [#{appname}]: ", false
1517+ client.delete_app(appname)
1518+ display 'OK'.green
1519+
1520+ services_to_delete.each do |s|
1521+ delete_service_banner(s)
1522+ end
1523+ end
1524+
1525+ def do_start(appname, push=false)
1526+ app = client.app_info(appname)
1527+ return display "Application '#{appname}' could not be found".red if app.nil?
1528+ return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
1529+
1530+
1531+
1532+ if @options[:debug]
1533+ runtimes = client.runtimes_info
1534+ return display "Cannot get runtime information." unless runtimes
1535+
1536+ runtime = runtimes[app[:staging][:stack].to_sym]
1537+ return display "Unknown runtime." unless runtime
1538+
1539+ unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
1540+ modes = runtime[:debug_modes] || []
1541+
1542+ display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
1543+
1544+ if push
1545+ display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
1546+ else
1547+ display "Available modes: #{modes.inspect}"
1548+ end
1549+
1550+ return
1551+ end
1552+ end
1553+
1554+ banner = "Staging Application '#{appname}': "
1555+ display banner, false
1556+
1557+ t = Thread.new do
1558+ count = 0
1559+ while count < TAIL_TICKS do
1560+ display '.', false
1561+ sleep SLEEP_TIME
1562+ count += 1
1563+ end
1564+ end
1565+
1566+ app[:state] = 'STARTED'
1567+ app[:debug] = @options[:debug]
1568+ app[:console] = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model]).console
1569+ client.update_app(appname, app)
1570+
1571+ Thread.kill(t)
1572+ clear(LINE_LENGTH)
1573+ display "#{banner}#{'OK'.green}"
1574+
1575+ banner = "Starting Application '#{appname}': "
1576+ display banner, false
1577+
1578+ count = log_lines_displayed = 0
1579+ failed = false
1580+ start_time = Time.now.to_i
1581+
1582+ loop do
1583+ display '.', false unless count > TICKER_TICKS
1584+ sleep SLEEP_TIME
1585+
1586+ break if app_started_properly(appname, count > HEALTH_TICKS)
1587+
1588+ if !crashes(appname, false, start_time).empty?
1589+ # Check for the existance of crashes
1590+ display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
1591+ grab_crash_logs(appname, '0', true)
1592+ if push and !no_prompt
1593+ display "\n"
1594+ delete_app(appname, false) if ask "Delete the application?", :default => true
1595+ end
1596+ failed = true
1597+ break
1598+ elsif count > TAIL_TICKS
1599+ log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
1600+ end
1601+
1602+ count += 1
1603+ if count > GIVEUP_TICKS # 2 minutes
1604+ display "\nApplication is taking too long to start, check your logs".yellow
1605+ break
1606+ end
1607+ end
1608+ exit(false) if failed
1609+ clear(LINE_LENGTH)
1610+ display "#{banner}#{'OK'.green}"
1611+ end
1612+
1613+ def do_stop(appname)
1614+ app = client.app_info(appname)
1615+ return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
1616+ display "Stopping Application '#{appname}': ", false
1617+ app[:state] = 'STOPPED'
1618+ client.update_app(appname, app)
1619+ display 'OK'.green
1620+ end
1621+
1622+ def do_push(appname=nil)
1623+ unless @app_info || no_prompt
1624+ @manifest = { "applications" => { @path => { "name" => appname } } }
1625+
1626+ interact
1627+
1628+ if ask("Would you like to save this configuration?", :default => false)
1629+ save_manifest
1630+ end
1631+
1632+ resolve_manifest(@manifest)
1633+
1634+ @app_info = @manifest["applications"][@path]
1635+ end
1636+
1637+ instances = info(:instances, 1)
1638+ exec = info(:exec, 'thin start')
1639+
1640+ ignore_framework = @options[:noframework]
1641+ no_start = @options[:nostart]
1642+
1643+ appname ||= info(:name)
1644+ url = info(:url) || info(:urls)
1645+ mem, memswitch = nil, info(:mem)
1646+ memswitch = normalize_mem(memswitch) if memswitch
1647+ command = info(:command)
1648+ runtime = info(:runtime)
1649+
1650+ # Check app existing upfront if we have appname
1651+ app_checked = false
1652+ if appname
1653+ err "Application '#{appname}' already exists, use update" if app_exists?(appname)
1654+ app_checked = true
1655+ else
1656+ raise VMC::Client::AuthError unless client.logged_in?
1657+ end
1658+
1659+ # check if we have hit our app limit
1660+ check_app_limit
1661+ # check memsize here for capacity
1662+ if memswitch && !no_start
1663+ check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
1664+ end
1665+
1666+ appname ||= ask("Application Name") unless no_prompt
1667+ err "Application Name required." if appname.nil? || appname.empty?
1668+
1669+ check_deploy_directory(@application)
1670+
1671+ if !app_checked and app_exists?(appname)
1672+ err "Application '#{appname}' already exists, use update or delete."
1673+ end
1674+
1675+ if ignore_framework
1676+ framework = VMC::Cli::Framework.new
1677+ elsif f = info(:framework)
1678+ info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
1679+
1680+ framework = VMC::Cli::Framework.create(f["name"], info)
1681+ exec = framework.exec if framework && framework.exec
1682+ else
1683+ framework = detect_framework(prompt_ok)
1684+ end
1685+
1686+ err "Application Type undetermined for path '#{@application}'" unless framework
1687+
1688+ if not runtime
1689+ default_runtime = framework.default_runtime @application
1690+ runtime = detect_runtime(default_runtime, !no_prompt) if framework.prompt_for_runtime?
1691+ end
1692+ command = ask("Start Command") if !command && framework.require_start_command?
1693+
1694+ default_url = "None"
1695+ default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if framework.require_url?
1696+
1697+
1698+ unless no_prompt || url || !framework.require_url?
1699+ url = ask(
1700+ "Application Deployed URL",
1701+ :default => default_url
1702+ )
1703+
1704+ # common error case is for prompted users to answer y or Y or yes or
1705+ # YES to this ask() resulting in an unintended URL of y. Special case
1706+ # this common error
1707+ url = nil if YES_SET.member? url
1708+ end
1709+ url = nil if url == "None"
1710+ default_url = nil if default_url == "None"
1711+ url ||= default_url
1712+
1713+ if memswitch
1714+ mem = memswitch
1715+ elsif prompt_ok
1716+ mem = ask("Memory Reservation",
1717+ :default => framework.memory(runtime),
1718+ :choices => mem_choices)
1719+ else
1720+ mem = framework.memory runtime
1721+ end
1722+
1723+ # Set to MB number
1724+ mem_quota = mem_choice_to_quota(mem)
1725+
1726+ # check memsize here for capacity
1727+ check_has_capacity_for(mem_quota * instances) unless no_start
1728+
1729+ display 'Creating Application: ', false
1730+
1731+ manifest = {
1732+ :name => "#{appname}",
1733+ :staging => {
1734+ :framework => framework.name,
1735+ :runtime => runtime
1736+ },
1737+ :uris => Array(url),
1738+ :instances => instances,
1739+ :resources => {
1740+ :memory => mem_quota
1741+ }
1742+ }
1743+ manifest[:staging][:command] = command if command
1744+
1745+ # Send the manifest to the cloud controller
1746+ client.create_app(appname, manifest)
1747+ display 'OK'.green
1748+
1749+
1750+ existing = Set.new(client.services.collect { |s| s[:name] })
1751+
1752+ if @app_info && services = @app_info["services"]
1753+ services.each do |name, info|
1754+ unless existing.include? name
1755+ create_service_banner(info["type"], name, true)
1756+ end
1757+
1758+ bind_service_banner(name, appname)
1759+ end
1760+ end
1761+
1762+ # Stage and upload the app bits.
1763+ upload_app_bits(appname, @application)
1764+
1765+ start(appname, true) unless no_start
1766+ end
1767+
1768+ def do_stats(appname)
1769+ stats = client.app_stats(appname)
1770+ return display JSON.pretty_generate(stats) if @options[:json]
1771+
1772+ stats_table = table do |t|
1773+ t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
1774+ stats.each do |entry|
1775+ index = entry[:instance]
1776+ stat = entry[:stats]
1777+ hp = "#{stat[:host]}:#{stat[:port]}"
1778+ uptime = uptime_string(stat[:uptime])
1779+ usage = stat[:usage]
1780+ if usage
1781+ cpu = usage[:cpu]
1782+ mem = (usage[:mem] * 1024) # mem comes in K's
1783+ disk = usage[:disk]
1784+ end
1785+ mem_quota = stat[:mem_quota]
1786+ disk_quota = stat[:disk_quota]
1787+ mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
1788+ disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
1789+ cpu = cpu ? cpu.to_s : 'NA'
1790+ cpu = "#{cpu}% (#{stat[:cores]})"
1791+ t << [index, cpu, mem, disk, uptime]
1792+ end
1793+ end
1794+
1795+ if stats.empty?
1796+ display "No running instances for [#{appname}]".yellow
1797+ else
1798+ display stats_table
1799+ end
1800+ end
1801+
1802+ def all_files(appname, path)
1803+ instances_info_envelope = client.app_instances(appname)
1804+ return if instances_info_envelope.is_a?(Array)
1805+ instances_info = instances_info_envelope[:instances] || []
1806+ instances_info.each do |entry|
1807+ begin
1808+ content = client.app_files(appname, path, entry[:index])
1809+ display_logfile(
1810+ path,
1811+ content,
1812+ entry[:index],
1813+ "====> [#{entry[:index]}: #{path}] <====\n".bold
1814+ )
1815+ rescue VMC::Client::NotFound, VMC::Client::TargetError
1816+ end
1817+ end
1818+ end
1819 end
1820
1821 class FileWithPercentOutput < ::File
1822
1823=== modified file 'lib/cli/commands/base.rb'
1824--- lib/cli/commands/base.rb 2011-06-17 13:38:56 +0000
1825+++ lib/cli/commands/base.rb 2012-06-13 01:37:18 +0000
1826@@ -1,33 +1,189 @@
1827-
1828 require 'rubygems'
1829+require 'interact'
1830 require 'terminal-table/import'
1831-require 'highline/import'
1832
1833 module VMC::Cli
1834
1835 module Command
1836
1837 class Base
1838+ include Interactive
1839+
1840 attr_reader :no_prompt, :prompt_ok
1841
1842+ MANIFEST = "manifest.yml"
1843+
1844 def initialize(options={})
1845 @options = options.dup
1846 @no_prompt = @options[:noprompts]
1847 @prompt_ok = !no_prompt
1848
1849- # Fix for system ruby and Highline (stdin) on MacOSX
1850- if RUBY_PLATFORM =~ /darwin/ && RUBY_VERSION == '1.8.7' && RUBY_PATCHLEVEL <= 174
1851- HighLine.track_eof = false
1852- end
1853-
1854 # Suppress colorize on Windows systems for now.
1855- if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
1856+ if WINDOWS
1857 VMC::Cli::Config.colorize = false
1858 end
1859
1860- end
1861-
1862- def client
1863+ @path = @options[:path] || '.'
1864+
1865+ load_manifest manifest_file if manifest_file
1866+ end
1867+
1868+ def manifest_file
1869+ return @options[:manifest] if @options[:manifest]
1870+ return @manifest_file if @manifest_file
1871+
1872+ where = File.expand_path(@path)
1873+ while true
1874+ if File.exists?(File.join(where, MANIFEST))
1875+ @manifest_file = File.join(where, MANIFEST)
1876+ break
1877+ elsif File.basename(where) == "/"
1878+ @manifest_file = nil
1879+ break
1880+ else
1881+ where = File.expand_path("../", where)
1882+ end
1883+ end
1884+
1885+ @manifest_file
1886+ end
1887+
1888+ def load_manifest_structure(file)
1889+ manifest = YAML.load_file file
1890+
1891+ Array(manifest["inherit"]).each do |p|
1892+ manifest = merge_parent(manifest, p)
1893+ end
1894+
1895+ if apps = manifest["applications"]
1896+ apps.each do |k, v|
1897+ abs = File.expand_path(k, file)
1898+ if Dir.pwd.start_with? abs
1899+ manifest = merge_manifest(manifest, v)
1900+ end
1901+ end
1902+ end
1903+
1904+ manifest
1905+ end
1906+
1907+ def resolve_manifest(manifest)
1908+ if apps = manifest["applications"]
1909+ apps.each_value do |v|
1910+ resolve_lexically(v, [manifest])
1911+ end
1912+ end
1913+
1914+ resolve_lexically(manifest, [manifest])
1915+ end
1916+
1917+ def load_manifest(file)
1918+ @manifest = load_manifest_structure(file)
1919+ resolve_manifest(@manifest)
1920+ end
1921+
1922+ def merge_parent(child, path)
1923+ file = File.expand_path("../" + path, manifest_file)
1924+ merge_manifest(child, load_manifest_structure(file))
1925+ end
1926+
1927+ def merge_manifest(child, parent)
1928+ merge = proc do |_, old, new|
1929+ if new.is_a?(Hash) and old.is_a?(Hash)
1930+ old.merge(new, &merge)
1931+ else
1932+ new
1933+ end
1934+ end
1935+
1936+ parent.merge(child, &merge)
1937+ end
1938+
1939+ def resolve_lexically(val, ctx = [@manifest])
1940+ case val
1941+ when Hash
1942+ val.each_value do |v|
1943+ resolve_lexically(v, [val] + ctx)
1944+ end
1945+ when Array
1946+ val.each do |v|
1947+ resolve_lexically(v, ctx)
1948+ end
1949+ when String
1950+ val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
1951+ resolve_symbol($1, ctx)
1952+ end
1953+ end
1954+
1955+ nil
1956+ end
1957+
1958+ def resolve_symbol(sym, ctx)
1959+ case sym
1960+ when "target-base"
1961+ target_base(ctx)
1962+
1963+ when "target-url"
1964+ target_url(ctx)
1965+
1966+ when "random-word"
1967+ "%04x" % [rand(0x0100000)]
1968+
1969+ else
1970+ found = find_symbol(sym, ctx)
1971+
1972+ if found
1973+ resolve_lexically(found, ctx)
1974+ found
1975+ else
1976+ err(sym, "Unknown symbol in manifest: ")
1977+ end
1978+ end
1979+ end
1980+
1981+ def find_symbol(sym, ctx)
1982+ ctx.each do |h|
1983+ if val = resolve_in(h, sym)
1984+ return val
1985+ end
1986+ end
1987+
1988+ nil
1989+ end
1990+
1991+ def resolve_in(hash, *where)
1992+ find_in_hash(hash, ["properties"] + where) ||
1993+ find_in_hash(hash, ["applications", @application] + where) ||
1994+ find_in_hash(hash, where)
1995+ end
1996+
1997+ def manifest(*where)
1998+ resolve_in(@manifest, *where)
1999+ end
2000+
2001+ def find_in_hash(hash, where)
2002+ what = hash
2003+ where.each do |x|
2004+ return nil unless what.is_a?(Hash)
2005+ what = what[x]
2006+ end
2007+
2008+ what
2009+ end
2010+
2011+ def target_url(ctx = [])
2012+ find_symbol("target", ctx) ||
2013+ (@client && @client.target) ||
2014+ VMC::Cli::Config.target_url
2015+ end
2016+
2017+ def target_base(ctx = [])
2018+ VMC::Cli::Config.base_of(find_symbol("target", ctx) || target_url)
2019+ end
2020+
2021+ # Inject a client to help in testing.
2022+ def client(cli=nil)
2023+ @client ||= cli
2024 return @client if @client
2025 @client = VMC::Client.new(target_url, auth_token)
2026 @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace
2027@@ -36,18 +192,11 @@
2028 end
2029
2030 def client_info
2031- return @client_info if @client_info
2032- @client_info = client.info
2033- end
2034-
2035- def target_url
2036- return @target_url if @target_url
2037- @target_url = VMC::Cli::Config.target_url
2038+ @client_info ||= client.info
2039 end
2040
2041 def auth_token
2042- return @auth_token if @auth_token
2043- @auth_token = VMC::Cli::Config.auth_token
2044+ @auth_token = VMC::Cli::Config.auth_token(@options[:token_file])
2045 end
2046
2047 def runtimes_info
2048@@ -72,7 +221,6 @@
2049 end
2050 @frameworks
2051 end
2052-
2053 end
2054 end
2055 end
2056
2057=== added file 'lib/cli/commands/manifest.rb'
2058--- lib/cli/commands/manifest.rb 1970-01-01 00:00:00 +0000
2059+++ lib/cli/commands/manifest.rb 2012-06-13 01:37:18 +0000
2060@@ -0,0 +1,56 @@
2061+module VMC::Cli::Command
2062+ class Manifest < Base
2063+ include VMC::Cli::ManifestHelper
2064+
2065+ def initialize(options)
2066+ super
2067+
2068+ # don't resolve any of the manifest template stuff
2069+ if manifest_file
2070+ @manifest = load_manifest_structure manifest_file
2071+ else
2072+ @manifest = {}
2073+ end
2074+ end
2075+
2076+ def edit
2077+ build_manifest
2078+ save_manifest
2079+ end
2080+
2081+ def extend(which)
2082+ parent = load_manifest_structure which
2083+ @manifest = load_manifest_structure which
2084+
2085+ build_manifest
2086+
2087+ simplify(@manifest, parent)
2088+
2089+ @manifest["inherit"] ||= []
2090+ @manifest["inherit"] << which
2091+
2092+ save_manifest(ask("Save where?"))
2093+ end
2094+
2095+ private
2096+
2097+ def simplify(child, parent)
2098+ return unless child.is_a?(Hash) and parent.is_a?(Hash)
2099+
2100+ child.reject! do |k, v|
2101+ if v == parent[k]
2102+ puts "rejecting #{k}"
2103+ true
2104+ else
2105+ simplify(v, parent[k])
2106+ false
2107+ end
2108+ end
2109+ end
2110+
2111+ def build_manifest
2112+ @application = ask("Configure for which application?", :default => ".")
2113+ interact true
2114+ end
2115+ end
2116+end
2117
2118=== added file 'lib/cli/commands/micro.rb'
2119--- lib/cli/commands/micro.rb 1970-01-01 00:00:00 +0000
2120+++ lib/cli/commands/micro.rb 2012-06-13 01:37:18 +0000
2121@@ -0,0 +1,115 @@
2122+module VMC::Cli::Command
2123+ class Micro < Base
2124+
2125+ def initialize(args)
2126+ super(args)
2127+ end
2128+
2129+ def offline(mode)
2130+ command('offline')
2131+ end
2132+
2133+ def online(mode)
2134+ command('online')
2135+ end
2136+
2137+ def status(mode)
2138+ command('status')
2139+ end
2140+
2141+ def command(cmd)
2142+ config = build_config
2143+ switcher(config).send(cmd)
2144+ store_config(config)
2145+ end
2146+
2147+ def switcher(config)
2148+ case Micro.platform
2149+ when :darwin
2150+ switcher = VMC::Micro::Switcher::Darwin.new(config)
2151+ when :linux
2152+ switcher = VMC::Micro::Switcher::Linux.new(config)
2153+ when :windows
2154+ switcher = VMC::Micro::Switcher::Windows.new(config)
2155+ when :dummy # for testing only
2156+ switcher = VMC::Micro::Switcher::Dummy.new(config)
2157+ else
2158+ err "unsupported platform: #{Micro.platform}"
2159+ end
2160+ end
2161+
2162+ # Returns the configuration needed to run the micro related subcommands.
2163+ # First loads saved config from file (if there is any), then overrides
2164+ # loaded values with command line arguments, and finally tries to guess
2165+ # in case neither was used:
2166+ # vmx location of micro.vmx file
2167+ # vmrun location of vmrun command
2168+ # password password for vcap user (in the guest vm)
2169+ # platform current platform
2170+ def build_config
2171+ conf = VMC::Cli::Config.micro # returns {} if there isn't a saved config
2172+
2173+ override(conf, 'vmx', true) do
2174+ locate_vmx(Micro.platform)
2175+ end
2176+
2177+ override(conf, 'vmrun', true) do
2178+ VMC::Micro::VMrun.locate(Micro.platform)
2179+ end
2180+
2181+ override(conf, 'password') do
2182+ @password = ask("Please enter your Micro Cloud Foundry VM password (vcap user) password", :echo => "*")
2183+ end
2184+
2185+ conf['platform'] = Micro.platform
2186+
2187+ conf
2188+ end
2189+
2190+ # Save the cleartext password if --save is supplied.
2191+ # Note: it is due to vix we have to use a cleartext password :(
2192+ # Only if --password is used and not --save is the password deleted from the
2193+ # config file before it is stored to disk.
2194+ def store_config(config)
2195+ if @options[:save]
2196+ warn("cleartext password saved in: #{VMC::Cli::Config::MICRO_FILE}")
2197+ elsif @options[:password] || @password
2198+ config.delete('password')
2199+ end
2200+
2201+ VMC::Cli::Config.store_micro(config)
2202+ end
2203+
2204+ # override with command line arguments and yield the block in case the option isn't set
2205+ def override(config, option, escape=false, &blk)
2206+ # override if given on the command line
2207+ if opt = @options[option.to_sym]
2208+ opt = VMC::Micro.escape_path(opt) if escape
2209+ config[option] = opt
2210+ end
2211+ config[option] = yield unless config[option]
2212+ end
2213+
2214+ def locate_vmx(platform)
2215+ paths = YAML.load_file(VMC::Micro.config_file('paths.yml'))
2216+ vmx_paths = paths[platform.to_s]['vmx']
2217+ vmx = VMC::Micro.locate_file('micro.vmx', 'micro', vmx_paths)
2218+ err "Unable to locate micro.vmx, please supply --vmx option" unless vmx
2219+ vmx
2220+ end
2221+
2222+ def self.platform
2223+ case RUBY_PLATFORM
2224+ when /darwin/ # x86_64-darwin11.2.0
2225+ :darwin
2226+ when /linux/ # x86_64-linux
2227+ :linux
2228+ when /mingw|mswin32|cygwin/ # i386-mingw32
2229+ :windows
2230+ else
2231+ RUBY_PLATFORM
2232+ end
2233+ end
2234+
2235+ end
2236+end
2237
2238=== modified file 'lib/cli/commands/misc.rb'
2239--- lib/cli/commands/misc.rb 2011-06-17 13:38:56 +0000
2240+++ lib/cli/commands/misc.rb 2012-06-13 01:37:18 +0000
2241@@ -30,14 +30,15 @@
2242 client = VMC::Client.new(target_url)
2243 unless client.target_valid?
2244 if prompt_ok
2245- display "Host is not valid: '#{target_url}'".red
2246- show_response = ask "Would you like see the response [yN]? "
2247- display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response.upcase == 'Y'
2248+ display "Host is not available or is not valid: '#{target_url}'".red
2249+ show_response = ask "Would you like see the response?",
2250+ :default => false
2251+ display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response
2252 end
2253 exit(false)
2254 else
2255 VMC::Cli::Config.store_target(target_url)
2256- say "Succesfully targeted to [#{target_url}]".green
2257+ say "Successfully targeted to [#{target_url}]".green
2258 end
2259 end
2260
2261
2262=== modified file 'lib/cli/commands/services.rb'
2263--- lib/cli/commands/services.rb 2011-06-17 13:38:56 +0000
2264+++ lib/cli/commands/services.rb 2012-06-13 01:37:18 +0000
2265@@ -1,11 +1,16 @@
2266+require "uuidtools"
2267+
2268 module VMC::Cli::Command
2269
2270 class Services < Base
2271 include VMC::Cli::ServicesHelper
2272+ include VMC::Cli::TunnelHelper
2273
2274 def services
2275 ss = client.services_info
2276 ps = client.services
2277+ ps.sort! {|a, b| a[:name] <=> b[:name] }
2278+
2279 if @options[:json]
2280 services = { :system => ss, :provisioned => ps }
2281 return display JSON.pretty_generate(services)
2282@@ -18,15 +23,15 @@
2283 unless no_prompt || service
2284 services = client.services_info
2285 err 'No services available to provision' if services.empty?
2286- choose do |menu|
2287- menu.prompt = 'Please select one you wish to provision: '
2288- menu.select_by = :index_or_name
2289- services.each do |service_type, value|
2290- value.each do |vendor, version|
2291- menu.choice(vendor.to_s) { service = vendor.to_s }
2292- end
2293- end
2294- end
2295+ service = ask(
2296+ "Which service would you like to provision?",
2297+ { :indexed => true,
2298+ :choices =>
2299+ services.values.collect { |type|
2300+ type.keys.collect(&:to_s)
2301+ }.flatten
2302+ }
2303+ )
2304 end
2305 name = @options[:name] unless name
2306 unless name
2307@@ -42,13 +47,12 @@
2308 unless no_prompt || service
2309 user_services = client.services
2310 err 'No services available to delete' if user_services.empty?
2311- choose do |menu|
2312- menu.prompt = 'Please select one you wish to delete: '
2313- menu.select_by = :index_or_name
2314- user_services.each do |s|
2315- menu.choice(s[:name]) { service = s[:name] }
2316- end
2317- end
2318+ service = ask(
2319+ "Which service would you like to delete?",
2320+ { :indexed => true,
2321+ :choices => user_services.collect { |s| s[:name] }
2322+ }
2323+ )
2324 end
2325 err "Service name required." unless service
2326 display "Deleting service [#{service}]: ", false
2327@@ -80,5 +84,97 @@
2328 check_app_for_restart(dest_app)
2329 end
2330
2331+ def tunnel(service=nil, client_name=nil)
2332+ unless defined? Caldecott
2333+ display "To use `vmc tunnel', you must first install Caldecott:"
2334+ display ""
2335+ display "\tgem install caldecott"
2336+ display ""
2337+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
2338+ display "will provide one. If you're on Windows, try DevKit."
2339+ display ""
2340+ display "This manual step will be removed in the future."
2341+ display ""
2342+ err "Caldecott is not installed."
2343+ end
2344+
2345+ ps = client.services
2346+ err "No services available to tunnel to" if ps.empty?
2347+
2348+ unless service
2349+ choices = ps.collect { |s| s[:name] }.sort
2350+ service = ask(
2351+ "Which service to tunnel to?",
2352+ :choices => choices,
2353+ :indexed => true
2354+ )
2355+ end
2356+
2357+ info = ps.select { |s| s[:name] == service }.first
2358+
2359+ err "Unknown service '#{service}'" unless info
2360+
2361+ port = pick_tunnel_port(@options[:port] || 10000)
2362+
2363+ raise VMC::Client::AuthError unless client.logged_in?
2364+
2365+ if not tunnel_pushed?
2366+ display "Deploying tunnel application '#{tunnel_appname}'."
2367+ auth = UUIDTools::UUID.random_create.to_s
2368+ push_caldecott(auth)
2369+ bind_service_banner(service, tunnel_appname, false)
2370+ start_caldecott
2371+ else
2372+ auth = tunnel_auth
2373+ end
2374+
2375+ if not tunnel_healthy?(auth)
2376+ display "Redeploying tunnel application '#{tunnel_appname}'."
2377+
2378+ # We don't expect caldecott not to be running, so take the
2379+ # most aggressive restart method.. delete/re-push
2380+ client.delete_app(tunnel_appname)
2381+ invalidate_tunnel_app_info
2382+
2383+ push_caldecott(auth)
2384+ bind_service_banner(service, tunnel_appname, false)
2385+ start_caldecott
2386+ end
2387+
2388+ if not tunnel_bound?(service)
2389+ bind_service_banner(service, tunnel_appname)
2390+ end
2391+
2392+ conn_info = tunnel_connection_info info[:vendor], service, auth
2393+ display_tunnel_connection_info(conn_info)
2394+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
2395+ start_tunnel(port, conn_info, auth)
2396+
2397+ clients = get_clients_for(info[:vendor])
2398+
2399+ if clients.empty?
2400+ client_name ||= "none"
2401+ else
2402+ client_name ||= ask(
2403+ "Which client would you like to start?",
2404+ :choices => ["none"] + clients.keys,
2405+ :indexed => true
2406+ )
2407+ end
2408+
2409+ if client_name == "none"
2410+ wait_for_tunnel_end
2411+ else
2412+ wait_for_tunnel_start(port)
2413+ unless start_local_prog(clients, client_name, conn_info, port)
2414+ err "'#{client_name}' execution failed; is it in your $PATH?"
2415+ end
2416+ end
2417+ end
2418+
2419+ def get_clients_for(type)
2420+ conf = VMC::Cli::Config.clients
2421+ conf[type] || {}
2422+ end
2423 end
2424 end
2425
2426=== modified file 'lib/cli/commands/user.rb'
2427--- lib/cli/commands/user.rb 2011-06-17 13:38:56 +0000
2428+++ lib/cli/commands/user.rb 2012-06-13 01:37:18 +0000
2429@@ -12,19 +12,24 @@
2430 def login(email=nil)
2431 email = @options[:email] unless email
2432 password = @options[:password]
2433- tries = 0
2434- email = ask("Email: ") unless no_prompt || email
2435- password = ask("Password: ") {|q| q.echo = '*'} unless no_prompt || password
2436+ tries ||= 0
2437+
2438+ unless no_prompt
2439+ display "Attempting login to [#{target_url}]" if target_url
2440+ email ||= ask("Email")
2441+ password ||= ask("Password", :echo => "*")
2442+ end
2443+
2444 err "Need a valid email" unless email
2445 err "Need a password" unless password
2446 login_and_save_token(email, password)
2447 say "Successfully logged into [#{target_url}]".green
2448 rescue VMC::Client::TargetError
2449- display "Problem with login, invalid account or password.".red
2450+ display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
2451 retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
2452 exit 1
2453 rescue => e
2454- display "Problem with login, #{e}, try again or register for an account.".red
2455+ display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
2456 exit 1
2457 end
2458
2459@@ -39,8 +44,8 @@
2460 err "Need to be logged in to change password." unless email
2461 say "Changing password for '#{email}'\n"
2462 unless no_prompt
2463- password = ask("New Password: ") {|q| q.echo = '*'}
2464- password2 = ask("Verify Password: ") {|q| q.echo = '*'}
2465+ password = ask "New Password", :echo => "*"
2466+ password2 = ask "Verify Password", :echo => "*"
2467 err "Passwords did not match, try again" if password != password2
2468 end
2469 err "Password required" unless password
2470@@ -52,7 +57,7 @@
2471
2472 def login_and_save_token(email, password)
2473 token = client.login(email, password)
2474- VMC::Cli::Config.store_token(token)
2475+ VMC::Cli::Config.store_token(token, @options[:token_file])
2476 end
2477
2478 end
2479
2480=== modified file 'lib/cli/config.rb'
2481--- lib/cli/config.rb 2011-06-17 13:38:56 +0000
2482+++ lib/cli/config.rb 2012-06-13 01:37:18 +0000
2483@@ -8,56 +8,60 @@
2484 class Config
2485
2486 DEFAULT_TARGET = 'api.vcap.me'
2487- DEFAULT_SUGGEST = 'vcap.me'
2488
2489 TARGET_FILE = '~/.vmc_target'
2490 TOKEN_FILE = '~/.vmc_token'
2491 INSTANCES_FILE = '~/.vmc_instances'
2492 ALIASES_FILE = '~/.vmc_aliases'
2493+ CLIENTS_FILE = '~/.vmc_clients'
2494+ MICRO_FILE = '~/.vmc_micro'
2495+
2496+ STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
2497
2498 class << self
2499 attr_accessor :colorize
2500 attr_accessor :output
2501 attr_accessor :trace
2502 attr_accessor :nozip
2503- attr_reader :suggest_url
2504
2505 def target_url
2506 return @target_url if @target_url
2507 target_file = File.expand_path(TARGET_FILE)
2508 if File.exists? target_file
2509- @target_url = File.read(target_file).strip!
2510- ha = @target_url.split('.')
2511- ha.shift
2512- @suggest_url = ha.join('.')
2513- @suggest_url = DEFAULT_SUGGEST if @suggest_url.empty?
2514+ @target_url = lock_and_read(target_file).strip
2515 else
2516 @target_url = DEFAULT_TARGET
2517- @suggest_url = DEFAULT_SUGGEST
2518 end
2519 @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
2520 @target_url = @target_url.gsub(/\/+$/, '')
2521 @target_url
2522 end
2523
2524+ def base_of(url)
2525+ url.sub(/^[^\.]+\./, "")
2526+ end
2527+
2528+ def suggest_url
2529+ @suggest_url ||= base_of(target_url)
2530+ end
2531+
2532 def store_target(target_host)
2533 target_file = File.expand_path(TARGET_FILE)
2534- File.open(target_file, 'w+') { |f| f.puts target_host }
2535- FileUtils.chmod 0600, target_file
2536+ lock_and_write(target_file, target_host)
2537 end
2538
2539- def all_tokens
2540- token_file = File.expand_path(TOKEN_FILE)
2541+ def all_tokens(token_file_path=nil)
2542+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
2543 return nil unless File.exists? token_file
2544- contents = File.read(token_file).strip
2545+ contents = lock_and_read(token_file).strip
2546 JSON.parse(contents)
2547 end
2548
2549 alias :targets :all_tokens
2550
2551- def auth_token
2552+ def auth_token(token_file_path=nil)
2553 return @token if @token
2554- tokens = all_tokens
2555+ tokens = all_tokens(token_file_path)
2556 @token = tokens[target_url] if tokens
2557 end
2558
2559@@ -65,24 +69,23 @@
2560 FileUtils.rm_f(File.expand_path(TOKEN_FILE))
2561 end
2562
2563- def store_token(token)
2564- tokens = all_tokens || {}
2565+ def store_token(token, token_file_path=nil)
2566+ tokens = all_tokens(token_file_path) || {}
2567 tokens[target_url] = token
2568- token_file = File.expand_path(TOKEN_FILE)
2569- File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }
2570- FileUtils.chmod 0600, token_file
2571+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
2572+ lock_and_write(token_file, tokens.to_json)
2573 end
2574
2575 def instances
2576 instances_file = File.expand_path(INSTANCES_FILE)
2577 return nil unless File.exists? instances_file
2578- contents = File.read(instances_file).strip
2579+ contents = lock_and_read(instances_file).strip
2580 JSON.parse(contents)
2581 end
2582
2583 def store_instances(instances)
2584 instances_file = File.expand_path(INSTANCES_FILE)
2585- File.open(instances_file, 'w') { |f| f.write(instances.to_json) }
2586+ lock_and_write(instances_file, instances.to_json)
2587 end
2588
2589 def aliases
2590@@ -100,6 +103,66 @@
2591 File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
2592 end
2593
2594+ def micro
2595+ micro_file = File.expand_path(MICRO_FILE)
2596+ return {} unless File.exists? micro_file
2597+ contents = lock_and_read(micro_file).strip
2598+ JSON.parse(contents)
2599+ end
2600+
2601+ def store_micro(micro)
2602+ micro_file = File.expand_path(MICRO_FILE)
2603+ lock_and_write(micro_file, micro.to_json)
2604+ end
2605+
2606+ def deep_merge(a, b)
2607+ merge = proc do |_, old, new|
2608+ if new.is_a?(Hash) and old.is_a?(Hash)
2609+ old.merge(new, &merge)
2610+ else
2611+ new
2612+ end
2613+ end
2614+
2615+ a.merge(b, &merge)
2616+ end
2617+
2618+ def clients
2619+ return @clients if @clients
2620+
2621+ stock = YAML.load_file(STOCK_CLIENTS)
2622+ clients = File.expand_path CLIENTS_FILE
2623+ if File.exists? clients
2624+ user = YAML.load_file(clients)
2625+ @clients = deep_merge(stock, user)
2626+ else
2627+ @clients = stock
2628+ end
2629+ end
2630+
2631+ def lock_and_read(file)
2632+ File.open(file, File::RDONLY) {|f|
2633+ if defined? JRUBY_VERSION
2634+ f.flock(File::LOCK_SH)
2635+ else
2636+ f.flock(File::LOCK_EX)
2637+ end
2638+ contents = f.read
2639+ f.flock(File::LOCK_UN)
2640+ contents
2641+ }
2642+ end
2643+
2644+ def lock_and_write(file, contents)
2645+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
2646+ f.flock(File::LOCK_EX)
2647+ f.rewind
2648+ f.puts contents
2649+ f.flush
2650+ f.truncate(f.pos)
2651+ f.flock(File::LOCK_UN)
2652+ }
2653+ end
2654 end
2655
2656 def initialize(work_dir = Dir.pwd)
2657
2658=== added file 'lib/cli/console_helper.rb'
2659--- lib/cli/console_helper.rb 1970-01-01 00:00:00 +0000
2660+++ lib/cli/console_helper.rb 2012-06-13 01:37:18 +0000
2661@@ -0,0 +1,160 @@
2662+require 'net/telnet'
2663+require 'readline'
2664+
2665+module VMC::Cli
2666+ module ConsoleHelper
2667+
2668+ def console_connection_info(appname)
2669+ app = client.app_info(appname)
2670+ fw = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model])
2671+ if !fw.console
2672+ err "'#{appname}' is a #{fw.name} application. " +
2673+ "Console access is not supported for #{fw.name} applications."
2674+ end
2675+ instances_info_envelope = client.app_instances(appname)
2676+ instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
2677+
2678+ instances_info = instances_info_envelope[:instances] || []
2679+ err "No running instances for [#{appname}]" if instances_info.empty?
2680+
2681+ entry = instances_info[0]
2682+ if !entry[:console_port]
2683+ begin
2684+ client.app_files(appname, '/app/cf-rails-console')
2685+ err "Console port not provided for [#{appname}]. Try restarting the app."
2686+ rescue VMC::Client::TargetError, VMC::Client::NotFound
2687+ err "Console access not supported for [#{appname}]. " +
2688+ "Please redeploy your app to enable support."
2689+ end
2690+ end
2691+ conn_info = {'hostname' => entry[:console_ip], 'port' => entry[:console_port]}
2692+ end
2693+
2694+ def start_local_console(port, appname)
2695+ auth_info = console_credentials(appname)
2696+ display "Connecting to '#{appname}' console: ", false
2697+ prompt = console_login(auth_info, port)
2698+ display "OK".green
2699+ display "\n"
2700+ initialize_readline
2701+ run_console prompt
2702+ end
2703+
2704+ def console_login(auth_info, port)
2705+ if !auth_info["username"] || !auth_info["password"]
2706+ err "Unable to verify console credentials."
2707+ end
2708+ @telnet_client = telnet_client(port)
2709+ prompt = nil
2710+ err_msg = "Login attempt timed out."
2711+ 5.times do
2712+ begin
2713+ results = @telnet_client.login("Name"=>auth_info["username"],
2714+ "Password"=>auth_info["password"])
2715+ lines = results.sub("Login: Password: ", "").split("\n")
2716+ last_line = lines.pop
2717+ if last_line =~ /[$%#>] \z/n
2718+ prompt = last_line
2719+ elsif last_line =~ /Login failed/
2720+ err_msg = last_line
2721+ end
2722+ break
2723+ rescue TimeoutError
2724+ sleep 1
2725+ rescue EOFError
2726+ #This may happen if we login right after app starts
2727+ close_console
2728+ sleep 5
2729+ @telnet_client = telnet_client(port)
2730+ end
2731+ display ".", false
2732+ end
2733+ unless prompt
2734+ close_console
2735+ err err_msg
2736+ end
2737+ prompt
2738+ end
2739+
2740+ def send_console_command(cmd)
2741+ results = @telnet_client.cmd(cmd)
2742+ results.split("\n")
2743+ end
2744+
2745+ def console_credentials(appname)
2746+ content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
2747+ YAML.load(content)
2748+ end
2749+
2750+ def close_console
2751+ @telnet_client.close
2752+ end
2753+
2754+ def console_tab_completion_data(cmd)
2755+ begin
2756+ results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
2757+ results.chomp.split(",")
2758+ rescue TimeoutError
2759+ [] #Just return empty results if timeout occurred on tab completion
2760+ end
2761+ end
2762+
2763+ private
2764+ def telnet_client(port)
2765+ Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
2766+ end
2767+
2768+ def readline_with_history(prompt)
2769+ line = Readline::readline(prompt)
2770+ return nil if line == nil || line == 'quit' || line == 'exit'
2771+ Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
2772+ line
2773+ end
2774+
2775+ def run_console(prompt)
2776+ prev = trap("INT") { |x| exit_console; prev.call(x); exit }
2777+ prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
2778+ loop do
2779+ cmd = readline_with_history(prompt)
2780+ if(cmd == nil)
2781+ exit_console
2782+ break
2783+ end
2784+ prompt = send_console_command_display_results(cmd, prompt)
2785+ end
2786+ end
2787+
2788+ def exit_console
2789+ #TimeoutError expected, as exit doesn't return anything
2790+ @telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
2791+ close_console
2792+ end
2793+
2794+ def send_console_command_display_results(cmd, prompt)
2795+ begin
2796+ lines = send_console_command cmd
2797+ #Assumes the last line is a prompt
2798+ prompt = lines.pop
2799+ lines.each {|line| display line if line != cmd}
2800+ rescue TimeoutError
2801+ display "Timed out sending command to server.".red
2802+ rescue EOFError
2803+ err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
2804+ end
2805+ prompt
2806+ end
2807+
2808+ def initialize_readline
2809+ if Readline.respond_to?("basic_word_break_characters=")
2810+ Readline.basic_word_break_characters= " \t\n`><=;|&{("
2811+ end
2812+ Readline.completion_append_character = nil
2813+ #Assumes that sending a String ending with tab will return a non-empty
2814+ #String of comma-separated completion options, terminated by a new line
2815+ #For example, "app.\t" might result in "to_s,nil?,etc\n"
2816+ Readline.completion_proc = proc {|s|
2817+ console_tab_completion_data s
2818+ }
2819+ end
2820+ end
2821+end
2822
2823=== modified file 'lib/cli/core_ext.rb'
2824--- lib/cli/core_ext.rb 2011-06-17 13:38:56 +0000
2825+++ lib/cli/core_ext.rb 2012-06-13 01:37:18 +0000
2826@@ -38,6 +38,10 @@
2827 raise VMC::Cli::CliExit, "#{prefix}#{message}"
2828 end
2829
2830+ def warn(msg)
2831+ say "#{"[WARNING]".yellow} #{msg}"
2832+ end
2833+
2834 def quit(message = nil)
2835 raise VMC::Cli::GracefulExit, message
2836 end
2837@@ -64,7 +68,6 @@
2838 return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
2839 return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
2840 end
2841-
2842 end
2843
2844 module VMCStringExtensions
2845
2846=== modified file 'lib/cli/frameworks.rb'
2847--- lib/cli/frameworks.rb 2011-06-17 13:38:56 +0000
2848+++ lib/cli/frameworks.rb 2012-06-13 01:37:18 +0000
2849@@ -6,47 +6,78 @@
2850 DEFAULT_MEM = '256M'
2851
2852 FRAMEWORKS = {
2853- 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application'}],
2854+ 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application', :console=>true}],
2855 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
2856 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
2857- 'Roo' => ['spring', { :mem => '512M', :description => 'Java SpringSource Roo Application'}],
2858- 'JavaWeb' => ['spring', { :mem => '512M', :description => 'Java Web Application'}],
2859+ 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
2860+ 'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}],
2861+ 'Standalone' => ['standalone', { :mem => '64M', :description => 'Standalone Application'}],
2862 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
2863 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],
2864+ 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
2865+ 'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '64M', :description => 'Erlang/OTP Rebar Application'}],
2866+ 'WSGI' => ['wsgi', { :mem => '64M', :description => 'Python WSGI Application'}],
2867+ 'Django' => ['django', { :mem => '128M', :description => 'Python Django Application'}],
2868+ 'dotNet' => ['dotNet', { :mem => '128M', :description => '.Net Web Application'}],
2869+ 'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}],
2870+ 'Play' => ['play', { :mem => '256M', :description => 'Play Framework Application'}]
2871 }
2872
2873 class << self
2874
2875- def known_frameworks
2876- FRAMEWORKS.keys
2877+ def known_frameworks(available_frameworks)
2878+ frameworks = []
2879+ FRAMEWORKS.each do |key,fw|
2880+ frameworks << key if available_frameworks.include? [fw[0]]
2881+ end
2882+ frameworks
2883 end
2884
2885 def lookup(name)
2886- return Framework.new(*FRAMEWORKS[name])
2887- end
2888-
2889- def detect(path)
2890+ return create(*FRAMEWORKS[name])
2891+ end
2892+
2893+ def lookup_by_framework(name)
2894+ FRAMEWORKS.each do |key,fw|
2895+ return create(fw[0],fw[1]) if fw[0] == name
2896+ end
2897+ end
2898+
2899+ def create(name,opts)
2900+ if name == "standalone"
2901+ return StandaloneFramework.new(name, opts)
2902+ else
2903+ return Framework.new(name,opts)
2904+ end
2905+ end
2906+
2907+ def detect(path, available_frameworks)
2908+ if !File.directory? path
2909+ if path.end_with?('.war')
2910+ return detect_framework_from_war path
2911+ elsif path.end_with?('.zip')
2912+ return detect_framework_from_zip path, available_frameworks
2913+ elsif available_frameworks.include?(["standalone"])
2914+ return Framework.lookup('Standalone')
2915+ else
2916+ return nil
2917+ end
2918+ end
2919 Dir.chdir(path) do
2920-
2921 # Rails
2922 if File.exist?('config/environment.rb')
2923 return Framework.lookup('Rails')
2924
2925- # Java
2926+ # Rack
2927+ elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
2928+ return Framework.lookup('Rack')
2929+
2930+ # Java Web Apps
2931 elsif Dir.glob('*.war').first
2932- war_file = Dir.glob('*.war').first
2933- contents = ZipUtil.entry_lines(war_file)
2934+ return detect_framework_from_war(Dir.glob('*.war').first)
2935
2936- # Spring Variations
2937- if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
2938- return Framework.lookup('Grails')
2939- elsif contents =~ /WEB-INF\/classes\/org\/springframework/
2940- return Framework.lookup('Spring')
2941- elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
2942- return Framework.lookup('Spring')
2943- else
2944- return Framework.lookup('JavaWeb')
2945- end
2946+ elsif File.exist?('WEB-INF/web.xml')
2947+ return detect_framework_from_war
2948
2949 # Simple Ruby Apps
2950 elsif !Dir.glob('*.rb').empty?
2951@@ -55,43 +86,180 @@
2952 next if matched_file
2953 File.open(fname, 'r') do |f|
2954 str = f.read # This might want to be limited
2955- matched_file = fname if (str && str.match(/^\s*require\s*['"]sinatra['"]/))
2956+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
2957 end
2958 end
2959 if matched_file
2960+ # Sinatra apps
2961 f = Framework.lookup('Sinatra')
2962 f.exec = "ruby #{matched_file}"
2963 return f
2964 end
2965
2966+ # PHP
2967+ elsif !Dir.glob('*.php').empty?
2968+ return Framework.lookup('PHP')
2969+
2970+ # Erlang/OTP using Rebar
2971+ elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
2972+ return Framework.lookup('Erlang/OTP Rebar')
2973+
2974+ # Python Django
2975+ # XXX: not all django projects keep settings.py in top-level directory
2976+ elsif File.exist?('manage.py') && File.exist?('settings.py')
2977+ return Framework.lookup('Django')
2978+
2979+ # Python
2980+ elsif !Dir.glob('wsgi.py').empty?
2981+ return Framework.lookup('WSGI')
2982+
2983+ # .Net
2984+ elsif !Dir.glob('web.config').empty?
2985+ return Framework.lookup('dotNet')
2986+
2987 # Node.js
2988 elsif !Dir.glob('*.js').empty?
2989- # Fixme, make other files work too..
2990- if File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
2991+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
2992 return Framework.lookup('Node')
2993 end
2994+
2995+ # Play or Standalone Apps
2996+ elsif Dir.glob('*.zip').first
2997+ zip_file = Dir.glob('*.zip').first
2998+ return detect_framework_from_zip zip_file, available_frameworks
2999 end
3000- end
3001- nil
3002- end
3003-
3004+
3005+ # Default to Standalone if no other match was made
3006+ return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"])
3007+ end
3008+ end
3009+
3010+ def detect_framework_from_war(war_file=nil)
3011+ if war_file
3012+ contents = ZipUtil.entry_lines(war_file)
3013+ else
3014+ #assume we are working with current dir
3015+ contents = Dir['**/*'].join("\n")
3016+ end
3017+
3018+ # Spring/Lift Variations
3019+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
3020+ return Framework.lookup('Grails')
3021+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
3022+ return Framework.lookup('Lift')
3023+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
3024+ return Framework.lookup('Spring')
3025+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
3026+ return Framework.lookup('Spring')
3027+ elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
3028+ return Framework.lookup('Spring')
3029+ else
3030+ return Framework.lookup('JavaWeb')
3031+ end
3032+ end
3033+
3034+ def detect_framework_from_zip(zip_file, available_frameworks)
3035+ contents = ZipUtil.entry_lines(zip_file)
3036+ detect_framework_from_zip_contents(contents, available_frameworks)
3037+ end
3038+
3039+ def detect_framework_from_zip_contents(contents, available_frameworks)
3040+ if available_frameworks.include?(["play"]) && contents =~ /lib\/play\..*\.jar/
3041+ return Framework.lookup('Play')
3042+ elsif available_frameworks.include?(["standalone"])
3043+ return Framework.lookup('Standalone')
3044+ end
3045+ end
3046 end
3047
3048- attr_reader :name, :description, :memory
3049+ attr_reader :name, :description, :console
3050 attr_accessor :exec
3051
3052- alias :mem :memory
3053-
3054 def initialize(framework=nil, opts={})
3055 @name = framework || DEFAULT_FRAMEWORK
3056 @memory = opts[:mem] || DEFAULT_MEM
3057 @description = opts[:description] || 'Unknown Application Type'
3058 @exec = opts[:exec]
3059+ @console = opts[:console] || false
3060 end
3061
3062 def to_s
3063 description
3064 end
3065+
3066+ def require_url?
3067+ true
3068+ end
3069+
3070+ def require_start_command?
3071+ false
3072+ end
3073+
3074+ def prompt_for_runtime?
3075+ false
3076+ end
3077+
3078+ def default_runtime(path)
3079+ nil
3080+ end
3081+
3082+ def memory(runtime=nil)
3083+ @memory
3084+ end
3085+
3086+ alias :mem :memory
3087+ end
3088+
3089+ class StandaloneFramework < Framework
3090+ def require_url?
3091+ false
3092+ end
3093+
3094+ def require_start_command?
3095+ true
3096+ end
3097+
3098+ def prompt_for_runtime?
3099+ true
3100+ end
3101+
3102+ def default_runtime(path)
3103+ if !File.directory? path
3104+ if path =~ /\.(jar|class)$/
3105+ return "java"
3106+ elsif path =~ /\.(rb)$/
3107+ return "ruby18"
3108+ elsif path =~ /\.(zip)$/
3109+ return detect_runtime_from_zip path
3110+ end
3111+ else
3112+ Dir.chdir(path) do
3113+ return "ruby18" if not Dir.glob('**/*.rb').empty?
3114+ if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty?
3115+ return "java"
3116+ elsif Dir.glob('*.zip').first
3117+ zip_file = Dir.glob('*.zip').first
3118+ return detect_runtime_from_zip zip_file
3119+ end
3120+ end
3121+ end
3122+ return nil
3123+ end
3124+
3125+ def memory(runtime=nil)
3126+ default_mem = @memory
3127+ default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php"
3128+ default_mem = '512M' if runtime == "java" || runtime == "java7"
3129+ default_mem
3130+ end
3131+
3132+ private
3133+ def detect_runtime_from_zip(zip_file)
3134+ contents = ZipUtil.entry_lines(zip_file)
3135+ if contents =~ /\.(jar)$/
3136+ return "java"
3137+ end
3138+ end
3139 end
3140
3141 end
3142
3143=== added file 'lib/cli/manifest_helper.rb'
3144--- lib/cli/manifest_helper.rb 1970-01-01 00:00:00 +0000
3145+++ lib/cli/manifest_helper.rb 2012-06-13 01:37:18 +0000
3146@@ -0,0 +1,302 @@
3147+require "set"
3148+
3149+module VMC::Cli::ManifestHelper
3150+ include VMC::Cli::ServicesHelper
3151+
3152+ DEFAULTS = {
3153+ "url" => "${name}.${target-base}",
3154+ "mem" => "128M",
3155+ "instances" => 1
3156+ }
3157+
3158+ MANIFEST = "manifest.yml"
3159+
3160+ YES_SET = Set.new(["y", "Y", "yes", "YES"])
3161+
3162+ # take a block and call it once for each app to push/update.
3163+ # with @application and @app_info set appropriately
3164+ def each_app(panic=true)
3165+ if @manifest and all_apps = @manifest["applications"]
3166+ where = File.expand_path(@path)
3167+ single = false
3168+
3169+ all_apps.each do |path, info|
3170+ app = File.expand_path("../" + path, manifest_file)
3171+ if where.start_with?(app)
3172+ @application = app
3173+ @app_info = info
3174+ yield info["name"]
3175+ single = true
3176+ break
3177+ end
3178+ end
3179+
3180+ unless single
3181+ if where == File.expand_path("../", manifest_file)
3182+ ordered_by_deps(all_apps).each do |path, info|
3183+ app = File.expand_path("../" + path, manifest_file)
3184+ @application = app
3185+ @app_info = info
3186+ yield info["name"]
3187+ end
3188+ else
3189+ err "Path '#{@path}' is not known to manifest '#{manifest_file}'."
3190+ end
3191+ end
3192+ else
3193+ @application = @path
3194+ @app_info = @manifest
3195+ if @app_info
3196+ yield @app_info["name"]
3197+ elsif panic
3198+ err "No applications."
3199+ end
3200+ end
3201+
3202+ nil
3203+ ensure
3204+ @application = nil
3205+ @app_info = nil
3206+ end
3207+
3208+ def interact(many=false)
3209+ @manifest ||= {}
3210+ configure_app(many)
3211+ end
3212+
3213+ def target_manifest
3214+ @options[:manifest] || MANIFEST
3215+ end
3216+
3217+ def save_manifest(save_to = nil)
3218+ save_to ||= target_manifest
3219+
3220+ File.open(save_to, "w") do |f|
3221+ f.write @manifest.to_yaml
3222+ end
3223+
3224+ say "Manifest written to #{save_to}."
3225+ end
3226+
3227+ def configure_app(many=false)
3228+ name = manifest("name") ||
3229+ set(ask("Application Name", :default => manifest("name")), "name")
3230+
3231+
3232+
3233+ if manifest "framework"
3234+ framework = VMC::Cli::Framework.lookup_by_framework manifest("framework","name")
3235+ else
3236+ framework = detect_framework
3237+ set framework.name, "framework", "name"
3238+ set(
3239+ { "mem" => framework.mem,
3240+ "description" => framework.description,
3241+ "exec" => framework.exec
3242+ },
3243+ "framework",
3244+ "info"
3245+ )
3246+ end
3247+
3248+ default_runtime = manifest "runtime"
3249+ if not default_runtime
3250+ default_runtime = framework.default_runtime(@application)
3251+ set(detect_runtime(default_runtime), "runtime") if framework.prompt_for_runtime?
3252+ end
3253+ default_command = manifest "command"
3254+ set ask("Start Command", :default => default_command), "command" if framework.require_start_command?
3255+
3256+ url_template = manifest("url") || DEFAULTS["url"]
3257+ url_resolved = url_template.dup
3258+ resolve_lexically(url_resolved)
3259+
3260+ if !framework.require_url?
3261+ url_resolved = "None"
3262+ end
3263+ url = ask("Application Deployed URL", :default => url_resolved)
3264+
3265+ if url == url_resolved && url != "None"
3266+ url = url_template
3267+ end
3268+
3269+ # common error case is for prompted users to answer y or Y or yes or
3270+ # YES to this ask() resulting in an unintended URL of y. Special
3271+ # case this common error
3272+ url = url_resolved if YES_SET.member? url
3273+
3274+ if(url == "None")
3275+ url = nil
3276+ end
3277+
3278+ set url, "url"
3279+
3280+ default_mem = manifest("mem")
3281+ default_mem = framework.memory(manifest("runtime")) if not default_mem
3282+ set ask(
3283+ "Memory reservation",
3284+ :default =>
3285+ default_mem ||
3286+ DEFAULTS["mem"],
3287+ :choices => ["128M", "256M", "512M", "1G", "2G"]
3288+ ), "mem"
3289+
3290+ set ask(
3291+ "How many instances?",
3292+ :default => manifest("instances") || DEFAULTS["instances"]
3293+ ), "instances"
3294+
3295+ unless manifest "services"
3296+ user_services = client.services
3297+ user_services.sort! {|a, b| a[:name] <=> b[:name] }
3298+
3299+ unless user_services.empty?
3300+ if ask "Bind existing services to '#{name}'?", :default => false
3301+ bind_services(user_services)
3302+ end
3303+ end
3304+
3305+ services = client.services_info
3306+ unless services.empty?
3307+ if ask "Create services to bind to '#{name}'?", :default => false
3308+ create_services(services.values.collect(&:keys).flatten)
3309+ end
3310+ end
3311+ end
3312+
3313+ if many and ask("Configure for another application?", :default => false)
3314+ @application = ask "Application path?"
3315+ configure_app
3316+ end
3317+ end
3318+
3319+ def set(what, *where)
3320+ where.unshift "applications", @application
3321+
3322+ which = @manifest
3323+ where.each_with_index do |k, i|
3324+ if i + 1 == where.size
3325+ which[k] = what
3326+ else
3327+ which = (which[k] ||= {})
3328+ end
3329+ end
3330+
3331+ what
3332+ end
3333+
3334+ # Detect the appropriate framework.
3335+ def detect_framework(prompt_ok = true)
3336+ framework = VMC::Cli::Framework.detect(@application, frameworks_info)
3337+ framework_correct = ask("Detected a #{framework}, is this correct?", :default => true) if prompt_ok && framework
3338+ if prompt_ok && (framework.nil? || !framework_correct)
3339+ display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
3340+ framework = nil if !framework_correct
3341+ framework = VMC::Cli::Framework.lookup(
3342+ ask(
3343+ "Select Application Type",
3344+ :indexed => true,
3345+ :default => framework,
3346+ :choices => VMC::Cli::Framework.known_frameworks(frameworks_info)
3347+ )
3348+ )
3349+ display "Selected #{framework}"
3350+ end
3351+
3352+ framework
3353+ end
3354+
3355+ # Detect the appropriate runtime.
3356+ def detect_runtime(default, prompt_ok=true)
3357+ runtime = nil
3358+ runtime_keys=[]
3359+ runtimes_info.keys.each {|runtime_key| runtime_keys << runtime_key.dup }
3360+ runtime_keys.sort!
3361+ if prompt_ok
3362+ runtime = ask(
3363+ "Select Runtime",
3364+ :indexed => true,
3365+ :default => default,
3366+ :choices => runtime_keys
3367+ )
3368+ display "Selected #{runtime}"
3369+ end
3370+ runtime
3371+ end
3372+
3373+ def bind_services(user_services, chosen = 0)
3374+ svcname = ask(
3375+ "Which one?",
3376+ :indexed => true,
3377+ :choices => user_services.collect { |p| p[:name] })
3378+
3379+ svc = user_services.find { |p| p[:name] == svcname }
3380+
3381+ set svc[:vendor], "services", svcname, "type"
3382+
3383+ if chosen + 1 < user_services.size && ask("Bind another?", :default => false)
3384+ bind_services(user_services, chosen + 1)
3385+ end
3386+ end
3387+
3388+ def create_services(services)
3389+ svcs = services.collect(&:to_s).sort!
3390+
3391+ configure_service(
3392+ ask(
3393+ "What kind of service?",
3394+ :indexed => true,
3395+ :choices => svcs
3396+ )
3397+ )
3398+
3399+ if ask "Create another?", :default => false
3400+ create_services(services)
3401+ end
3402+ end
3403+
3404+ def configure_service(vendor)
3405+ default_name = random_service_name(vendor)
3406+ name = ask "Specify the name of the service", :default => default_name
3407+
3408+ set vendor, "services", name, "type"
3409+ end
3410+
3411+ private
3412+ def ordered_by_deps(apps, abspaths = nil, processed = Set[])
3413+ unless abspaths
3414+ abspaths = {}
3415+ apps.each do |p, i|
3416+ ep = File.expand_path("../" + p, manifest_file)
3417+ abspaths[ep] = i
3418+ end
3419+ end
3420+
3421+ ordered = []
3422+ apps.each do |path, info|
3423+ epath = File.expand_path("../" + path, manifest_file)
3424+
3425+ if deps = info["depends-on"]
3426+ dep_apps = {}
3427+ deps.each do |dep|
3428+ edep = File.expand_path("../" + dep, manifest_file)
3429+
3430+ err "Circular dependency detected." if processed.include? edep
3431+
3432+ dep_apps[dep] = abspaths[edep]
3433+ end
3434+
3435+ processed.add(epath)
3436+
3437+ ordered += ordered_by_deps(dep_apps, abspaths, processed)
3438+ ordered << [path, info]
3439+ elsif not processed.include? epath
3440+ ordered << [path, info]
3441+ processed.add(epath)
3442+ end
3443+ end
3444+
3445+ ordered
3446+ end
3447+
3448+end
3449
3450=== modified file 'lib/cli/runner.rb'
3451--- lib/cli/runner.rb 2011-06-17 13:38:56 +0000
3452+++ lib/cli/runner.rb 2012-06-13 01:37:18 +0000
3453@@ -31,6 +31,7 @@
3454 opts.on('--passwd PASS') { |pass| @options[:password] = pass }
3455 opts.on('--pass PASS') { |pass| @options[:password] = pass }
3456 opts.on('--password PASS') { |pass| @options[:password] = pass }
3457+ opts.on('--token-file TOKEN_FILE') { |token_file| @options[:token_file] = token_file }
3458 opts.on('--app NAME') { |name| @options[:name] = name }
3459 opts.on('--name NAME') { |name| @options[:name] = name }
3460 opts.on('--bind BIND') { |bind| @options[:bind] = bind }
3461@@ -48,8 +49,21 @@
3462 opts.on('-t [TKEY]') { |tkey| @options[:trace] = tkey || true }
3463 opts.on('--trace [TKEY]') { |tkey| @options[:trace] = tkey || true }
3464
3465+ # start application in debug mode
3466+ opts.on('-d [MODE]') { |mode| @options[:debug] = mode || "run" }
3467+ opts.on('--debug [MODE]') { |mode| @options[:debug] = mode || "run" }
3468+
3469+ # override manifest file
3470+ opts.on('-m FILE') { |file| @options[:manifest] = file }
3471+ opts.on('--manifest FILE') { |file| @options[:manifest] = file }
3472+
3473 opts.on('-q', '--quiet') { @options[:quiet] = true }
3474
3475+ # micro cloud options
3476+ opts.on('--vmx FILE') { |file| @options[:vmx] = file }
3477+ opts.on('--vmrun FILE') { |file| @options[:vmrun] = file }
3478+ opts.on('--save') { @options[:save] = true }
3479+
3480 # Don't use builtin zip
3481 opts.on('--no-zip') { @options[:nozip] = true }
3482 opts.on('--nozip') { @options[:nozip] = true }
3483@@ -73,6 +87,8 @@
3484 opts.on('-v', '--version') { set_cmd(:misc, :version) }
3485 opts.on('-h', '--help') { puts "#{command_usage}\n"; exit }
3486
3487+ opts.on('--port PORT') { |port| @options[:port] = port }
3488+
3489 opts.on('--runtime RUNTIME') { |rt| @options[:runtime] = rt }
3490
3491 # deprecated
3492@@ -107,7 +123,6 @@
3493 def convert_options!
3494 # make sure certain options are valid and in correct form.
3495 @options[:instances] = Integer(@options[:instances]) if @options[:instances]
3496- @options[:instance] = Integer(@options[:instance]) if @options[:instance]
3497 end
3498
3499 def set_cmd(namespace, action, args_range=0)
3500@@ -204,6 +219,10 @@
3501 usage('vmc delete-user <user>')
3502 set_cmd(:admin, :delete_user, 1)
3503
3504+ when 'users'
3505+ usage('vmc users')
3506+ set_cmd(:admin, :users)
3507+
3508 when 'apps'
3509 usage('vmc apps')
3510 set_cmd(:apps, :apps)
3511@@ -214,19 +233,15 @@
3512
3513 when 'start'
3514 usage('vmc start <appname>')
3515- set_cmd(:apps, :start, 1)
3516+ set_cmd(:apps, :start, @args.size == 1 ? 1 : 0)
3517
3518 when 'stop'
3519 usage('vmc stop <appname>')
3520- set_cmd(:apps, :stop, 1)
3521+ set_cmd(:apps, :stop, @args.size == 1 ? 1 : 0)
3522
3523 when 'restart'
3524 usage('vmc restart <appname>')
3525- set_cmd(:apps, :restart, 1)
3526-
3527- when 'rename'
3528- usage('vmc rename <appname> <newname>')
3529- set_cmd(:apps, :rename, 2)
3530+ set_cmd(:apps, :restart, @args.size == 1 ? 1 : 0)
3531
3532 when 'mem'
3533 usage('vmc mem <appname> [memsize]')
3534@@ -238,7 +253,7 @@
3535
3536 when 'stats'
3537 usage('vmc stats <appname>')
3538- set_cmd(:apps, :stats, 1)
3539+ set_cmd(:apps, :stats, @args.size == 1 ? 1 : 0)
3540
3541 when 'map'
3542 usage('vmc map <appname> <url>')
3543@@ -269,12 +284,12 @@
3544 set_cmd(:apps, :logs, 1)
3545
3546 when 'instances', 'scale'
3547- if @args.size == 1
3548+ if @args.size > 1
3549+ usage('vmc instances <appname> <num|delta>')
3550+ set_cmd(:apps, :instances, 2)
3551+ else
3552 usage('vmc instances <appname>')
3553 set_cmd(:apps, :instances, 1)
3554- else
3555- usage('vmc instances <appname> <num|delta>')
3556- set_cmd(:apps, :instances, 2)
3557 end
3558
3559 when 'crashes'
3560@@ -286,7 +301,7 @@
3561 set_cmd(:apps, :crashlogs, 1)
3562
3563 when 'push'
3564- usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--no-start]')
3565+ usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--runtime RUNTIME] [--no-start]')
3566 if @args.size == 1
3567 set_cmd(:apps, :push, 1)
3568 else
3569@@ -295,7 +310,7 @@
3570
3571 when 'update'
3572 usage('vmc update <appname> [--path PATH]')
3573- set_cmd(:apps, :update, 1)
3574+ set_cmd(:apps, :update, @args.size == 1 ? 1 : 0)
3575
3576 when 'services'
3577 usage('vmc services')
3578@@ -360,6 +375,22 @@
3579 usage('vmc unalias <alias>')
3580 set_cmd(:misc, :unalias, 1)
3581
3582+ when 'tunnel'
3583+ usage('vmc tunnel [servicename] [clientcmd] [--port port]')
3584+ set_cmd(:services, :tunnel, 0) if @args.size == 0
3585+ set_cmd(:services, :tunnel, 1) if @args.size == 1
3586+ set_cmd(:services, :tunnel, 2) if @args.size == 2
3587+
3588+ when 'rails-console'
3589+ usage('vmc rails-console <appname>')
3590+ set_cmd(:apps, :console, 1)
3591+
3592+ when 'micro'
3593+ usage('vmc micro <online|offline|status> [--password password] [--save] [--vmx file] [--vmrun executable]')
3594+ if %w[online offline status].include?(@args[0])
3595+ set_cmd(:micro, @args[0].to_sym, 1)
3596+ end
3597+
3598 when 'help'
3599 display_help if @args.size == 0
3600 @help_only = true
3601@@ -374,6 +405,14 @@
3602 @args = @args.unshift('--options')
3603 parse_options!
3604
3605+ when 'manifest'
3606+ usage('vmc manifest')
3607+ set_cmd(:manifest, :edit)
3608+
3609+ when 'extend-manifest'
3610+ usage('vmc extend-manifest')
3611+ set_cmd(:manifest, :extend, 1)
3612+
3613 else
3614 if verb
3615 display "vmc: Unknown command [#{verb}]"
3616@@ -407,8 +446,7 @@
3617
3618 def run
3619
3620- trap('TERM') { print "\nInterrupted\n"; exit(false)}
3621- trap('INT') { print "\nInterrupted\n"; exit(false)}
3622+ trap('TERM') { print "\nTerminated\n"; exit(false)}
3623
3624 parse_options!
3625
3626@@ -423,7 +461,8 @@
3627 parse_command!
3628
3629 if @namespace && @action
3630- eval("VMC::Cli::Command::#{@namespace.to_s.capitalize}").new(@options).send(@action.to_sym, *@args)
3631+ cmd = VMC::Cli::Command.const_get(@namespace.to_s.capitalize)
3632+ cmd.new(@options).send(@action, *@args.collect(&:dup))
3633 elsif @help_only || @usage
3634 display_usage
3635 else
3636@@ -432,6 +471,10 @@
3637 end
3638
3639 rescue OptionParser::InvalidOption => e
3640+ puts(e.message.red)
3641+ puts("\n")
3642+ puts(basic_usage)
3643+ @exit_status = false
3644 rescue OptionParser::AmbiguousOption => e
3645 puts(e.message.red)
3646 puts("\n")
3647@@ -464,7 +507,10 @@
3648 puts e.message.red
3649 puts e.backtrace
3650 @exit_status = false
3651- rescue => e
3652+ rescue Interrupt => e
3653+ say("\nInterrupted".red)
3654+ @exit_status = false
3655+ rescue Exception => e
3656 puts e.message.red
3657 puts e.backtrace
3658 @exit_status = false
3659
3660=== modified file 'lib/cli/services_helper.rb'
3661--- lib/cli/services_helper.rb 2011-06-17 13:38:56 +0000
3662+++ lib/cli/services_helper.rb 2012-06-13 01:37:18 +0000
3663@@ -8,15 +8,19 @@
3664
3665 return display "No system services available" if services.empty?
3666
3667+ displayed_services = []
3668+ services.each do |service_type, value|
3669+ value.each do |vendor, version|
3670+ version.each do |version_str, service|
3671+ displayed_services << [ vendor, version_str, service[:description] ]
3672+ end
3673+ end
3674+ end
3675+ displayed_services.sort! { |a, b| a.first.to_s <=> b.first.to_s}
3676+
3677 services_table = table do |t|
3678 t.headings = 'Service', 'Version', 'Description'
3679- services.each do |service_type, value|
3680- value.each do |vendor, version|
3681- version.each do |version_str, service|
3682- t << [ vendor, version_str, service[:description] ]
3683- end
3684- end
3685- end
3686+ displayed_services.each { |s| t << s }
3687 end
3688 display services_table
3689 end
3690@@ -46,19 +50,25 @@
3691 end
3692
3693 def bind_service_banner(service, appname, check_restart=true)
3694- display "Binding Service: ", false
3695+ display "Binding Service [#{service}]: ", false
3696 client.bind_service(service, appname)
3697 display 'OK'.green
3698 check_app_for_restart(appname) if check_restart
3699 end
3700
3701 def unbind_service_banner(service, appname, check_restart=true)
3702- display "Unbinding Service: ", false
3703+ display "Unbinding Service [#{service}]: ", false
3704 client.unbind_service(service, appname)
3705 display 'OK'.green
3706 check_app_for_restart(appname) if check_restart
3707 end
3708
3709+ def delete_service_banner(service)
3710+ display "Deleting service [#{service}]: ", false
3711+ client.delete_service(service)
3712+ display 'OK'.green
3713+ end
3714+
3715 def random_service_name(service)
3716 r = "%04x" % [rand(0x0100000)]
3717 "#{service.to_s}-#{r}"
3718
3719=== added file 'lib/cli/tunnel_helper.rb'
3720--- lib/cli/tunnel_helper.rb 1970-01-01 00:00:00 +0000
3721+++ lib/cli/tunnel_helper.rb 2012-06-13 01:37:18 +0000
3722@@ -0,0 +1,332 @@
3723+# Copyright (c) 2009-2011 VMware, Inc.
3724+
3725+require 'addressable/uri'
3726+
3727+begin
3728+ require 'caldecott'
3729+rescue LoadError
3730+end
3731+
3732+module VMC::Cli
3733+ module TunnelHelper
3734+ PORT_RANGE = 10
3735+
3736+ HELPER_APP = File.expand_path("../../../caldecott_helper", __FILE__)
3737+
3738+ # bump this AND the version info reported by HELPER_APP/server.rb
3739+ # this is to keep the helper in sync with any updates here
3740+ HELPER_VERSION = '0.0.4'
3741+
3742+ def tunnel_uniquename
3743+ random_service_name(tunnel_appname)
3744+ end
3745+
3746+ def tunnel_appname
3747+ "caldecott"
3748+ end
3749+
3750+ def tunnel_app_info
3751+ return @tun_app_info if @tunnel_app_info
3752+ begin
3753+ @tun_app_info = client.app_info(tunnel_appname)
3754+ rescue => e
3755+ @tun_app_info = nil
3756+ end
3757+ end
3758+
3759+ def tunnel_auth
3760+ tunnel_app_info[:env].each do |e|
3761+ name, val = e.split("=", 2)
3762+ return val if name == "CALDECOTT_AUTH"
3763+ end
3764+ nil
3765+ end
3766+
3767+ def tunnel_url
3768+ return @tunnel_url if @tunnel_url
3769+
3770+ tun_url = tunnel_app_info[:uris][0]
3771+
3772+ ["https", "http"].each do |scheme|
3773+ url = "#{scheme}://#{tun_url}"
3774+ begin
3775+ RestClient.get(url)
3776+
3777+ # https failed
3778+ rescue Errno::ECONNREFUSED
3779+
3780+ # we expect a 404 since this request isn't auth'd
3781+ rescue RestClient::ResourceNotFound
3782+ return @tunnel_url = url
3783+ end
3784+ end
3785+
3786+ err "Cannot determine URL for #{tun_url}"
3787+ end
3788+
3789+ def invalidate_tunnel_app_info
3790+ @tunnel_url = nil
3791+ @tunnel_app_info = nil
3792+ end
3793+
3794+ def tunnel_pushed?
3795+ not tunnel_app_info.nil?
3796+ end
3797+
3798+ def tunnel_healthy?(token)
3799+ return false unless tunnel_app_info[:state] == 'STARTED'
3800+
3801+ begin
3802+ response = RestClient.get(
3803+ "#{tunnel_url}/info",
3804+ "Auth-Token" => token
3805+ )
3806+
3807+ info = JSON.parse(response)
3808+ if info["version"] == HELPER_VERSION
3809+ true
3810+ else
3811+ stop_caldecott
3812+ false
3813+ end
3814+ rescue RestClient::Exception
3815+ stop_caldecott
3816+ false
3817+ end
3818+ end
3819+
3820+ def tunnel_bound?(service)
3821+ tunnel_app_info[:services].include?(service)
3822+ end
3823+
3824+ def tunnel_connection_info(type, service, token)
3825+ display "Getting tunnel connection info: ", false
3826+ response = nil
3827+ 10.times do
3828+ begin
3829+ response = RestClient.get(tunnel_url + "/" + VMC::Client.path("services", service), "Auth-Token" => token)
3830+ break
3831+ rescue RestClient::Exception
3832+ sleep 1
3833+ end
3834+
3835+ display ".", false
3836+ end
3837+
3838+ unless response
3839+ err "Expected remote tunnel to know about #{service}, but it doesn't"
3840+ end
3841+
3842+ display "OK".green
3843+
3844+ info = JSON.parse(response)
3845+ case type
3846+ when "rabbitmq"
3847+ uri = Addressable::URI.parse info["url"]
3848+ info["hostname"] = uri.host
3849+ info["port"] = uri.port
3850+ info["vhost"] = uri.path[1..-1]
3851+ info["user"] = uri.user
3852+ info["password"] = uri.password
3853+ info.delete "url"
3854+
3855+ # we use "db" as the "name" for mongo
3856+ # existing "name" is junk
3857+ when "mongodb"
3858+ info["name"] = info["db"]
3859+ info.delete "db"
3860+
3861+ # our "name" is irrelevant for redis
3862+ when "redis"
3863+ info.delete "name"
3864+ end
3865+
3866+ ['hostname', 'port', 'password'].each do |k|
3867+ err "Could not determine #{k} for #{service}" if info[k].nil?
3868+ end
3869+
3870+ info
3871+ end
3872+
3873+ def display_tunnel_connection_info(info)
3874+ display ''
3875+ display "Service connection info: "
3876+
3877+ to_show = [nil, nil, nil] # reserved for user, pass, db name
3878+ info.keys.each do |k|
3879+ case k
3880+ when "host", "hostname", "port", "node_id"
3881+ # skip
3882+ when "user", "username"
3883+ # prefer "username" over "user"
3884+ to_show[0] = k unless to_show[0] == "username"
3885+ when "password"
3886+ to_show[1] = k
3887+ when "name"
3888+ to_show[2] = k
3889+ else
3890+ to_show << k
3891+ end
3892+ end
3893+ to_show.compact!
3894+
3895+ align_len = to_show.collect(&:size).max + 1
3896+
3897+ to_show.each do |k|
3898+ # TODO: modify the server services rest call to have explicit knowledge
3899+ # about the items to return. It should return all of them if
3900+ # the service is unknown so that we don't have to do this weird
3901+ # filtering.
3902+ display " #{k.ljust align_len}: ", false
3903+ display "#{info[k]}".yellow
3904+ end
3905+ display ''
3906+ end
3907+
3908+ def start_tunnel(local_port, conn_info, auth)
3909+ @local_tunnel_thread = Thread.new do
3910+ Caldecott::Client.start({
3911+ :local_port => local_port,
3912+ :tun_url => tunnel_url,
3913+ :dst_host => conn_info['hostname'],
3914+ :dst_port => conn_info['port'],
3915+ :log_file => STDOUT,
3916+ :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
3917+ :auth_token => auth,
3918+ :quiet => true
3919+ })
3920+ end
3921+
3922+ at_exit { @local_tunnel_thread.kill }
3923+ end
3924+
3925+
3926+
3927+ def pick_tunnel_port(port)
3928+ original = port
3929+
3930+ PORT_RANGE.times do |n|
3931+ begin
3932+ TCPSocket.open('localhost', port)
3933+ port += 1
3934+ rescue
3935+ return port
3936+ end
3937+ end
3938+
3939+ grab_ephemeral_port
3940+ end
3941+
3942+ def grab_ephemeral_port
3943+ socket = TCPServer.new('0.0.0.0', 0)
3944+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
3945+ Socket.do_not_reverse_lookup = true
3946+ port = socket.addr[1]
3947+ socket.close
3948+ return port
3949+ end
3950+
3951+ def wait_for_tunnel_start(port)
3952+ 10.times do |n|
3953+ begin
3954+ client = TCPSocket.open('localhost', port)
3955+ display '' if n > 0
3956+ client.close
3957+ return true
3958+ rescue => e
3959+ display "Waiting for local tunnel to become available", false if n == 0
3960+ display '.', false
3961+ sleep 1
3962+ end
3963+ end
3964+ err "Could not connect to local tunnel."
3965+ end
3966+
3967+ def wait_for_tunnel_end
3968+ display "Open another shell to run command-line clients or"
3969+ display "use a UI tool to connect using the displayed information."
3970+ display "Press Ctrl-C to exit..."
3971+ @local_tunnel_thread.join
3972+ end
3973+
3974+ def resolve_symbols(str, info, local_port)
3975+ str.gsub(/\$\{\s*([^\}]+)\s*\}/) do
3976+ case $1
3977+ when "host"
3978+ # TODO: determine proper host
3979+ "localhost"
3980+ when "port"
3981+ local_port
3982+ when "user", "username"
3983+ info["username"]
3984+ else
3985+ info[$1] || ask($1)
3986+ end
3987+ end
3988+ end
3989+
3990+ def start_local_prog(clients, command, info, port)
3991+ client = clients[File.basename(command)]
3992+
3993+ cmdline = "#{command} "
3994+
3995+ case client
3996+ when Hash
3997+ cmdline << resolve_symbols(client["command"], info, port)
3998+ client["environment"].each do |e|
3999+ if e =~ /([^=]+)=(["']?)([^"']*)\2/
4000+ ENV[$1] = resolve_symbols($3, info, port)
4001+ else
4002+ err "Invalid environment variable: #{e}"
4003+ end
4004+ end
4005+ when String
4006+ cmdline << resolve_symbols(client, info, port)
4007+ else
4008+ err "Unknown client info: #{client.inspect}."
4009+ end
4010+
4011+ display "Launching '#{cmdline}'"
4012+ display ''
4013+
4014+ system(cmdline)
4015+ end
4016+
4017+ def push_caldecott(token)
4018+ client.create_app(
4019+ tunnel_appname,
4020+ { :name => tunnel_appname,
4021+ :staging => {:framework => "sinatra"},
4022+ :uris => ["#{tunnel_uniquename}.#{target_base}"],
4023+ :instances => 1,
4024+ :resources => {:memory => 64},
4025+ :env => ["CALDECOTT_AUTH=#{token}"]
4026+ }
4027+ )
4028+
4029+ apps_cmd.send(:upload_app_bits, tunnel_appname, HELPER_APP)
4030+
4031+ invalidate_tunnel_app_info
4032+ end
4033+
4034+ def stop_caldecott
4035+ apps_cmd.stop(tunnel_appname)
4036+
4037+ invalidate_tunnel_app_info
4038+ end
4039+
4040+ def start_caldecott
4041+ apps_cmd.start(tunnel_appname)
4042+
4043+ invalidate_tunnel_app_info
4044+ end
4045+
4046+ private
4047+
4048+ def apps_cmd
4049+ a = Command::Apps.new(@options)
4050+ a.client client
4051+ a
4052+ end
4053+ end
4054+end
4055
4056=== modified file 'lib/cli/usage.rb'
4057--- lib/cli/usage.rb 2011-06-17 13:38:56 +0000
4058+++ lib/cli/usage.rb 2012-06-13 01:37:18 +0000
4059@@ -38,17 +38,18 @@
4060 push [appname] --url Set the url for the application
4061 push [appname] --instances <N> Set the expected number <N> of instances
4062 push [appname] --mem M Set the memory reservation for the application
4063+ push [appname] --runtime RUNTIME Set the runtime to use for the application
4064+ push [appname] --debug [MODE] Push application and start in a debug mode
4065 push [appname] --no-start Do not auto-start the application
4066
4067 Application Operations
4068- start <appname> Start the application
4069+ start <appname> [--debug [MODE]] Start the application
4070 stop <appname> Stop the application
4071- restart <appname> Restart the application
4072+ restart <appname> [--debug [MODE]] Restart the application
4073 delete <appname> Delete the application
4074- rename <appname> <newname> Rename the application
4075
4076 Application Updates
4077- update <appname> [--path] Update the application bits
4078+ update <appname> [--path,--debug [MODE]] Update the application bits
4079 mem <appname> [memsize] Update the memory reservation for an application
4080 map <appname> <url> Register the application to the url
4081 unmap <appname> <url> Unregister the application from the url
4082@@ -76,6 +77,8 @@
4083 bind-service <servicename> <appname> Bind a service to an application
4084 unbind-service <servicename> <appname> Unbind service from the application
4085 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
4086+ tunnel <servicename> [--port] Create a local tunnel to a service
4087+ tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client
4088
4089 Administration
4090 user Display user account information
4091@@ -88,6 +91,15 @@
4092 runtimes Display the supported runtimes of the target system
4093 frameworks Display the recognized frameworks of the target system
4094
4095+ Micro Cloud Foundry
4096+ micro status Display Micro Cloud Foundry VM status
4097+ micro offline Configure Micro Cloud Foundry VM for offline mode
4098+ micro online Configure Micro Cloud Foundry VM for online mode
4099+ [--vmx file] Path to micro.vmx
4100+ [--vmrun executable] Path to vmrun executable
4101+ [--password cleartext] Cleartext password for guest VM vcap user
4102+ [--save] Save cleartext password in ~/.vmc_micro
4103+
4104 Misc
4105 aliases List aliases
4106 alias <alias[=]command> Create an alias for a command
4107
4108=== modified file 'lib/cli/version.rb'
4109--- lib/cli/version.rb 2011-06-17 13:38:56 +0000
4110+++ lib/cli/version.rb 2012-06-13 01:37:18 +0000
4111@@ -2,6 +2,6 @@
4112 module Cli
4113 # This version number is used as the RubyGem release version.
4114 # The internal VMC version number is VMC::VERSION.
4115- VERSION = '0.3.10'
4116+ VERSION = '0.3.18'
4117 end
4118 end
4119
4120=== modified file 'lib/cli/zip_util.rb'
4121--- lib/cli/zip_util.rb 2011-06-17 13:38:56 +0000
4122+++ lib/cli/zip_util.rb 2012-06-13 01:37:18 +0000
4123@@ -10,7 +10,7 @@
4124 class << self
4125
4126 def to_dev_null
4127- if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
4128+ if WINDOWS
4129 'nul'
4130 else
4131 '/dev/null'
4132
4133=== modified file 'lib/vmc/client.rb'
4134--- lib/vmc/client.rb 2011-06-17 13:38:56 +0000
4135+++ lib/vmc/client.rb 2012-06-13 01:37:18 +0000
4136@@ -11,6 +11,7 @@
4137
4138 require 'rubygems'
4139 require 'json/pure'
4140+require 'open-uri'
4141
4142 require File.expand_path('../const', __FILE__)
4143
4144@@ -24,7 +25,7 @@
4145 attr_accessor :trace
4146
4147 # Error codes
4148- VMC_HTTP_ERROR_CODES = [ 400, 403, 404, 500 ]
4149+ VMC_HTTP_ERROR_CODES = [ 400, 500 ]
4150
4151 # Errors
4152 class BadTarget < RuntimeError; end
4153@@ -38,7 +39,7 @@
4154 def initialize(target_url=VMC::DEFAULT_TARGET, auth_token=nil)
4155 target_url = "http://#{target_url}" unless /^https?/ =~ target_url
4156 target_url = target_url.gsub(/\/+$/, '')
4157- @target = target_url
4158+ @target = target_url
4159 @auth_token = auth_token
4160 end
4161
4162@@ -59,7 +60,11 @@
4163 # Global listing of services that are available on the target system
4164 def services_info
4165 check_login_status
4166- json_get(VMC::GLOBAL_SERVICES_PATH)
4167+ json_get(path(VMC::GLOBAL_SERVICES_PATH))
4168+ end
4169+
4170+ def runtimes_info
4171+ json_get(path(VMC::GLOBAL_RUNTIMES_PATH))
4172 end
4173
4174 ######################################################
4175@@ -81,7 +86,7 @@
4176
4177 def update_app(name, manifest)
4178 check_login_status
4179- json_put("#{VMC::APPS_PATH}/#{name}", manifest)
4180+ json_put(path(VMC::APPS_PATH, name), manifest)
4181 end
4182
4183 def upload_app(name, zipfile, resource_manifest=nil)
4184@@ -98,27 +103,29 @@
4185 upload_data[:application] = file
4186 end
4187 upload_data[:resources] = resource_manifest.to_json if resource_manifest
4188- http_post("#{VMC::APPS_PATH}/#{name}/application", upload_data)
4189+ http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
4190+ rescue RestClient::ServerBrokeConnection
4191+ retry
4192 end
4193
4194 def delete_app(name)
4195 check_login_status
4196- http_delete("#{VMC::APPS_PATH}/#{name}")
4197+ http_delete(path(VMC::APPS_PATH, name))
4198 end
4199
4200 def app_info(name)
4201 check_login_status
4202- json_get("#{VMC::APPS_PATH}/#{name}")
4203+ json_get(path(VMC::APPS_PATH, name))
4204 end
4205
4206 def app_update_info(name)
4207 check_login_status
4208- json_get("#{VMC::APPS_PATH}/#{name}/update")
4209+ json_get(path(VMC::APPS_PATH, name, "update"))
4210 end
4211
4212 def app_stats(name)
4213 check_login_status
4214- stats_raw = json_get("#{VMC::APPS_PATH}/#{name}/stats")
4215+ stats_raw = json_get(path(VMC::APPS_PATH, name, "stats"))
4216 stats = []
4217 stats_raw.each_pair do |k, entry|
4218 # Skip entries with no stats
4219@@ -132,20 +139,20 @@
4220
4221 def app_instances(name)
4222 check_login_status
4223- json_get("#{VMC::APPS_PATH}/#{name}/instances")
4224+ json_get(path(VMC::APPS_PATH, name, "instances"))
4225 end
4226
4227 def app_crashes(name)
4228 check_login_status
4229- json_get("#{VMC::APPS_PATH}/#{name}/crashes")
4230+ json_get(path(VMC::APPS_PATH, name, "crashes"))
4231 end
4232
4233 # List the directory or download the actual file indicated by
4234 # the path.
4235- def app_files(name, path, instance=0)
4236+ def app_files(name, path, instance='0')
4237 check_login_status
4238- url = "#{VMC::APPS_PATH}/#{name}/instances/#{instance}/files/#{path}"
4239- url.gsub!('//', '/')
4240+ path = path.gsub('//', '/')
4241+ url = path(VMC::APPS_PATH, name, "instances", instance, "files", path)
4242 _, body, headers = http_get(url)
4243 body
4244 end
4245@@ -185,7 +192,7 @@
4246
4247 raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
4248 service_hash[:name] = name
4249- json_post(VMC::SERVICES_PATH, service_hash)
4250+ json_post(path(VMC::SERVICES_PATH), service_hash)
4251 end
4252
4253 def delete_service(name)
4254@@ -193,7 +200,7 @@
4255 svcs = services || []
4256 names = svcs.collect { |s| s[:name] }
4257 raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
4258- http_delete("#{VMC::SERVICES_PATH}/#{name}")
4259+ http_delete(path(VMC::SERVICES_PATH, name))
4260 end
4261
4262 def bind_service(service, appname)
4263@@ -263,7 +270,7 @@
4264 # Auth token can be retained and used in creating
4265 # new clients, avoiding login.
4266 def login(user, password)
4267- status, body, headers = json_post("#{VMC::USERS_PATH}/#{user}/tokens", {:password => password})
4268+ status, body, headers = json_post(path(VMC::USERS_PATH, user, "tokens"), {:password => password})
4269 response_info = json_parse(body)
4270 if response_info
4271 @user = user
4272@@ -274,10 +281,10 @@
4273 # sets the password for the current logged user
4274 def change_password(new_password)
4275 check_login_status
4276- user_info = json_get("#{VMC::USERS_PATH}/#{@user}")
4277+ user_info = json_get(path(VMC::USERS_PATH, @user))
4278 if user_info
4279 user_info[:password] = new_password
4280- json_put("#{VMC::USERS_PATH}/#{@user}", user_info)
4281+ json_put(path(VMC::USERS_PATH, @user), user_info)
4282 end
4283 end
4284
4285@@ -293,18 +300,34 @@
4286 @proxy = proxy
4287 end
4288
4289+ def users
4290+ check_login_status
4291+ json_get(VMC::USERS_PATH)
4292+ end
4293+
4294 def add_user(user_email, password)
4295 json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
4296 end
4297
4298 def delete_user(user_email)
4299- http_delete("#{VMC::USERS_PATH}/#{user_email}")
4300+ check_login_status
4301+ http_delete(path(VMC::USERS_PATH, user_email))
4302 end
4303
4304 ######################################################
4305
4306+ def self.path(*path)
4307+ path.flatten.collect { |x|
4308+ URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
4309+ }.join("/")
4310+ end
4311+
4312 private
4313
4314+ def path(*args, &blk)
4315+ self.class.path(*args, &blk)
4316+ end
4317+
4318 def json_get(url)
4319 status, body, headers = http_get(url, 'application/json')
4320 json_parse(body)
4321@@ -357,12 +380,12 @@
4322 end
4323
4324 req = {
4325- :method => method, :url => "#{@target}#{path}",
4326- :payload => payload, :headers => headers
4327+ :method => method, :url => "#{@target}/#{path}",
4328+ :payload => payload, :headers => headers, :multipart => true
4329 }
4330 status, body, response_headers = perform_http_request(req)
4331
4332- if VMC_HTTP_ERROR_CODES.include?(status)
4333+ if request_failed?(status)
4334 # FIXME, old cc returned 400 on not found for file access
4335 err = (status == 404 || status == 400) ? NotFound : TargetError
4336 raise err, parse_error_message(status, body)
4337@@ -373,8 +396,13 @@
4338 raise BadTarget, "Cannot access target (%s)" % [ e.message ]
4339 end
4340
4341+ def request_failed?(status)
4342+ VMC_HTTP_ERROR_CODES.detect{|error_code| status >= error_code}
4343+ end
4344+
4345 def perform_http_request(req)
4346- RestClient.proxy = ENV['https_proxy'] || ENV['http_proxy']
4347+ proxy_uri = URI.parse(req[:url]).find_proxy()
4348+ RestClient.proxy = proxy_uri.to_s if proxy_uri
4349
4350 # Setup tracing if needed
4351 unless trace.nil?
4352@@ -388,9 +416,17 @@
4353 puts '>>>'
4354 puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
4355 puts "REQUEST: #{req[:method]} #{req[:url]}"
4356- puts "RESPONSE_HEADERS: #{response.headers}"
4357+ puts "RESPONSE_HEADERS:"
4358+ response.headers.each do |key, value|
4359+ puts " #{key} : #{value}"
4360+ end
4361 puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
4362- puts "RESPONSE: [#{response.code}] #{response.body}"
4363+ puts "RESPONSE: [#{response.code}]"
4364+ begin
4365+ puts JSON.pretty_generate(JSON.parse(response.body))
4366+ rescue
4367+ puts "#{response.body}"
4368+ end
4369 puts '<<<'
4370 end
4371 end
4372
4373=== modified file 'lib/vmc/const.rb'
4374--- lib/vmc/const.rb 2011-06-17 13:38:56 +0000
4375+++ lib/vmc/const.rb 2012-06-13 01:37:18 +0000
4376@@ -5,17 +5,18 @@
4377 VERSION = '0.3.2'
4378
4379 # Targets
4380- DEFAULT_TARGET = 'http://api.cloudfoundry.com'
4381+ DEFAULT_TARGET = 'https://api.cloudfoundry.com'
4382 DEFAULT_LOCAL_TARGET = 'http://api.vcap.me'
4383
4384 # General Paths
4385- INFO_PATH = '/info'
4386- GLOBAL_SERVICES_PATH = '/info/services'
4387- RESOURCES_PATH = '/resources'
4388+ INFO_PATH = 'info'
4389+ GLOBAL_SERVICES_PATH = ['info', 'services']
4390+ GLOBAL_RUNTIMES_PATH = ['info', 'runtimes']
4391+ RESOURCES_PATH = 'resources'
4392
4393 # User specific paths
4394- APPS_PATH = '/apps'
4395- SERVICES_PATH = '/services'
4396- USERS_PATH = '/users'
4397+ APPS_PATH = 'apps'
4398+ SERVICES_PATH = 'services'
4399+ USERS_PATH = 'users'
4400
4401 end
4402
4403=== added directory 'lib/vmc/micro'
4404=== added file 'lib/vmc/micro.rb'
4405--- lib/vmc/micro.rb 1970-01-01 00:00:00 +0000
4406+++ lib/vmc/micro.rb 2012-06-13 01:37:18 +0000
4407@@ -0,0 +1,56 @@
4408+require 'find'
4409+
4410+module VMC::Micro
4411+ def config_file(file)
4412+ File.join(File.dirname(__FILE__), '..', '..', 'config', 'micro', file)
4413+ end
4414+
4415+ def escape_path(path)
4416+ path = File.expand_path(path)
4417+ if RUBY_PLATFORM =~ /mingw|mswin32|cygwin/
4418+ if path.include?(' ')
4419+ return '"' + path + '"'
4420+ else
4421+ return path
4422+ end
4423+ else
4424+ return path.gsub(' ', '\ ')
4425+ end
4426+ end
4427+
4428+ def locate_file(file, directory, search_paths)
4429+ search_paths.each do |path|
4430+ expanded_path = File.expand_path(path)
4431+ if File.exists?(expanded_path)
4432+ Find.find(expanded_path) do |current|
4433+ if File.directory?(current) && current.include?(directory)
4434+ full_path = File.join(current, file)
4435+ return self.escape_path(full_path) if File.exists?(full_path)
4436+ end
4437+ end
4438+ end
4439+ end
4440+
4441+ false
4442+ end
4443+
4444+ def run_command(command, args=nil)
4445+ # TODO switch to using posix-spawn instead
4446+ result = %x{#{command} #{args} 2>&1}
4447+ unless $?.exitstatus == 0
4448+ if block_given?
4449+ yield
4450+ else
4451+ raise "failed to execute #{command} #{args}:\n#{result}"
4452+ end
4453+ else
4454+ result.split(/\n/)
4455+ end
4456+ end
4457+
4458+ module_function :config_file
4459+ module_function :escape_path
4460+ module_function :locate_file
4461+ module_function :run_command
4462+
4463+end
4464
4465=== added directory 'lib/vmc/micro/switcher'
4466=== added file 'lib/vmc/micro/switcher/base.rb'
4467--- lib/vmc/micro/switcher/base.rb 1970-01-01 00:00:00 +0000
4468+++ lib/vmc/micro/switcher/base.rb 2012-06-13 01:37:18 +0000
4469@@ -0,0 +1,97 @@
4470+require 'interact'
4471+
4472+module VMC::Micro::Switcher
4473+ class Base
4474+ include Interactive
4475+
4476+ def initialize(config)
4477+ @config = config
4478+
4479+ @vmrun = VMC::Micro::VMrun.new(config)
4480+ unless @vmrun.running?
4481+ if ask("Micro Cloud Foundry VM is not running. Do you want to start it?", :choices => ['y', 'n']) == 'y'
4482+ display "Starting Micro Cloud Foundry VM: ", false
4483+ @vmrun.start
4484+ say "done".green
4485+ else
4486+ err "Micro Cloud Foundry VM needs to be running."
4487+ end
4488+ end
4489+
4490+ err "Micro Cloud Foundry VM initial setup needs to be completed before using 'vmc micro'" unless @vmrun.ready?
4491+ end
4492+
4493+ def offline
4494+ unless @vmrun.offline?
4495+ # save online connection type so we can restore it later
4496+ @config['online_connection_type'] = @vmrun.connection_type
4497+
4498+ if (@config['online_connection_type'] != 'nat')
4499+ if ask("Reconfigure Micro Cloud Foundry VM network to nat mode and reboot?", :choices => ['y', 'n']) == 'y'
4500+ display "Rebooting Micro Cloud Foundry VM: ", false
4501+ @vmrun.connection_type = 'nat'
4502+ @vmrun.reset
4503+ say "done".green
4504+ else
4505+ err "Aborted"
4506+ end
4507+ end
4508+
4509+ display "Setting Micro Cloud Foundry VM to offline mode: ", false
4510+ @vmrun.offline!
4511+ say "done".green
4512+ display "Setting host DNS server: ", false
4513+
4514+ @config['domain'] = @vmrun.domain
4515+ @config['ip'] = @vmrun.ip
4516+ set_nameserver(@config['domain'], @config['ip'])
4517+ say "done".green
4518+ else
4519+ say "Micro Cloud Foundry VM already in offline mode".yellow
4520+ end
4521+ end
4522+
4523+ def online
4524+ if @vmrun.offline?
4525+ current_connection_type = @vmrun.connection_type
4526+ @config['online_connection_type'] ||= current_connection_type
4527+
4528+ if (@config['online_connection_type'] != current_connection_type)
4529+ # TODO handle missing connection type in saved config
4530+ question = "Reconfigure Micro Cloud Foundry VM network to #{@config['online_connection_type']} mode and reboot?"
4531+ if ask(question, :choices => ['y', 'n']) == 'y'
4532+ display "Rebooting Micro Cloud Foundry VM: ", false
4533+ @vmrun.connection_type = @config['online_connection_type']
4534+ @vmrun.reset
4535+ say "done".green
4536+ else
4537+ err "Aborted"
4538+ end
4539+ end
4540+
4541+ display "Unsetting host DNS server: ", false
4542+ # TODO handle missing domain and ip in saved config (look at the VM)
4543+ @config['domain'] ||= @vmrun.domain
4544+ @config['ip'] ||= @vmrun.ip
4545+ unset_nameserver(@config['domain'], @config['ip'])
4546+ say "done".green
4547+
4548+ display "Setting Micro Cloud Foundry VM to online mode: ", false
4549+ @vmrun.online!
4550+ say "done".green
4551+ else
4552+ say "Micro Cloud Foundry already in online mode".yellow
4553+ end
4554+ end
4555+
4556+ def status
4557+ mode = @vmrun.offline? ? 'offline' : 'online'
4558+ say "Micro Cloud Foundry VM currently in #{mode.green} mode"
4559+ # should the VMX path be unescaped?
4560+ say "VMX Path: #{@vmrun.vmx}"
4561+ say "Domain: #{@vmrun.domain.green}"
4562+ say "IP Address: #{@vmrun.ip.green}"
4563+ end
4564+ end
4565+
4566+end
4567
4568=== added file 'lib/vmc/micro/switcher/darwin.rb'
4569--- lib/vmc/micro/switcher/darwin.rb 1970-01-01 00:00:00 +0000
4570+++ lib/vmc/micro/switcher/darwin.rb 2012-06-13 01:37:18 +0000
4571@@ -0,0 +1,19 @@
4572+module VMC::Micro::Switcher
4573+
4574+ class Darwin < Base
4575+ def adminrun(command)
4576+ VMC::Micro.run_command("osascript", "-e 'do shell script \"#{command}\" with administrator privileges'")
4577+ end
4578+
4579+ def set_nameserver(domain, ip)
4580+ File.open("/tmp/#{domain}", 'w') { |file| file.write("nameserver #{ip}") }
4581+ adminrun("mkdir -p /etc/resolver;mv /tmp/#{domain} /etc/resolver/")
4582+ end
4583+
4584+ def unset_nameserver(domain, ip)
4585+ err "domain missing" unless domain
4586+ adminrun("rm -f /etc/resolver/#{domain}")
4587+ end
4588+ end
4589+
4590+end
4591
4592=== added file 'lib/vmc/micro/switcher/dummy.rb'
4593--- lib/vmc/micro/switcher/dummy.rb 1970-01-01 00:00:00 +0000
4594+++ lib/vmc/micro/switcher/dummy.rb 2012-06-13 01:37:18 +0000
4595@@ -0,0 +1,15 @@
4596+# only used for testing
4597+module VMC::Micro::Switcher
4598+
4599+ class Dummy < Base
4600+ def adminrun(command)
4601+ end
4602+
4603+ def set_nameserver(domain, ip)
4604+ end
4605+
4606+ def unset_nameserver(domain, ip)
4607+ end
4608+ end
4609+
4610+end
4611
4612=== added file 'lib/vmc/micro/switcher/linux.rb'
4613--- lib/vmc/micro/switcher/linux.rb 1970-01-01 00:00:00 +0000
4614+++ lib/vmc/micro/switcher/linux.rb 2012-06-13 01:37:18 +0000
4615@@ -0,0 +1,16 @@
4616+module VMC::Micro::Switcher
4617+
4618+ class Linux < Base
4619+ def set_nameserver(domain, ip)
4620+ VMC::Micro.run_command("sudo", "sed -i'.backup' '1 i nameserver #{ip}' /etc/resolv.conf")
4621+ # lock resolv.conf so Network Manager doesn't clear out the file when offline
4622+ VMC::Micro.run_command("sudo", "chattr +i /etc/resolv.conf")
4623+ end
4624+
4625+ def unset_nameserver(domain, ip)
4626+ VMC::Micro.run_command("sudo", "chattr -i /etc/resolv.conf")
4627+ VMC::Micro.run_command("sudo", "sed -i'.backup' '/#{ip}/d' /etc/resolv.conf")
4628+ end
4629+ end
4630+
4631+end
4632
4633=== added file 'lib/vmc/micro/switcher/windows.rb'
4634--- lib/vmc/micro/switcher/windows.rb 1970-01-01 00:00:00 +0000
4635+++ lib/vmc/micro/switcher/windows.rb 2012-06-13 01:37:18 +0000
4636@@ -0,0 +1,31 @@
4637+module VMC::Micro::Switcher
4638+
4639+ class Windows < Base
4640+ def version?
4641+ VMC::Micro.run_command("cmd", "/c ver").to_s.scan(/\d+\.\d+/).first.to_f
4642+ end
4643+
4644+ def adminrun(command, args=nil)
4645+ if version? > 5.2
4646+ require 'win32ole'
4647+ shell = WIN32OLE.new("Shell.Application")
4648+ shell.ShellExecute(command, args, nil, "runas", 0)
4649+ else
4650+ # on older version this will try to run the command, and if you don't have
4651+ # admin privilges it will tell you so and exit
4652+ VMC::Micro.run_command(command, args)
4653+ end
4654+ end
4655+
4656+ # TODO better method to figure out the interface name is to get the NAT ip and find the
4657+ # interface with the correct subnet
4658+ def set_nameserver(domain, ip)
4659+ adminrun("netsh", "interface ip set dns \"VMware Network Adapter VMnet8\" static #{ip}")
4660+ end
4661+
4662+ def unset_nameserver(domain, ip)
4663+ adminrun("netsh", "interface ip set dns \"VMware Network Adapter VMnet8\" static none")
4664+ end
4665+ end
4666+
4667+end
4668
4669=== added file 'lib/vmc/micro/vmrun.rb'
4670--- lib/vmc/micro/vmrun.rb 1970-01-01 00:00:00 +0000
4671+++ lib/vmc/micro/vmrun.rb 2012-06-13 01:37:18 +0000
4672@@ -0,0 +1,158 @@
4673+module VMC::Micro
4674+ class VMrun
4675+ attr_reader :vmx, :vmrun
4676+
4677+ def initialize(config)
4678+ @platform = config['platform']
4679+ @user = 'root' # must use root as we muck around with system settings
4680+ @password = config['password']
4681+ @vmrun = config['vmrun']
4682+ @vmx = config['vmx']
4683+
4684+ # TODO honor TMPDIR
4685+ if @platform == :windows
4686+ @temp_dir = ENV['temp']
4687+ else
4688+ @temp_dir = '/tmp'
4689+ end
4690+ end
4691+
4692+ def connection_type
4693+ read_variable('ethernet0.connectionType')
4694+ end
4695+
4696+ def connection_type=(type)
4697+ write_variable("ethernet0.connectionType", type)
4698+ end
4699+
4700+ def nat?
4701+ connection_type == "nat"
4702+ end
4703+
4704+ def bridged?
4705+ connection_type == "bridged"
4706+ end
4707+
4708+ def domain
4709+ # switch to Dir.mktmpdir
4710+ state_config = VMC::Micro.escape_path(File.join(@temp_dir, 'state.yml'))
4711+ run('CopyFileFromGuestToHost', "/var/vcap/bosh/state.yml #{state_config}")
4712+ bosh_config = YAML.load_file(state_config)
4713+ bosh_config['properties']['domain']
4714+ end
4715+
4716+ def ip
4717+ # switch to Dir.mktmpdir
4718+ path = VMC::Micro.escape_path(VMC::Micro.config_file('refresh_ip.rb'))
4719+ ip_file = VMC::Micro.escape_path(File.join(@temp_dir, 'ip.txt'))
4720+ run('CopyFileFromHostToGuest', "#{path} /tmp/refresh_ip.rb")
4721+ run('runProgramInGuest', '/tmp/refresh_ip.rb')
4722+ run('CopyFileFromGuestToHost', "/tmp/ip.txt #{ip_file}")
4723+ File.open(ip_file, 'r') { |file| file.read }
4724+ end
4725+
4726+ def list
4727+ vms = run("list")
4728+ vms.delete_if { |line| line =~ /^Total/ }
4729+ vms.map { |line| VMC::Micro.escape_path(File.expand_path(line)) }
4730+ end
4731+
4732+ def offline?
4733+ command = "-gu #{@user} -gp #{@password} runProgramInGuest"
4734+ args = '/usr/bin/test -e /var/vcap/micro/offline'
4735+ # why not use run_command?
4736+ result = %x{#{@vmrun} #{command} #{@vmx} #{args}}
4737+
4738+ if result.include?('Guest program exited with non-zero exit code: 1')
4739+ return false
4740+ elsif $?.exitstatus == 0
4741+ return true
4742+ else
4743+ raise "failed to execute vmrun:\n#{result}"
4744+ end
4745+ end
4746+
4747+ def offline!
4748+ path = VMC::Micro.escape_path(VMC::Micro.config_file('offline.conf'))
4749+ run('CopyFileFromHostToGuest', "#{path} /etc/dnsmasq.d/offline.conf")
4750+ run('runProgramInGuest', '/usr/bin/touch /var/vcap/micro/offline')
4751+ restart_dnsmasq
4752+ end
4753+
4754+ def online!
4755+ run('runProgramInGuest', '/bin/rm -f /etc/dnsmasq.d/offline.conf')
4756+ run('runProgramInGuest', '/bin/rm -f /var/vcap/micro/offline')
4757+ restart_dnsmasq
4758+ end
4759+
4760+ # check to see if the micro cloud has been configured
4761+ # uses default password to check
4762+ def ready?
4763+ command = "-gu root -gp 'ca$hc0w' runProgramInGuest"
4764+ args = '/usr/bin/test -e /var/vcap/micro/micro.json'
4765+ result = %x{#{@vmrun} #{command} #{@vmx} #{args}}
4766+
4767+ if result.include?('Invalid user name or password for the guest OS') || $?.exitstatus == 0
4768+ return true
4769+ elsif $?.exitstatus == 1
4770+ return false
4771+ else
4772+ raise "failed to execute vmrun:\n#{result}"
4773+ end
4774+ end
4775+
4776+ def read_variable(var)
4777+ # TODO deal with non-ok return
4778+ run("readVariable", "runtimeConfig #{var}").first
4779+ end
4780+
4781+ def write_variable(var, value)
4782+ run('writeVariable', "runtimeConfig #{var} #{value}")
4783+ end
4784+
4785+ def reset
4786+ run('reset', 'soft')
4787+ end
4788+
4789+ def restart_dnsmasq
4790+ # restart command doesn't always work, start and stop seems to be more reliable
4791+ run('runProgramInGuest', '/etc/init.d/dnsmasq stop')
4792+ run('runProgramInGuest', '/etc/init.d/dnsmasq start')
4793+ end
4794+
4795+ def run(command, args=nil)
4796+ if command.include?('Guest')
4797+ command = "-gu #{@user} -gp #{@password} #{command}"
4798+ end
4799+ VMC::Micro.run_command(@vmrun, "#{command} #{@vmx} #{args}")
4800+ end
4801+
4802+ def running?
4803+ vms = list
4804+ if @platform == :windows
4805+ vms.map! { |x| x.downcase }
4806+ vms.include?(@vmx.downcase)
4807+ else
4808+ vms.include?(@vmx)
4809+ end
4810+ end
4811+
4812+ def start
4813+ run('start') unless running?
4814+ end
4815+
4816+ def stop
4817+ run('stop') if running?
4818+ end
4819+
4820+ def self.locate(platform)
4821+ paths = YAML.load_file(VMC::Micro.config_file('paths.yml'))
4822+ vmrun_paths = paths[platform.to_s]['vmrun']
4823+ vmrun_exe = @platform == :windows ? 'vmrun.exe' : 'vmrun'
4824+ vmrun = VMC::Micro.locate_file(vmrun_exe, "VMware", vmrun_paths)
4825+ err "Unable to locate vmrun, please supply --vmrun option" unless vmrun
4826+ vmrun
4827+ end
4828+ end
4829+
4830+end
4831
4832=== modified file 'metadata.yml'
4833--- metadata.yml 2011-06-17 13:38:56 +0000
4834+++ metadata.yml 2012-06-13 01:37:18 +0000
4835@@ -1,13 +1,13 @@
4836 --- !ruby/object:Gem::Specification
4837 name: vmc
4838 version: !ruby/object:Gem::Version
4839- hash: 7
4840+ hash: 55
4841 prerelease:
4842 segments:
4843 - 0
4844 - 3
4845- - 10
4846- version: 0.3.10
4847+ - 18
4848+ version: 0.3.18
4849 platform: ruby
4850 authors:
4851 - VMware
4852@@ -15,8 +15,7 @@
4853 bindir: bin
4854 cert_chain: []
4855
4856-date: 2011-04-11 00:00:00 -07:00
4857-default_executable:
4858+date: 2012-05-30 00:00:00 Z
4859 dependencies:
4860 - !ruby/object:Gem::Dependency
4861 name: json_pure
4862@@ -24,7 +23,7 @@
4863 requirement: &id001 !ruby/object:Gem::Requirement
4864 none: false
4865 requirements:
4866- - - ~>
4867+ - - ">="
4868 - !ruby/object:Gem::Version
4869 hash: 1
4870 segments:
4871@@ -32,31 +31,39 @@
4872 - 5
4873 - 1
4874 version: 1.5.1
4875+ - - <
4876+ - !ruby/object:Gem::Version
4877+ hash: 11
4878+ segments:
4879+ - 1
4880+ - 7
4881+ - 0
4882+ version: 1.7.0
4883 type: :runtime
4884 version_requirements: *id001
4885 - !ruby/object:Gem::Dependency
4886- name: rubyzip2
4887+ name: rubyzip
4888 prerelease: false
4889 requirement: &id002 !ruby/object:Gem::Requirement
4890 none: false
4891 requirements:
4892 - - ~>
4893 - !ruby/object:Gem::Version
4894- hash: 13
4895+ hash: 51
4896 segments:
4897- - 2
4898 - 0
4899- - 1
4900- version: 2.0.1
4901+ - 9
4902+ - 4
4903+ version: 0.9.4
4904 type: :runtime
4905 version_requirements: *id002
4906 - !ruby/object:Gem::Dependency
4907- name: highline
4908+ name: rest-client
4909 prerelease: false
4910 requirement: &id003 !ruby/object:Gem::Requirement
4911 none: false
4912 requirements:
4913- - - ~>
4914+ - - ">="
4915 - !ruby/object:Gem::Version
4916 hash: 13
4917 segments:
4918@@ -64,52 +71,100 @@
4919 - 6
4920 - 1
4921 version: 1.6.1
4922+ - - <
4923+ - !ruby/object:Gem::Version
4924+ hash: 11
4925+ segments:
4926+ - 1
4927+ - 7
4928+ - 0
4929+ version: 1.7.0
4930 type: :runtime
4931 version_requirements: *id003
4932 - !ruby/object:Gem::Dependency
4933- name: rest-client
4934+ name: terminal-table
4935 prerelease: false
4936 requirement: &id004 !ruby/object:Gem::Requirement
4937 none: false
4938 requirements:
4939- - - ">="
4940- - !ruby/object:Gem::Version
4941- hash: 13
4942- segments:
4943- - 1
4944- - 6
4945- - 1
4946- version: 1.6.1
4947- - - <
4948- - !ruby/object:Gem::Version
4949- hash: 11
4950- segments:
4951- - 1
4952- - 7
4953- - 0
4954- version: 1.7.0
4955+ - - ~>
4956+ - !ruby/object:Gem::Version
4957+ hash: 3
4958+ segments:
4959+ - 1
4960+ - 4
4961+ - 2
4962+ version: 1.4.2
4963 type: :runtime
4964 version_requirements: *id004
4965 - !ruby/object:Gem::Dependency
4966- name: terminal-table
4967+ name: interact
4968 prerelease: false
4969 requirement: &id005 !ruby/object:Gem::Requirement
4970 none: false
4971 requirements:
4972 - - ~>
4973 - !ruby/object:Gem::Version
4974- hash: 3
4975+ hash: 15
4976 segments:
4977- - 1
4978+ - 0
4979 - 4
4980- - 2
4981- version: 1.4.2
4982+ - 0
4983+ version: 0.4.0
4984 type: :runtime
4985 version_requirements: *id005
4986 - !ruby/object:Gem::Dependency
4987+ name: addressable
4988+ prerelease: false
4989+ requirement: &id006 !ruby/object:Gem::Requirement
4990+ none: false
4991+ requirements:
4992+ - - ~>
4993+ - !ruby/object:Gem::Version
4994+ hash: 11
4995+ segments:
4996+ - 2
4997+ - 2
4998+ - 6
4999+ version: 2.2.6
5000+ type: :runtime
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: