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

Proposed by Logan Rosen
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
Ubuntu branches Pending
Review via email: mp+109967@code.launchpad.net
To post a comment you must log in.
Revision history for this message
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
=== removed file '.pc/applied-patches'
--- .pc/applied-patches 2011-06-17 13:38:56 +0000
+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1modify-include-path
20
=== removed directory '.pc/modify-include-path'
=== removed directory '.pc/modify-include-path/bin'
=== removed file '.pc/modify-include-path/bin/vmc'
--- .pc/modify-include-path/bin/vmc 2011-06-17 13:38:56 +0000
+++ .pc/modify-include-path/bin/vmc 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1#!/usr/bin/env ruby
2
3require File.expand_path('../../lib/cli', __FILE__)
4
5VMC::Cli::Runner.run(ARGV.dup)
6
70
=== modified file 'README.md'
--- README.md 2011-06-17 13:38:56 +0000
+++ README.md 2012-06-13 01:37:18 +0000
@@ -31,7 +31,6 @@
31 stop <appname> Stop the application31 stop <appname> Stop the application
32 restart <appname> Restart the application32 restart <appname> Restart the application
33 delete <appname> Delete the application33 delete <appname> Delete the application
34 rename <appname> <newname> Rename the application
3534
36 Application Updates35 Application Updates
37 update <appname> [--path] Update the application bits36 update <appname> [--path] Update the application bits
@@ -62,6 +61,8 @@
62 bind-service <servicename> <appname> Bind a service to an application61 bind-service <servicename> <appname> Bind a service to an application
63 unbind-service <servicename> <appname> Unbind service from the application62 unbind-service <servicename> <appname> Unbind service from the application
64 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>63 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
64 tunnel <servicename> [--port] Create a local tunnel to a service
65 tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client
6566
66 Administration67 Administration
67 user Display user account information68 user Display user account information
@@ -74,6 +75,15 @@
74 runtimes Display the supported runtimes of the target system75 runtimes Display the supported runtimes of the target system
75 frameworks Display the recognized frameworks of the target system76 frameworks Display the recognized frameworks of the target system
7677
78 Micro Cloud Foundry
79 micro status Display Micro Cloud Foundry VM status
80 mciro offline Configure Micro Cloud Foundry VM for offline mode
81 micro online Configure Micro Cloud Foundry VM for online mode
82 [--vmx file] Path to micro.vmx
83 [--vmrun executable] Path to vmrun executable
84 [--password cleartext] Cleartext password for guest VM vcap user
85 [--save] Save cleartext password in ~/.vmc_micro
86
77 Misc87 Misc
78 aliases List aliases88 aliases List aliases
79 alias <alias[=]command> Create an alias for a command89 alias <alias[=]command> Create an alias for a command
@@ -86,7 +96,7 @@
8696
87## Simple Story (for Ruby apps)97## Simple Story (for Ruby apps)
8898
89 vmc target api.vcloudlabs.com99 vmc target api.cloudfoundry.com
90 vmc login100 vmc login
91 bundle package101 bundle package
92 vmc push102 vmc push
93103
=== modified file 'Rakefile'
--- Rakefile 2011-06-17 13:38:56 +0000
+++ Rakefile 2012-06-13 01:37:18 +0000
@@ -2,8 +2,7 @@
2require 'spec/rake/spectask'2require 'spec/rake/spectask'
33
4desc "Run specs"4desc "Run specs"
5task :spec do5task :spec => :build do
6 sh('bundle install')
7 Spec::Rake::SpecTask.new('spec') do |t|6 Spec::Rake::SpecTask.new('spec') do |t|
8 t.spec_opts = %w(-fs -c)7 t.spec_opts = %w(-fs -c)
9 t.spec_files = FileList['spec/**/*_spec.rb']8 t.spec_files = FileList['spec/**/*_spec.rb']
@@ -15,3 +14,88 @@
15desc "Synonym for spec"14desc "Synonym for spec"
16task :tests => :spec15task :tests => :spec
17task :default => :spec16task :default => :spec
17
18def tests_path
19 if @tests_path == nil
20 @tests_path = File.join(Dir.pwd, "spec/assets/tests")
21 end
22 @tests_path
23end
24TESTS_PATH = tests_path
25
26BUILD_ARTIFACT = File.join(Dir.pwd, "spec/assets/.build")
27
28TESTS_TO_BUILD = ["#{TESTS_PATH}/java_web/java_tiny_app",
29# "#{TESTS_PATH}/grails/guestbook",
30 "#{TESTS_PATH}/lift/hello_lift",
31 "#{TESTS_PATH}/spring/roo-guestbook",
32 "#{TESTS_PATH}/spring/spring-osgi-hello",
33 "#{TESTS_PATH}/standalone/java_app",
34 "#{TESTS_PATH}/standalone/python_app"
35 ]
36
37desc "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]'"
38task :build, [:force] do |t, args|
39 sh('bundle install')
40 sh('git submodule update --init')
41 puts "\nBuilding tests"
42 if build_required? args.force
43 ENV['MAVEN_OPTS']="-XX:MaxPermSize=256M"
44 TESTS_TO_BUILD.each do |test|
45 puts "\tBuilding '#{test}'"
46 Dir.chdir test do
47 sh('mvn package -DskipTests') do |success, exit_code|
48 unless success
49 clear_build_artifact
50 do_mvn_clean('-q')
51 fail "\tFailed to build #{test} - aborting build"
52 end
53 end
54 end
55 puts "\tCompleted building '#{test}'"
56 end
57 save_git_hash
58 else
59 puts "Built artifacts in sync with test assets - no build required"
60 end
61end
62
63desc "Clean the build artifacts"
64task :clean do
65 puts "\nCleaning tests"
66 clear_build_artifact
67 TESTS_TO_BUILD.each do |test|
68 puts "\tCleaning '#{test}'"
69 Dir.chdir test do
70 do_mvn_clean
71 end
72 puts "\tCompleted cleaning '#{test}'"
73 end
74end
75
76def build_required? (force_build=nil)
77 if File.exists?(BUILD_ARTIFACT) == false or (force_build and force_build == "--force")
78 return true
79 end
80 Dir.chdir(tests_path) do
81 saved_git_hash = IO.readlines(BUILD_ARTIFACT)[0].split[0]
82 git_hash = `git rev-parse --short=8 --verify HEAD`
83 saved_git_hash.to_s.strip != git_hash.to_s.strip
84 end
85end
86
87def save_git_hash
88 Dir.chdir(tests_path) do
89 git_hash = `git rev-parse --short=8 --verify HEAD`
90 File.open(BUILD_ARTIFACT, 'w') {|f| f.puts("#{git_hash}")}
91 end
92end
93
94def clear_build_artifact
95 puts "\tClearing build artifact #{BUILD_ARTIFACT}"
96 File.unlink BUILD_ARTIFACT if File.exists? BUILD_ARTIFACT
97end
98
99def do_mvn_clean options=nil
100 sh("mvn clean #{options}")
101end
18102
=== modified file 'bin/vmc'
--- bin/vmc 2011-06-17 13:38:56 +0000
+++ bin/vmc 2012-06-13 01:37:18 +0000
@@ -1,6 +1,6 @@
1#!/usr/bin/ruby1.9.11#!/usr/bin/env ruby
22
3require File.expand_path('/usr/lib/ruby/vendor_ruby/cli', __FILE__)3require File.expand_path('../../lib/cli', __FILE__)
44
5VMC::Cli::Runner.run(ARGV.dup)5VMC::Cli::Runner.run(ARGV.dup)
66
77
=== added directory 'caldecott_helper'
=== added file 'caldecott_helper/Gemfile'
--- caldecott_helper/Gemfile 1970-01-01 00:00:00 +0000
+++ caldecott_helper/Gemfile 2012-06-13 01:37:18 +0000
@@ -0,0 +1,10 @@
1source "http://rubygems.org"
2
3gem 'rack', '~> 1.2.0'
4gem 'caldecott', '= 0.0.3'
5gem 'bundler'
6gem 'em-websocket'
7gem 'async_sinatra'
8gem 'thin'
9gem 'json'
10gem 'uuidtools'
011
=== added file 'caldecott_helper/Gemfile.lock'
--- caldecott_helper/Gemfile.lock 1970-01-01 00:00:00 +0000
+++ caldecott_helper/Gemfile.lock 2012-06-13 01:37:18 +0000
@@ -0,0 +1,48 @@
1GEM
2 remote: http://rubygems.org/
3 specs:
4 addressable (2.2.6)
5 async_sinatra (0.5.0)
6 rack (>= 1.2.1)
7 sinatra (>= 1.0)
8 caldecott (0.0.3)
9 addressable (= 2.2.6)
10 async_sinatra (= 0.5.0)
11 em-http-request (= 0.3.0)
12 em-websocket (= 0.3.1)
13 json (= 1.6.1)
14 uuidtools (= 2.1.2)
15 daemons (1.1.4)
16 em-http-request (0.3.0)
17 addressable (>= 2.0.0)
18 escape_utils
19 eventmachine (>= 0.12.9)
20 em-websocket (0.3.1)
21 addressable (>= 2.1.1)
22 eventmachine (>= 0.12.9)
23 escape_utils (0.2.4)
24 eventmachine (0.12.10)
25 json (1.6.1)
26 rack (1.2.4)
27 sinatra (1.2.7)
28 rack (~> 1.1)
29 tilt (>= 1.2.2, < 2.0)
30 thin (1.2.11)
31 daemons (>= 1.0.9)
32 eventmachine (>= 0.12.6)
33 rack (>= 1.0.0)
34 tilt (1.3.3)
35 uuidtools (2.1.2)
36
37PLATFORMS
38 ruby
39
40DEPENDENCIES
41 async_sinatra
42 bundler
43 caldecott (= 0.0.3)
44 em-websocket
45 json
46 rack (~> 1.2.0)
47 thin
48 uuidtools
049
=== added file 'caldecott_helper/server.rb'
--- caldecott_helper/server.rb 1970-01-01 00:00:00 +0000
+++ caldecott_helper/server.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,43 @@
1#!/usr/bin/env ruby
2# Copyright (c) 2009-2011 VMware, Inc.
3$:.unshift(File.dirname(__FILE__) + '/lib')
4
5require 'rubygems'
6require 'bundler/setup'
7
8require 'caldecott'
9require 'sinatra'
10require 'json'
11require 'eventmachine'
12
13port = ENV['VMC_APP_PORT']
14port ||= 8081
15
16# add vcap specific stuff to Caldecott
17class VcapHttpTunnel < Caldecott::Server::HttpTunnel
18 get '/info' do
19 { "version" => '0.0.4' }.to_json
20 end
21
22 def self.get_tunnels
23 super
24 end
25
26 get '/services' do
27 services_env = ENV['VMC_SERVICES']
28 return "no services env" if services_env.nil? or services_env.empty?
29 services_env
30 end
31
32 get '/services/:service' do |service_name|
33 services_env = ENV['VMC_SERVICES']
34 not_found if services_env.nil?
35
36 services = JSON.parse(services_env)
37 service = services.find { |s| s["name"] == service_name }
38 not_found if service.nil?
39 service["options"].to_json
40 end
41end
42
43VcapHttpTunnel.run!(:port => port, :auth_token => ENV["CALDECOTT_AUTH"])
044
=== added directory 'config'
=== added file 'config/clients.yml'
--- config/clients.yml 1970-01-01 00:00:00 +0000
+++ config/clients.yml 2012-06-13 01:37:18 +0000
@@ -0,0 +1,17 @@
1redis:
2 redis-cli: -h ${host} -p ${port} -a ${password}
3
4mysql:
5 mysql: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name}
6 mysqldump: --protocol=TCP --host=${host} --port=${port} --user=${user} --password=${password} ${name} > ${Output file}
7
8mongodb:
9 mongo: --host ${host} --port ${port} -u ${user} -p ${password} ${name}
10 mongodump: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name}
11 mongorestore: --host ${host} --port ${port} -u ${user} -p ${password} --db ${name} ${Directory or filename to restore from}
12
13postgresql:
14 psql:
15 command: -h ${host} -p ${port} -d ${name} -U ${user} -w
16 environment:
17 - PGPASSWORD='${password}'
018
=== added directory 'config/micro'
=== added file 'config/micro/offline.conf'
--- config/micro/offline.conf 1970-01-01 00:00:00 +0000
+++ config/micro/offline.conf 2012-06-13 01:37:18 +0000
@@ -0,0 +1,2 @@
1no-resolv
2log-queries
03
=== added file 'config/micro/paths.yml'
--- config/micro/paths.yml 1970-01-01 00:00:00 +0000
+++ config/micro/paths.yml 2012-06-13 01:37:18 +0000
@@ -0,0 +1,22 @@
1darwin:
2 vmrun:
3 - "/Applications/VMware Fusion.app/Contents/Library/"
4 - "/Applications/Fusion.app/Contents/Library/"
5 vmx:
6 - "~/Documents/Virtual Machines.localized/"
7 - "~/Documents/Virtual Machines/"
8 - "~/Desktop/"
9
10linux:
11 vmrun:
12 - "/usr/bin/"
13 vmx:
14 - "~/"
15
16windows:
17 vmrun:
18 - "c:\\Program Files (x86)\\"
19 - "c:\\Program Files\\"
20 vmx:
21 - "~\\Documents\\"
22 - "~\\Desktop\\"
023
=== added file 'config/micro/refresh_ip.rb'
--- config/micro/refresh_ip.rb 1970-01-01 00:00:00 +0000
+++ config/micro/refresh_ip.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,20 @@
1#!/var/vcap/bosh/bin/ruby
2require 'socket'
3
4A_ROOT_SERVER = '198.41.0.4'
5
6begin
7retries ||= 0
8route ||= A_ROOT_SERVER
9orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
10ip_address = UDPSocket.open {|s| s.connect(route, 1); s.addr.last }
11rescue Errno::ENETUNREACH
12 # happens on boot when dhcp hasn't completed when we get here
13 sleep 3
14 retries += 1
15 retry if retries < 10
16ensure
17 Socket.do_not_reverse_lookup = orig
18end
19
20File.open("/tmp/ip.txt", 'w') { |file| file.write(ip_address) }
021
=== modified file 'debian/changelog'
--- debian/changelog 2011-08-09 14:45:42 +0000
+++ debian/changelog 2012-06-13 01:37:18 +0000
@@ -1,3 +1,9 @@
1ruby-vmc (0.3.18-0ubuntu1) UNRELEASED; urgency=low
2
3 * New upstream release (LP: #998111).
4
5 -- Logan Rosen <logatronico@gmail.com> Tue, 12 Jun 2012 21:31:33 -0400
6
1ruby-vmc (0.3.10-0ubuntu10) oneiric; urgency=low7ruby-vmc (0.3.10-0ubuntu10) oneiric; urgency=low
28
3 [ Marc Cluet ]9 [ Marc Cluet ]
410
=== modified file 'lib/cli.rb'
--- lib/cli.rb 2011-06-17 13:38:56 +0000
+++ lib/cli.rb 2012-06-13 01:37:18 +0000
@@ -1,25 +1,42 @@
1require "rbconfig"
12
2ROOT = File.expand_path(File.dirname(__FILE__))3ROOT = File.expand_path(File.dirname(__FILE__))
4WINDOWS = !!(RbConfig::CONFIG['host_os'] =~ /mingw|mswin32|cygwin/)
35
4module VMC6module VMC
5
6 autoload :Client, "#{ROOT}/vmc/client"7 autoload :Client, "#{ROOT}/vmc/client"
8 autoload :Micro, "#{ROOT}/vmc/micro"
9
10 module Micro
11 module Switcher
12 autoload :Base, "#{ROOT}/vmc/micro/switcher/base"
13 autoload :Darwin, "#{ROOT}/vmc/micro/switcher/darwin"
14 autoload :Dummy, "#{ROOT}/vmc/micro/switcher/dummy"
15 autoload :Linux, "#{ROOT}/vmc/micro/switcher/linux"
16 autoload :Windows, "#{ROOT}/vmc/micro/switcher/windows"
17 end
18 autoload :VMrun, "#{ROOT}/vmc/micro/vmrun"
19 end
720
8 module Cli21 module Cli
9
10 autoload :Config, "#{ROOT}/cli/config"22 autoload :Config, "#{ROOT}/cli/config"
11 autoload :Framework, "#{ROOT}/cli/frameworks"23 autoload :Framework, "#{ROOT}/cli/frameworks"
12 autoload :Runner, "#{ROOT}/cli/runner"24 autoload :Runner, "#{ROOT}/cli/runner"
13 autoload :ZipUtil, "#{ROOT}/cli/zip_util"25 autoload :ZipUtil, "#{ROOT}/cli/zip_util"
14 autoload :ServicesHelper, "#{ROOT}/cli/services_helper"26 autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
27 autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
28 autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
29 autoload :ConsoleHelper, "#{ROOT}/cli/console_helper"
1530
16 module Command31 module Command
17 autoload :Base, "#{ROOT}/cli/commands/base"32 autoload :Base, "#{ROOT}/cli/commands/base"
18 autoload :Admin, "#{ROOT}/cli/commands/admin"33 autoload :Admin, "#{ROOT}/cli/commands/admin"
19 autoload :Apps, "#{ROOT}/cli/commands/apps"34 autoload :Apps, "#{ROOT}/cli/commands/apps"
35 autoload :Micro, "#{ROOT}/cli/commands/micro"
20 autoload :Misc, "#{ROOT}/cli/commands/misc"36 autoload :Misc, "#{ROOT}/cli/commands/misc"
21 autoload :Services, "#{ROOT}/cli/commands/services"37 autoload :Services, "#{ROOT}/cli/commands/services"
22 autoload :User, "#{ROOT}/cli/commands/user"38 autoload :User, "#{ROOT}/cli/commands/user"
39 autoload :Manifest, "#{ROOT}/cli/commands/manifest"
23 end40 end
2441
25 end42 end
2643
=== modified file 'lib/cli/commands/admin.rb'
--- lib/cli/commands/admin.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/admin.rb 2012-06-13 01:37:18 +0000
@@ -2,13 +2,32 @@
22
3 class Admin < Base3 class Admin < Base
44
5 def list_users
6 users = client.users
7 users.sort! {|a, b| a[:email] <=> b[:email] }
8 return display JSON.pretty_generate(users || []) if @options[:json]
9
10 display "\n"
11 return display "No Users" if users.nil? || users.empty?
12
13 users_table = table do |t|
14 t.headings = 'Email', 'Admin', 'Apps'
15 users.each do |user|
16 t << [user[:email], user[:admin], user[:apps].map {|x| x[:name]}.join(', ')]
17 end
18 end
19 display users_table
20 end
21
22 alias :users :list_users
23
5 def add_user(email=nil)24 def add_user(email=nil)
6 email = @options[:email] unless email25 email ||= @options[:email]
26 email ||= ask("Email") unless no_prompt
7 password = @options[:password]27 password = @options[:password]
8 email = ask("Email: ") unless no_prompt || email
9 unless no_prompt || password28 unless no_prompt || password
10 password = ask("Password: ") {|q| q.echo = '*'}29 password = ask("Password", :echo => "*")
11 password2 = ask("Verify Password: ") {|q| q.echo = '*'}30 password2 = ask("Verify Password", :echo => "*")
12 err "Passwords did not match, try again" if password != password231 err "Passwords did not match, try again" if password != password2
13 end32 end
14 err "Need a valid email" unless email33 err "Need a valid email" unless email
@@ -34,11 +53,14 @@
3453
35 if (apps && !apps.empty?)54 if (apps && !apps.empty?)
36 unless no_prompt55 unless no_prompt
37 proceed = ask("\nDeployed applications and associated services will be DELETED, continue? [yN]: ")56 proceed = ask(
38 err "Aborted" if proceed.upcase != 'Y'57 "\nDeployed applications and associated services will be DELETED, continue?",
58 :default => false
59 )
60 err "Aborted" unless proceed
39 end61 end
40 cmd = Apps.new(@options)62 cmd = Apps.new(@options.merge({ :force => true }))
41 apps.each { |app| cmd.delete_app(app[:name], true) }63 apps.each { |app| cmd.delete(app[:name]) }
42 end64 end
4365
44 services = client.services66 services = client.services
@@ -48,6 +70,7 @@
48 end70 end
4971
50 display 'Deleting User: ', false72 display 'Deleting User: ', false
73 client.proxy = nil
51 client.delete_user(user_email)74 client.delete_user(user_email)
52 display 'OK'.green75 display 'OK'.green
53 end76 end
5477
=== modified file 'lib/cli/commands/apps.rb'
--- lib/cli/commands/apps.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/apps.rb 2012-06-13 01:37:18 +0000
@@ -1,16 +1,23 @@
1require 'digest/sha1'1require 'digest/sha1'
2require 'fileutils'2require 'fileutils'
3require 'pathname'
3require 'tempfile'4require 'tempfile'
4require 'tmpdir'5require 'tmpdir'
5require 'set'6require 'set'
7require "uuidtools"
8require 'socket'
69
7module VMC::Cli::Command10module VMC::Cli::Command
811
9 class Apps < Base12 class Apps < Base
10 include VMC::Cli::ServicesHelper13 include VMC::Cli::ServicesHelper
14 include VMC::Cli::ManifestHelper
15 include VMC::Cli::TunnelHelper
16 include VMC::Cli::ConsoleHelper
1117
12 def list18 def list
13 apps = client.apps19 apps = client.apps
20 apps.sort! {|a, b| a[:name] <=> b[:name] }
14 return display JSON.pretty_generate(apps || []) if @options[:json]21 return display JSON.pretty_generate(apps || []) if @options[:json]
1522
16 display "\n"23 display "\n"
@@ -35,108 +42,97 @@
35 HEALTH_TICKS = 5/SLEEP_TIME42 HEALTH_TICKS = 5/SLEEP_TIME
36 TAIL_TICKS = 45/SLEEP_TIME43 TAIL_TICKS = 45/SLEEP_TIME
37 GIVEUP_TICKS = 120/SLEEP_TIME44 GIVEUP_TICKS = 120/SLEEP_TIME
38 YES_SET = Set.new(["y", "Y", "yes", "YES"])45
3946 def info(what, default=nil)
40 def start(appname, push = false)47 @options[what] || (@app_info && @app_info[what.to_s]) || default
41 app = client.app_info(appname)48 end
4249
43 return display "Application '#{appname}' could not be found".red if app.nil?50 def console(appname, interactive=true)
44 return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'51 unless defined? Caldecott
4552 display "To use `vmc rails-console', you must first install Caldecott:"
46 banner = 'Staging Application: '53 display ""
47 display banner, false54 display "\tgem install caldecott"
4855 display ""
49 t = Thread.new do56 display "Note that you'll need a C compiler. If you're on OS X, Xcode"
50 count = 057 display "will provide one. If you're on Windows, try DevKit."
51 while count < TAIL_TICKS do58 display ""
52 display '.', false59 display "This manual step will be removed in the future."
53 sleep SLEEP_TIME60 display ""
54 count += 161 err "Caldecott is not installed."
55 end62 end
56 end63
5764 #Make sure there is a console we can connect to first
58 app[:state] = 'STARTED'65 conn_info = console_connection_info appname
59 client.update_app(appname, app)66
6067 port = pick_tunnel_port(@options[:port] || 20000)
61 Thread.kill(t)68
62 clear(LINE_LENGTH)69 raise VMC::Client::AuthError unless client.logged_in?
63 display "#{banner}#{'OK'.green}"70
6471 if not tunnel_pushed?
65 banner = 'Starting Application: '72 display "Deploying tunnel application '#{tunnel_appname}'."
66 display banner, false73 auth = UUIDTools::UUID.random_create.to_s
6774 push_caldecott(auth)
68 count = log_lines_displayed = 075 start_caldecott
69 failed = false76 else
70 start_time = Time.now.to_i77 auth = tunnel_auth
7178 end
72 loop do79
73 display '.', false unless count > TICKER_TICKS80 if not tunnel_healthy?(auth)
74 sleep SLEEP_TIME81 display "Redeploying tunnel application '#{tunnel_appname}'."
75 begin82 # We don't expect caldecott not to be running, so take the
76 break if app_started_properly(appname, count > HEALTH_TICKS)83 # most aggressive restart method.. delete/re-push
77 if !crashes(appname, false, start_time).empty?84 client.delete_app(tunnel_appname)
78 # Check for the existance of crashes85 invalidate_tunnel_app_info
79 display "\nError: Application [#{appname}] failed to start, logs information below.\n".red86 push_caldecott(auth)
80 grab_crash_logs(appname, '0', true)87 start_caldecott
81 if push88 end
82 display "\n"89
83 should_delete = ask 'Should I delete the application? (Y/n)? ' unless no_prompt90 start_tunnel(port, conn_info, auth)
84 delete_app(appname, false) unless no_prompt || should_delete.upcase == 'N'91 wait_for_tunnel_start(port)
85 end92 start_local_console(port, appname) if interactive
86 failed = true93 port
87 break94 end
88 elsif count > TAIL_TICKS95
89 log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)96 def start(appname=nil, push=false)
90 end97 if appname
91 rescue => e98 do_start(appname, push)
92 err(e.message, '')99 else
93 end100 each_app do |name|
94 count += 1101 do_start(name, push)
95 if count > GIVEUP_TICKS # 2 minutes102 end
96 display "\nApplication is taking too long to start, check your logs".yellow103 end
97 break104 end
98 end105
99 end106 def stop(appname=nil)
100 exit(false) if failed107 if appname
101 clear(LINE_LENGTH)108 do_stop(appname)
102 display "#{banner}#{'OK'.green}"109 else
103 end110 reversed = []
104111 each_app do |name|
105 def stop(appname)112 reversed.unshift name
106 app = client.app_info(appname)113 end
107 return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'114
108 display 'Stopping Application: ', false115 reversed.each do |name|
109 app[:state] = 'STOPPED'116 do_stop(name)
110 client.update_app(appname, app)117 end
111 display 'OK'.green118 end
112 end119 end
113120
114 def restart(appname)121 def restart(appname=nil)
115 stop(appname)122 stop(appname)
116 start(appname)123 start(appname)
117 end124 end
118125
119 def rename(appname, newname)
120 app = client.app_info(appname)
121 app[:name] = newname
122 display 'Renaming Appliction: '
123 client.update_app(newname, app)
124 display 'OK'.green
125 end
126
127 def mem(appname, memsize=nil)126 def mem(appname, memsize=nil)
128 app = client.app_info(appname)127 app = client.app_info(appname)
129 mem = current_mem = mem_quota_to_choice(app[:resources][:memory])128 mem = current_mem = mem_quota_to_choice(app[:resources][:memory])
130 memsize = normalize_mem(memsize) if memsize129 memsize = normalize_mem(memsize) if memsize
131130
132 unless memsize131 memsize ||= ask(
133 choose do |menu|132 "Update Memory Reservation?",
134 menu.layout = :one_line133 :default => current_mem,
135 menu.prompt = "Update Memory Reservation? [Current:#{current_mem}] "134 :choices => mem_choices
136 menu.default = current_mem135 )
137 mem_choices.each { |choice| menu.choice(choice) { memsize = choice } }
138 end
139 end
140136
141 mem = mem_choice_to_quota(mem)137 mem = mem_choice_to_quota(mem)
142 memsize = mem_choice_to_quota(memsize)138 memsize = mem_choice_to_quota(memsize)
@@ -165,7 +161,7 @@
165 uris << url161 uris << url
166 app[:uris] = uris162 app[:uris] = uris
167 client.update_app(appname, app)163 client.update_app(appname, app)
168 display "Succesfully mapped url".green164 display "Successfully mapped url".green
169 end165 end
170166
171 def unmap(appname, url)167 def unmap(appname, url)
@@ -176,18 +172,13 @@
176 err "Invalid url" unless deleted172 err "Invalid url" unless deleted
177 app[:uris] = uris173 app[:uris] = uris
178 client.update_app(appname, app)174 client.update_app(appname, app)
179 display "Succesfully unmapped url".green175 display "Successfully unmapped url".green
180
181 end176 end
182177
183 def delete(appname=nil)178 def delete(appname=nil)
184 force = @options[:force]179 force = @options[:force]
185 if @options[:all]180 if @options[:all]
186 should_delete = force && no_prompt ? 'Y' : 'N'181 if no_prompt || force || ask("Delete ALL applications?", :default => false)
187 unless no_prompt || force
188 should_delete = ask 'Delete ALL Applications and Services? (y/N)? '
189 end
190 if should_delete.upcase == 'Y'
191 apps = client.apps182 apps = client.apps
192 apps.each { |app| delete_app(app[:name], force) }183 apps.each { |app| delete_app(app[:name], force) }
193 end184 end
@@ -197,48 +188,18 @@
197 end188 end
198 end189 end
199190
200 def delete_app(appname, force)
201 app = client.app_info(appname)
202 services_to_delete = []
203 app_services = app[:services]
204 app_services.each { |service|
205 del_service = force && no_prompt ? 'Y' : 'N'
206 unless no_prompt || force
207 del_service = ask("Provisioned service [#{service}] detected, would you like to delete it? [yN]: ")
208 end
209 services_to_delete << service if del_service.upcase == 'Y'
210 }
211 display "Deleting application [#{appname}]: ", false
212 client.delete_app(appname)
213 display 'OK'.green
214
215 services_to_delete.each do |s|
216 display "Deleting service [#{s}]: ", false
217 client.delete_service(s)
218 display 'OK'.green
219 end
220 end
221
222 def all_files(appname, path)
223 instances_info_envelope = client.app_instances(appname)
224 return if instances_info_envelope.is_a?(Array)
225 instances_info = instances_info_envelope[:instances] || []
226 instances_info.each do |entry|
227 content = client.app_files(appname, path, entry[:index])
228 display_logfile(path, content, entry[:index], "====> [#{entry[:index]}: #{path}] <====\n".bold)
229 end
230 end
231
232 def files(appname, path='/')191 def files(appname, path='/')
233 return all_files(appname, path) if @options[:all] && !@options[:instance]192 return all_files(appname, path) if @options[:all] && !@options[:instance]
234 instance = @options[:instance] || '0'193 instance = @options[:instance] || '0'
235 content = client.app_files(appname, path, instance)194 content = client.app_files(appname, path, instance)
236 display content195 display content
237 rescue VMC::Client::NotFound => e196 rescue VMC::Client::NotFound, VMC::Client::TargetError
238 err 'No such file or directory'197 err 'No such file or directory'
239 end198 end
240199
241 def logs(appname)200 def logs(appname)
201 # Check if we have an app before progressing further
202 client.app_info(appname)
242 return grab_all_logs(appname) if @options[:all] && !@options[:instance]203 return grab_all_logs(appname) if @options[:all] && !@options[:instance]
243 instance = @options[:instance] || '0'204 instance = @options[:instance] || '0'
244 grab_logs(appname, instance)205 grab_logs(appname, instance)
@@ -285,194 +246,67 @@
285 end246 end
286247
287 def instances(appname, num=nil)248 def instances(appname, num=nil)
288 if (num)249 if num
289 change_instances(appname, num)250 change_instances(appname, num)
290 else251 else
291 get_instances(appname)252 get_instances(appname)
292 end253 end
293 end254 end
294255
295 def stats(appname)256 def stats(appname=nil)
296 stats = client.app_stats(appname)257 if appname
297 return display JSON.pretty_generate(stats) if @options[:json]258 display "\n", false
298259 do_stats(appname)
299 stats_table = table do |t|260 else
300 t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'261 each_app do |n|
301 stats.each do |entry|262 display "\n#{n}:"
302 index = entry[:instance]263 do_stats(n)
303 stat = entry[:stats]
304 hp = "#{stat[:host]}:#{stat[:port]}"
305 uptime = uptime_string(stat[:uptime])
306 usage = stat[:usage]
307 if usage
308 cpu = usage[:cpu]
309 mem = (usage[:mem] * 1024) # mem comes in K's
310 disk = usage[:disk]
311 end
312 mem_quota = stat[:mem_quota]
313 disk_quota = stat[:disk_quota]
314 mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
315 disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
316 cpu = cpu ? cpu.to_s : 'NA'
317 cpu = "#{cpu}% (#{stat[:cores]})"
318 t << [index, cpu, mem, disk, uptime]
319 end264 end
320 end265 end
321 display "\n"
322 if stats.empty?
323 display "No running instances for [#{appname}]".yellow
324 else
325 display stats_table
326 end
327 end266 end
328267
329 def update(appname)268 def update(appname=nil)
330 app = client.app_info(appname)269 if appname
331 if @options[:canary]270 app = client.app_info(appname)
332 display "[--canary] is deprecated and will be removed in a future version".yellow271 if @options[:canary]
272 display "[--canary] is deprecated and will be removed in a future version".yellow
273 end
274 upload_app_bits(appname, @path)
275 restart appname if app[:state] == 'STARTED'
276 else
277 each_app do |name|
278 display "Updating application '#{name}'..."
279
280 app = client.app_info(name)
281 upload_app_bits(name, @application)
282 restart name if app[:state] == 'STARTED'
283 end
333 end284 end
334 path = @options[:path] || '.'
335 upload_app_bits(appname, path)
336 restart appname if app[:state] == 'STARTED'
337 end285 end
338286
339 def push(appname=nil)287 def push(appname=nil)
340 instances = @options[:instances] || 1
341 exec = @options[:exec] || 'thin start'
342 ignore_framework = @options[:noframework]
343 no_start = @options[:nostart]
344
345 path = @options[:path] || '.'
346 appname = @options[:name] unless appname
347 url = @options[:url]
348 mem, memswitch = nil, @options[:mem]
349 memswitch = normalize_mem(memswitch) if memswitch
350
351 # Check app existing upfront if we have appname
352 app_checked = false
353 if appname
354 err "Application '#{appname}' already exists, use update" if app_exists?(appname)
355 app_checked = true
356 else
357 raise VMC::Client::AuthError unless client.logged_in?
358 end
359
360 # check if we have hit our app limit
361 check_app_limit
362
363 # check memsize here for capacity
364 if memswitch && !no_start
365 check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
366 end
367
368 unless no_prompt || @options[:path]288 unless no_prompt || @options[:path]
369 proceed = ask('Would you like to deploy from the current directory? [Yn]: ')289 proceed = ask(
370 if proceed.upcase == 'N'290 'Would you like to deploy from the current directory?',
371 path = ask('Please enter in the deployment path: ')291 :default => true
372 end292 )
373 end293
374294 unless proceed
375 path = File.expand_path(path)295 @path = ask('Deployment path')
376 check_deploy_directory(path)296 end
377297 end
378 appname = ask("Application Name: ") unless no_prompt || appname298
379 err "Application Name required." if appname.nil? || appname.empty?299 pushed = false
380300 each_app(false) do |name|
381 unless app_checked301 display "Pushing application '#{name}'..." if name
382 err "Application '#{appname}' already exists, use update or delete." if app_exists?(appname)302 do_push(name)
383 end303 pushed = true
384304 end
385 unless no_prompt || url305
386 url = ask("Application Deployed URL: '#{appname}.#{VMC::Cli::Config.suggest_url}'? ")306 unless pushed
387307 @application = @path
388 # common error case is for prompted users to answer y or Y or yes or YES to this ask() resulting in an308 do_push(appname)
389 # unintended URL of y. Special case this common error309 end
390 if YES_SET.member?(url)
391 #silently revert to the stock url
392 url = "#{appname}.#{VMC::Cli::Config.suggest_url}"
393 end
394 end
395
396 url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if url.nil? || url.empty?
397
398 # Detect the appropriate framework.
399 framework = nil
400 unless ignore_framework
401 framework = VMC::Cli::Framework.detect(path)
402 framework_correct = ask("Detected a #{framework}, is this correct? [Yn]: ") if prompt_ok && framework
403 framework_correct ||= 'y'
404 if prompt_ok && (framework.nil? || framework_correct.upcase == 'N')
405 display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
406 framework = nil if framework_correct.upcase == 'N'
407 choose do |menu|
408 menu.layout = :one_line
409 menu.prompt = "Select Application Type: "
410 menu.default = framework
411 VMC::Cli::Framework.known_frameworks.each do |f|
412 menu.choice(f) { framework = VMC::Cli::Framework.lookup(f) }
413 end
414 end
415 display "Selected #{framework}"
416 end
417 # Framework override, deprecated
418 exec = framework.exec if framework && framework.exec
419 else
420 framework = VMC::Cli::Framework.new
421 end
422
423 err "Application Type undetermined for path '#{path}'" unless framework
424 unless memswitch
425 mem = framework.memory
426 if prompt_ok
427 choose do |menu|
428 menu.layout = :one_line
429 menu.prompt = "Memory Reservation [Default:#{mem}] "
430 menu.default = mem
431 mem_choices.each { |choice| menu.choice(choice) { mem = choice } }
432 end
433 end
434 else
435 mem = memswitch
436 end
437
438 # Set to MB number
439 mem_quota = mem_choice_to_quota(mem)
440
441 # check memsize here for capacity
442 check_has_capacity_for(mem_quota * instances) unless no_start
443
444 display 'Creating Application: ', false
445
446 manifest = {
447 :name => "#{appname}",
448 :staging => {
449 :framework => framework.name,
450 :runtime => @options[:runtime]
451 },
452 :uris => [url],
453 :instances => instances,
454 :resources => {
455 :memory => mem_quota
456 },
457 }
458
459 # Send the manifest to the cloud controller
460 client.create_app(appname, manifest)
461 display 'OK'.green
462
463 # Services check
464 unless no_prompt || @options[:noservices]
465 services = client.services_info
466 unless services.empty?
467 proceed = ask("Would you like to bind any services to '#{appname}'? [yN]: ")
468 bind_services(appname, services) if proceed.upcase == 'Y'
469 end
470 end
471
472 # Stage and upload the app bits.
473 upload_app_bits(appname, path)
474
475 start(appname, true) unless no_start
476 end310 end
477311
478 def environment(appname)312 def environment(appname)
@@ -483,7 +317,7 @@
483 etable = table do |t|317 etable = table do |t|
484 t.headings = 'Variable', 'Value'318 t.headings = 'Variable', 'Value'
485 env.each do |e|319 env.each do |e|
486 k,v = e.split('=')320 k,v = e.split('=', 2)
487 t << [k, v]321 t << [k, v]
488 end322 end
489 end323 end
@@ -494,7 +328,7 @@
494 def environment_add(appname, k, v=nil)328 def environment_add(appname, k, v=nil)
495 app = client.app_info(appname)329 app = client.app_info(appname)
496 env = app[:env] || []330 env = app[:env] || []
497 k,v = k.split('=') unless v331 k,v = k.split('=', 2) unless v
498 env << "#{k}=#{v}"332 env << "#{k}=#{v}"
499 display "Adding Environment Variable [#{k}=#{v}]: ", false333 display "Adding Environment Variable [#{k}=#{v}]: ", false
500 app[:env] = env334 app[:env] = env
@@ -537,11 +371,35 @@
537371
538 def check_deploy_directory(path)372 def check_deploy_directory(path)
539 err 'Deployment path does not exist' unless File.exists? path373 err 'Deployment path does not exist' unless File.exists? path
540 err 'Deployment path is not a directory' unless File.directory? path
541 return if File.expand_path(Dir.tmpdir) != File.expand_path(path)374 return if File.expand_path(Dir.tmpdir) != File.expand_path(path)
542 err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"375 err "Can't deploy applications from staging directory: [#{Dir.tmpdir}]"
543 end376 end
544377
378 def check_unreachable_links(path)
379 files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
380
381 pwd = Pathname.pwd
382
383 abspath = File.expand_path(path)
384 unreachable = []
385 files.each do |f|
386 file = Pathname.new(f)
387 if file.symlink? && !file.realpath.to_s.start_with?(abspath)
388 unreachable << file.relative_path_from(pwd)
389 end
390 end
391
392 unless unreachable.empty?
393 root = Pathname.new(path).relative_path_from(pwd)
394 err "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
395 end
396 end
397
398 def find_sockets(path)
399 files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
400 files && files.select { |f| File.socket? f }
401 end
402
545 def upload_app_bits(appname, path)403 def upload_app_bits(appname, path)
546 display 'Uploading Application:'404 display 'Uploading Application:'
547405
@@ -551,147 +409,112 @@
551 explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"409 explode_dir = "#{Dir.tmpdir}/.vmc_#{appname}_files"
552 FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..410 FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
553411
554 Dir.chdir(path) do412 if path =~ /\.(war|zip)$/
555 # Stage the app appropriately and do the appropriate fingerprinting, etc.413 #single file that needs unpacking
556 if war_file = Dir.glob('*.war').first414 VMC::Cli::ZipUtil.unpack(path, explode_dir)
557 VMC::Cli::ZipUtil.unpack(war_file, explode_dir)415 elsif !File.directory? path
558 else416 #single file that doesn't need unpacking
559 FileUtils.mkdir(explode_dir)417 FileUtils.mkdir(explode_dir)
560 files = Dir.glob('{*,.[^\.]*}')418 FileUtils.cp(path,explode_dir)
561 FileUtils.cp_r(files, explode_dir)419 else
562 end420 Dir.chdir(path) do
563421 # Stage the app appropriately and do the appropriate fingerprinting, etc.
564 # Send the resource list to the cloudcontroller, the response will tell us what it already has..422 if war_file = Dir.glob('*.war').first
565 unless @options[:noresources]423 VMC::Cli::ZipUtil.unpack(war_file, explode_dir)
566 display ' Checking for available resources: ', false424 elsif zip_file = Dir.glob('*.zip').first
567 fingerprints = []425 VMC::Cli::ZipUtil.unpack(zip_file, explode_dir)
568 total_size = 0426 else
569 resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)427 check_unreachable_links(path)
570 resource_files.each do |filename|428 FileUtils.mkdir(explode_dir)
571 next if (File.directory?(filename) || !File.exists?(filename))429
572 fingerprints << {430 files = Dir.glob('{*,.[^\.]*}')
573 :size => File.size(filename),431
574 :sha1 => Digest::SHA1.file(filename).hexdigest,432 # Do not process .git files
575 :fn => filename433 files.delete('.git') if files
576 }434
577 total_size += File.size(filename)435 FileUtils.cp_r(files, explode_dir)
578 end436
579437 find_sockets(explode_dir).each do |s|
580 # Check to see if the resource check is worth the round trip438 File.delete s
581 if (total_size > (64*1024)) # 64k for now
582 # Send resource fingerprints to the cloud controller
583 appcloud_resources = client.check_resources(fingerprints)
584 end
585 display 'OK'.green
586
587 if appcloud_resources
588 display ' Processing resources: ', false
589 # We can then delete what we do not need to send.
590 appcloud_resources.each do |resource|
591 FileUtils.rm_f resource[:fn]
592 # adjust filenames sans the explode_dir prefix
593 resource[:fn].sub!("#{explode_dir}/", '')
594 end439 end
595 display 'OK'.green440 end
596 end441 end
597442 end
598 end443
599444 # Send the resource list to the cloudcontroller, the response will tell us what it already has..
600 # Perform Packing of the upload bits here.445 unless @options[:noresources]
601 unless VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?446 display ' Checking for available resources: ', false
602 display ' Packing application: ', false447 fingerprints = []
603 VMC::Cli::ZipUtil.pack(explode_dir, upload_file)448 total_size = 0
604 display 'OK'.green449 resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
605450 resource_files.each do |filename|
606 upload_size = File.size(upload_file);451 next if (File.directory?(filename) || !File.exists?(filename))
607 if upload_size > 1024*1024452 fingerprints << {
608 upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'453 :size => File.size(filename),
609 elsif upload_size > 0454 :sha1 => Digest::SHA1.file(filename).hexdigest,
610 upload_size = (upload_size/1024.0).round.to_s + 'K'455 :fn => filename
611 end456 }
612 else457 total_size += File.size(filename)
613 upload_size = '0K'458 end
614 end459
615460 # Check to see if the resource check is worth the round trip
616 upload_str = " Uploading (#{upload_size}): "461 if (total_size > (64*1024)) # 64k for now
617 display upload_str, false462 # Send resource fingerprints to the cloud controller
618463 appcloud_resources = client.check_resources(fingerprints)
619 unless VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?464 end
620 FileWithPercentOutput.display_str = upload_str
621 FileWithPercentOutput.upload_size = File.size(upload_file);
622 file = FileWithPercentOutput.open(upload_file, 'rb')
623 end
624
625 client.upload_app(appname, file, appcloud_resources)
626 display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
627
628 display 'Push Status: ', false
629 display 'OK'.green465 display 'OK'.green
630 end466
631467 if appcloud_resources
632 ensure468 display ' Processing resources: ', false
633 # Cleanup if we created an exploded directory.469 # We can then delete what we do not need to send.
634 FileUtils.rm_f(upload_file) if upload_file470 appcloud_resources.each do |resource|
635 FileUtils.rm_rf(explode_dir) if explode_dir471 FileUtils.rm_f resource[:fn]
636 end472 # adjust filenames sans the explode_dir prefix
637473 resource[:fn].sub!("#{explode_dir}/", '')
638 def choose_existing_service(appname, user_services)474 end
639 return unless prompt_ok475 display 'OK'.green
640 selected = false476 end
641 choose do |menu|477
642 menu.header = "The following provisioned services are available:"478 end
643 menu.prompt = 'Please select one you wish to provision: '479
644 menu.select_by = :index_or_name480 # If no resource needs to be sent, add an empty file to ensure we have
645 user_services.each do |s|481 # a multi-part request that is expected by nginx fronting the CC.
646 menu.choice(s[:name]) do482 if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
647 display "Binding Service: ", false483 Dir.chdir(explode_dir) do
648 client.bind_service(s[:name], appname)484 File.new(".__empty__", "w")
649 display 'OK'.green485 end
650 selected = true486 end
651 end487 # Perform Packing of the upload bits here.
652 end488 display ' Packing application: ', false
653 end489 VMC::Cli::ZipUtil.pack(explode_dir, upload_file)
654 selected490 display 'OK'.green
655 end491
656492 upload_size = File.size(upload_file);
657 def choose_new_service(appname, services)493 if upload_size > 1024*1024
658 return unless prompt_ok494 upload_size = (upload_size/(1024.0*1024.0)).round.to_s + 'M'
659 choose do |menu|495 elsif upload_size > 0
660 menu.header = "The following system services are available:"496 upload_size = (upload_size/1024.0).round.to_s + 'K'
661 menu.prompt = 'Please select one you wish to provision: '497 else
662 menu.select_by = :index_or_name498 upload_size = '0K'
663 service_choices = []499 end
664 services.each do |service_type, value|500
665 value.each do |vendor, version|501 upload_str = " Uploading (#{upload_size}): "
666 service_choices << vendor502 display upload_str, false
667 end503
668 end504 FileWithPercentOutput.display_str = upload_str
669 service_choices.sort! {|a, b| a.to_s <=> b.to_s }505 FileWithPercentOutput.upload_size = File.size(upload_file);
670 service_choices.each do |vendor|506 file = FileWithPercentOutput.open(upload_file, 'rb')
671 menu.choice(vendor) do507
672 default_name = random_service_name(vendor)508 client.upload_app(appname, file, appcloud_resources)
673 service_name = ask("Specify the name of the service [#{default_name}]: ")509 display 'OK'.green if VMC::Cli::ZipUtil.get_files_to_pack(explode_dir).empty?
674 service_name = default_name if service_name.empty?510
675 create_service_banner(vendor, service_name)511 display 'Push Status: ', false
676 bind_service_banner(service_name, appname)512 display 'OK'.green
677 end513
678 end514 ensure
679 end515 # Cleanup if we created an exploded directory.
680 end516 FileUtils.rm_f(upload_file) if upload_file
681517 FileUtils.rm_rf(explode_dir) if explode_dir
682 def bind_services(appname, services)
683 user_services = client.services
684 selected_existing = false
685 unless no_prompt || user_services.empty?
686 use_existing = ask "Would you like to use an existing provisioned service [yN]? "
687 if use_existing.upcase == 'Y'
688 selected_existing = choose_existing_service(appname, user_services)
689 end
690 end
691 # Create a new service and bind it here
692 unless selected_existing
693 choose_new_service(appname, services)
694 end
695 end518 end
696519
697 def check_app_limit520 def check_app_limit
@@ -768,9 +591,19 @@
768 return display "No running instances for [#{appname}]".yellow if instances_info.empty?591 return display "No running instances for [#{appname}]".yellow if instances_info.empty?
769592
770 instances_table = table do |t|593 instances_table = table do |t|
771 t.headings = 'Index', 'State', 'Start Time'594 show_debug = instances_info.any? { |e| e[:debug_port] }
595
596 headings = ['Index', 'State', 'Start Time']
597 headings << 'Debug IP' if show_debug
598 headings << 'Debug Port' if show_debug
599
600 t.headings = headings
601
772 instances_info.each do |entry|602 instances_info.each do |entry|
773 t << [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]603 row = [entry[:index], entry[:state], Time.at(entry[:since]).strftime("%m/%d/%Y %I:%M%p")]
604 row << entry[:debug_ip] if show_debug
605 row << entry[:debug_port] if show_debug
606 t << row
774 end607 end
775 end608 end
776 display "\n"609 display "\n"
@@ -822,18 +655,24 @@
822 case health(app)655 case health(app)
823 when 'N/A'656 when 'N/A'
824 # Health manager not running.657 # Health manager not running.
825 err "\Application '#{appname}'s state is undetermined, not enough information available." if error_on_health658 err "\nApplication '#{appname}'s state is undetermined, not enough information available." if error_on_health
826 return false659 return false
827 when 'RUNNING'660 when 'RUNNING'
828 return true661 return true
829 else662 else
830 return false663 if app[:meta][:debug] == "suspend"
664 display "\nApplication [#{appname}] has started in a mode that is waiting for you to trigger startup."
665 return true
666 else
667 return false
668 end
831 end669 end
832 end670 end
833671
834 def display_logfile(path, content, instance='0', banner=nil)672 def display_logfile(path, content, instance='0', banner=nil)
835 banner ||= "====> #{path} <====\n\n"673 banner ||= "====> #{path} <====\n\n"
836 if content && !content.empty?674
675 unless content.empty?
837 display banner676 display banner
838 prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]677 prefix = "[#{instance}: #{path}] -".bold if @options[:prefixlogs]
839 unless prefix678 unless prefix
@@ -846,10 +685,6 @@
846 end685 end
847 end686 end
848687
849 def log_file_paths
850 %w[logs/stderr.log logs/stdout.log logs/startup.log]
851 end
852
853 def grab_all_logs(appname)688 def grab_all_logs(appname)
854 instances_info_envelope = client.app_instances(appname)689 instances_info_envelope = client.app_instances(appname)
855 return if instances_info_envelope.is_a?(Array)690 return if instances_info_envelope.is_a?(Array)
@@ -860,13 +695,21 @@
860 end695 end
861696
862 def grab_logs(appname, instance)697 def grab_logs(appname, instance)
863 log_file_paths.each do |path|698 files_under(appname, instance, "/logs").each do |path|
864 begin699 begin
865 content = client.app_files(appname, path, instance)700 content = client.app_files(appname, path, instance)
866 rescue701 display_logfile(path, content, instance)
702 rescue VMC::Client::NotFound, VMC::Client::TargetError
867 end703 end
868 display_logfile(path, content, instance)704 end
869 end705 end
706
707 def files_under(appname, instance, path)
708 client.app_files(appname, path, instance).split("\n").collect do |l|
709 "#{path}/#{l.split[0]}"
710 end
711 rescue VMC::Client::NotFound, VMC::Client::TargetError
712 []
870 end713 end
871714
872 def grab_crash_logs(appname, instance, was_staged=false)715 def grab_crash_logs(appname, instance, was_staged=false)
@@ -877,11 +720,10 @@
877 map = VMC::Cli::Config.instances720 map = VMC::Cli::Config.instances
878 instance = map[instance] if map[instance]721 instance = map[instance] if map[instance]
879722
880 ['/logs/err.log', '/logs/staging.log', 'logs/stderr.log', 'logs/stdout.log', 'logs/startup.log'].each do |path|723 (files_under(appname, instance, "/logs") +
881 begin724 files_under(appname, instance, "/app/logs") +
882 content = client.app_files(appname, path, instance)725 files_under(appname, instance, "/app/log")).each do |path|
883 rescue726 content = client.app_files(appname, path, instance)
884 end
885 display_logfile(path, content, instance)727 display_logfile(path, content, instance)
886 end728 end
887 end729 end
@@ -899,8 +741,355 @@
899 display tail.join("\n") if new_lines > 0741 display tail.join("\n") if new_lines > 0
900 end742 end
901 since + new_lines743 since + new_lines
902 end744 rescue VMC::Client::NotFound, VMC::Client::TargetError
903 rescue745 0
746 end
747
748 def provisioned_services_apps_hash
749 apps = client.apps
750 services_apps_hash = {}
751 apps.each {|app|
752 app[:services].each { |svc|
753 svc_apps = services_apps_hash[svc]
754 unless svc_apps
755 svc_apps = Set.new
756 services_apps_hash[svc] = svc_apps
757 end
758 svc_apps.add(app[:name])
759 } unless app[:services] == nil
760 }
761 services_apps_hash
762 end
763
764 def delete_app(appname, force)
765 app = client.app_info(appname)
766 services_to_delete = []
767 app_services = app[:services]
768 services_apps_hash = provisioned_services_apps_hash
769 app_services.each { |service|
770 del_service = force && no_prompt
771 unless no_prompt || force
772 del_service = ask(
773 "Provisioned service [#{service}] detected, would you like to delete it?",
774 :default => false
775 )
776
777 if del_service
778 apps_using_service = services_apps_hash[service].reject!{ |app| app == appname}
779 if apps_using_service.size > 0
780 del_service = ask(
781 "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?",
782 :default => false
783 )
784 end
785 end
786 end
787 services_to_delete << service if del_service
788 }
789
790 display "Deleting application [#{appname}]: ", false
791 client.delete_app(appname)
792 display 'OK'.green
793
794 services_to_delete.each do |s|
795 delete_service_banner(s)
796 end
797 end
798
799 def do_start(appname, push=false)
800 app = client.app_info(appname)
801 return display "Application '#{appname}' could not be found".red if app.nil?
802 return display "Application '#{appname}' already started".yellow if app[:state] == 'STARTED'
803
804
805
806 if @options[:debug]
807 runtimes = client.runtimes_info
808 return display "Cannot get runtime information." unless runtimes
809
810 runtime = runtimes[app[:staging][:stack].to_sym]
811 return display "Unknown runtime." unless runtime
812
813 unless runtime[:debug_modes] and runtime[:debug_modes].include? @options[:debug]
814 modes = runtime[:debug_modes] || []
815
816 display "\nApplication '#{appname}' cannot start in '#{@options[:debug]}' mode"
817
818 if push
819 display "Try 'vmc start' with one of the following modes: #{modes.inspect}"
820 else
821 display "Available modes: #{modes.inspect}"
822 end
823
824 return
825 end
826 end
827
828 banner = "Staging Application '#{appname}': "
829 display banner, false
830
831 t = Thread.new do
832 count = 0
833 while count < TAIL_TICKS do
834 display '.', false
835 sleep SLEEP_TIME
836 count += 1
837 end
838 end
839
840 app[:state] = 'STARTED'
841 app[:debug] = @options[:debug]
842 app[:console] = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model]).console
843 client.update_app(appname, app)
844
845 Thread.kill(t)
846 clear(LINE_LENGTH)
847 display "#{banner}#{'OK'.green}"
848
849 banner = "Starting Application '#{appname}': "
850 display banner, false
851
852 count = log_lines_displayed = 0
853 failed = false
854 start_time = Time.now.to_i
855
856 loop do
857 display '.', false unless count > TICKER_TICKS
858 sleep SLEEP_TIME
859
860 break if app_started_properly(appname, count > HEALTH_TICKS)
861
862 if !crashes(appname, false, start_time).empty?
863 # Check for the existance of crashes
864 display "\nError: Application [#{appname}] failed to start, logs information below.\n".red
865 grab_crash_logs(appname, '0', true)
866 if push and !no_prompt
867 display "\n"
868 delete_app(appname, false) if ask "Delete the application?", :default => true
869 end
870 failed = true
871 break
872 elsif count > TAIL_TICKS
873 log_lines_displayed = grab_startup_tail(appname, log_lines_displayed)
874 end
875
876 count += 1
877 if count > GIVEUP_TICKS # 2 minutes
878 display "\nApplication is taking too long to start, check your logs".yellow
879 break
880 end
881 end
882 exit(false) if failed
883 clear(LINE_LENGTH)
884 display "#{banner}#{'OK'.green}"
885 end
886
887 def do_stop(appname)
888 app = client.app_info(appname)
889 return display "Application '#{appname}' already stopped".yellow if app[:state] == 'STOPPED'
890 display "Stopping Application '#{appname}': ", false
891 app[:state] = 'STOPPED'
892 client.update_app(appname, app)
893 display 'OK'.green
894 end
895
896 def do_push(appname=nil)
897 unless @app_info || no_prompt
898 @manifest = { "applications" => { @path => { "name" => appname } } }
899
900 interact
901
902 if ask("Would you like to save this configuration?", :default => false)
903 save_manifest
904 end
905
906 resolve_manifest(@manifest)
907
908 @app_info = @manifest["applications"][@path]
909 end
910
911 instances = info(:instances, 1)
912 exec = info(:exec, 'thin start')
913
914 ignore_framework = @options[:noframework]
915 no_start = @options[:nostart]
916
917 appname ||= info(:name)
918 url = info(:url) || info(:urls)
919 mem, memswitch = nil, info(:mem)
920 memswitch = normalize_mem(memswitch) if memswitch
921 command = info(:command)
922 runtime = info(:runtime)
923
924 # Check app existing upfront if we have appname
925 app_checked = false
926 if appname
927 err "Application '#{appname}' already exists, use update" if app_exists?(appname)
928 app_checked = true
929 else
930 raise VMC::Client::AuthError unless client.logged_in?
931 end
932
933 # check if we have hit our app limit
934 check_app_limit
935 # check memsize here for capacity
936 if memswitch && !no_start
937 check_has_capacity_for(mem_choice_to_quota(memswitch) * instances)
938 end
939
940 appname ||= ask("Application Name") unless no_prompt
941 err "Application Name required." if appname.nil? || appname.empty?
942
943 check_deploy_directory(@application)
944
945 if !app_checked and app_exists?(appname)
946 err "Application '#{appname}' already exists, use update or delete."
947 end
948
949 if ignore_framework
950 framework = VMC::Cli::Framework.new
951 elsif f = info(:framework)
952 info = Hash[f["info"].collect { |k, v| [k.to_sym, v] }]
953
954 framework = VMC::Cli::Framework.create(f["name"], info)
955 exec = framework.exec if framework && framework.exec
956 else
957 framework = detect_framework(prompt_ok)
958 end
959
960 err "Application Type undetermined for path '#{@application}'" unless framework
961
962 if not runtime
963 default_runtime = framework.default_runtime @application
964 runtime = detect_runtime(default_runtime, !no_prompt) if framework.prompt_for_runtime?
965 end
966 command = ask("Start Command") if !command && framework.require_start_command?
967
968 default_url = "None"
969 default_url = "#{appname}.#{VMC::Cli::Config.suggest_url}" if framework.require_url?
970
971
972 unless no_prompt || url || !framework.require_url?
973 url = ask(
974 "Application Deployed URL",
975 :default => default_url
976 )
977
978 # common error case is for prompted users to answer y or Y or yes or
979 # YES to this ask() resulting in an unintended URL of y. Special case
980 # this common error
981 url = nil if YES_SET.member? url
982 end
983 url = nil if url == "None"
984 default_url = nil if default_url == "None"
985 url ||= default_url
986
987 if memswitch
988 mem = memswitch
989 elsif prompt_ok
990 mem = ask("Memory Reservation",
991 :default => framework.memory(runtime),
992 :choices => mem_choices)
993 else
994 mem = framework.memory runtime
995 end
996
997 # Set to MB number
998 mem_quota = mem_choice_to_quota(mem)
999
1000 # check memsize here for capacity
1001 check_has_capacity_for(mem_quota * instances) unless no_start
1002
1003 display 'Creating Application: ', false
1004
1005 manifest = {
1006 :name => "#{appname}",
1007 :staging => {
1008 :framework => framework.name,
1009 :runtime => runtime
1010 },
1011 :uris => Array(url),
1012 :instances => instances,
1013 :resources => {
1014 :memory => mem_quota
1015 }
1016 }
1017 manifest[:staging][:command] = command if command
1018
1019 # Send the manifest to the cloud controller
1020 client.create_app(appname, manifest)
1021 display 'OK'.green
1022
1023
1024 existing = Set.new(client.services.collect { |s| s[:name] })
1025
1026 if @app_info && services = @app_info["services"]
1027 services.each do |name, info|
1028 unless existing.include? name
1029 create_service_banner(info["type"], name, true)
1030 end
1031
1032 bind_service_banner(name, appname)
1033 end
1034 end
1035
1036 # Stage and upload the app bits.
1037 upload_app_bits(appname, @application)
1038
1039 start(appname, true) unless no_start
1040 end
1041
1042 def do_stats(appname)
1043 stats = client.app_stats(appname)
1044 return display JSON.pretty_generate(stats) if @options[:json]
1045
1046 stats_table = table do |t|
1047 t.headings = 'Instance', 'CPU (Cores)', 'Memory (limit)', 'Disk (limit)', 'Uptime'
1048 stats.each do |entry|
1049 index = entry[:instance]
1050 stat = entry[:stats]
1051 hp = "#{stat[:host]}:#{stat[:port]}"
1052 uptime = uptime_string(stat[:uptime])
1053 usage = stat[:usage]
1054 if usage
1055 cpu = usage[:cpu]
1056 mem = (usage[:mem] * 1024) # mem comes in K's
1057 disk = usage[:disk]
1058 end
1059 mem_quota = stat[:mem_quota]
1060 disk_quota = stat[:disk_quota]
1061 mem = "#{pretty_size(mem)} (#{pretty_size(mem_quota, 0)})"
1062 disk = "#{pretty_size(disk)} (#{pretty_size(disk_quota, 0)})"
1063 cpu = cpu ? cpu.to_s : 'NA'
1064 cpu = "#{cpu}% (#{stat[:cores]})"
1065 t << [index, cpu, mem, disk, uptime]
1066 end
1067 end
1068
1069 if stats.empty?
1070 display "No running instances for [#{appname}]".yellow
1071 else
1072 display stats_table
1073 end
1074 end
1075
1076 def all_files(appname, path)
1077 instances_info_envelope = client.app_instances(appname)
1078 return if instances_info_envelope.is_a?(Array)
1079 instances_info = instances_info_envelope[:instances] || []
1080 instances_info.each do |entry|
1081 begin
1082 content = client.app_files(appname, path, entry[:index])
1083 display_logfile(
1084 path,
1085 content,
1086 entry[:index],
1087 "====> [#{entry[:index]}: #{path}] <====\n".bold
1088 )
1089 rescue VMC::Client::NotFound, VMC::Client::TargetError
1090 end
1091 end
1092 end
904 end1093 end
9051094
906 class FileWithPercentOutput < ::File1095 class FileWithPercentOutput < ::File
9071096
=== modified file 'lib/cli/commands/base.rb'
--- lib/cli/commands/base.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/base.rb 2012-06-13 01:37:18 +0000
@@ -1,33 +1,189 @@
1
2require 'rubygems'1require 'rubygems'
2require 'interact'
3require 'terminal-table/import'3require 'terminal-table/import'
4require 'highline/import'
54
6module VMC::Cli5module VMC::Cli
76
8 module Command7 module Command
98
10 class Base9 class Base
10 include Interactive
11
11 attr_reader :no_prompt, :prompt_ok12 attr_reader :no_prompt, :prompt_ok
1213
14 MANIFEST = "manifest.yml"
15
13 def initialize(options={})16 def initialize(options={})
14 @options = options.dup17 @options = options.dup
15 @no_prompt = @options[:noprompts]18 @no_prompt = @options[:noprompts]
16 @prompt_ok = !no_prompt19 @prompt_ok = !no_prompt
1720
18 # Fix for system ruby and Highline (stdin) on MacOSX
19 if RUBY_PLATFORM =~ /darwin/ && RUBY_VERSION == '1.8.7' && RUBY_PATCHLEVEL <= 174
20 HighLine.track_eof = false
21 end
22
23 # Suppress colorize on Windows systems for now.21 # Suppress colorize on Windows systems for now.
24 if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']22 if WINDOWS
25 VMC::Cli::Config.colorize = false23 VMC::Cli::Config.colorize = false
26 end24 end
2725
28 end26 @path = @options[:path] || '.'
2927
30 def client28 load_manifest manifest_file if manifest_file
29 end
30
31 def manifest_file
32 return @options[:manifest] if @options[:manifest]
33 return @manifest_file if @manifest_file
34
35 where = File.expand_path(@path)
36 while true
37 if File.exists?(File.join(where, MANIFEST))
38 @manifest_file = File.join(where, MANIFEST)
39 break
40 elsif File.basename(where) == "/"
41 @manifest_file = nil
42 break
43 else
44 where = File.expand_path("../", where)
45 end
46 end
47
48 @manifest_file
49 end
50
51 def load_manifest_structure(file)
52 manifest = YAML.load_file file
53
54 Array(manifest["inherit"]).each do |p|
55 manifest = merge_parent(manifest, p)
56 end
57
58 if apps = manifest["applications"]
59 apps.each do |k, v|
60 abs = File.expand_path(k, file)
61 if Dir.pwd.start_with? abs
62 manifest = merge_manifest(manifest, v)
63 end
64 end
65 end
66
67 manifest
68 end
69
70 def resolve_manifest(manifest)
71 if apps = manifest["applications"]
72 apps.each_value do |v|
73 resolve_lexically(v, [manifest])
74 end
75 end
76
77 resolve_lexically(manifest, [manifest])
78 end
79
80 def load_manifest(file)
81 @manifest = load_manifest_structure(file)
82 resolve_manifest(@manifest)
83 end
84
85 def merge_parent(child, path)
86 file = File.expand_path("../" + path, manifest_file)
87 merge_manifest(child, load_manifest_structure(file))
88 end
89
90 def merge_manifest(child, parent)
91 merge = proc do |_, old, new|
92 if new.is_a?(Hash) and old.is_a?(Hash)
93 old.merge(new, &merge)
94 else
95 new
96 end
97 end
98
99 parent.merge(child, &merge)
100 end
101
102 def resolve_lexically(val, ctx = [@manifest])
103 case val
104 when Hash
105 val.each_value do |v|
106 resolve_lexically(v, [val] + ctx)
107 end
108 when Array
109 val.each do |v|
110 resolve_lexically(v, ctx)
111 end
112 when String
113 val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
114 resolve_symbol($1, ctx)
115 end
116 end
117
118 nil
119 end
120
121 def resolve_symbol(sym, ctx)
122 case sym
123 when "target-base"
124 target_base(ctx)
125
126 when "target-url"
127 target_url(ctx)
128
129 when "random-word"
130 "%04x" % [rand(0x0100000)]
131
132 else
133 found = find_symbol(sym, ctx)
134
135 if found
136 resolve_lexically(found, ctx)
137 found
138 else
139 err(sym, "Unknown symbol in manifest: ")
140 end
141 end
142 end
143
144 def find_symbol(sym, ctx)
145 ctx.each do |h|
146 if val = resolve_in(h, sym)
147 return val
148 end
149 end
150
151 nil
152 end
153
154 def resolve_in(hash, *where)
155 find_in_hash(hash, ["properties"] + where) ||
156 find_in_hash(hash, ["applications", @application] + where) ||
157 find_in_hash(hash, where)
158 end
159
160 def manifest(*where)
161 resolve_in(@manifest, *where)
162 end
163
164 def find_in_hash(hash, where)
165 what = hash
166 where.each do |x|
167 return nil unless what.is_a?(Hash)
168 what = what[x]
169 end
170
171 what
172 end
173
174 def target_url(ctx = [])
175 find_symbol("target", ctx) ||
176 (@client && @client.target) ||
177 VMC::Cli::Config.target_url
178 end
179
180 def target_base(ctx = [])
181 VMC::Cli::Config.base_of(find_symbol("target", ctx) || target_url)
182 end
183
184 # Inject a client to help in testing.
185 def client(cli=nil)
186 @client ||= cli
31 return @client if @client187 return @client if @client
32 @client = VMC::Client.new(target_url, auth_token)188 @client = VMC::Client.new(target_url, auth_token)
33 @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace189 @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace
@@ -36,18 +192,11 @@
36 end192 end
37193
38 def client_info194 def client_info
39 return @client_info if @client_info195 @client_info ||= client.info
40 @client_info = client.info
41 end
42
43 def target_url
44 return @target_url if @target_url
45 @target_url = VMC::Cli::Config.target_url
46 end196 end
47197
48 def auth_token198 def auth_token
49 return @auth_token if @auth_token199 @auth_token = VMC::Cli::Config.auth_token(@options[:token_file])
50 @auth_token = VMC::Cli::Config.auth_token
51 end200 end
52201
53 def runtimes_info202 def runtimes_info
@@ -72,7 +221,6 @@
72 end221 end
73 @frameworks222 @frameworks
74 end223 end
75
76 end224 end
77 end225 end
78end226end
79227
=== added file 'lib/cli/commands/manifest.rb'
--- lib/cli/commands/manifest.rb 1970-01-01 00:00:00 +0000
+++ lib/cli/commands/manifest.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,56 @@
1module VMC::Cli::Command
2 class Manifest < Base
3 include VMC::Cli::ManifestHelper
4
5 def initialize(options)
6 super
7
8 # don't resolve any of the manifest template stuff
9 if manifest_file
10 @manifest = load_manifest_structure manifest_file
11 else
12 @manifest = {}
13 end
14 end
15
16 def edit
17 build_manifest
18 save_manifest
19 end
20
21 def extend(which)
22 parent = load_manifest_structure which
23 @manifest = load_manifest_structure which
24
25 build_manifest
26
27 simplify(@manifest, parent)
28
29 @manifest["inherit"] ||= []
30 @manifest["inherit"] << which
31
32 save_manifest(ask("Save where?"))
33 end
34
35 private
36
37 def simplify(child, parent)
38 return unless child.is_a?(Hash) and parent.is_a?(Hash)
39
40 child.reject! do |k, v|
41 if v == parent[k]
42 puts "rejecting #{k}"
43 true
44 else
45 simplify(v, parent[k])
46 false
47 end
48 end
49 end
50
51 def build_manifest
52 @application = ask("Configure for which application?", :default => ".")
53 interact true
54 end
55 end
56end
057
=== added file 'lib/cli/commands/micro.rb'
--- lib/cli/commands/micro.rb 1970-01-01 00:00:00 +0000
+++ lib/cli/commands/micro.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,115 @@
1module VMC::Cli::Command
2 class Micro < Base
3
4 def initialize(args)
5 super(args)
6 end
7
8 def offline(mode)
9 command('offline')
10 end
11
12 def online(mode)
13 command('online')
14 end
15
16 def status(mode)
17 command('status')
18 end
19
20 def command(cmd)
21 config = build_config
22 switcher(config).send(cmd)
23 store_config(config)
24 end
25
26 def switcher(config)
27 case Micro.platform
28 when :darwin
29 switcher = VMC::Micro::Switcher::Darwin.new(config)
30 when :linux
31 switcher = VMC::Micro::Switcher::Linux.new(config)
32 when :windows
33 switcher = VMC::Micro::Switcher::Windows.new(config)
34 when :dummy # for testing only
35 switcher = VMC::Micro::Switcher::Dummy.new(config)
36 else
37 err "unsupported platform: #{Micro.platform}"
38 end
39 end
40
41 # Returns the configuration needed to run the micro related subcommands.
42 # First loads saved config from file (if there is any), then overrides
43 # loaded values with command line arguments, and finally tries to guess
44 # in case neither was used:
45 # vmx location of micro.vmx file
46 # vmrun location of vmrun command
47 # password password for vcap user (in the guest vm)
48 # platform current platform
49 def build_config
50 conf = VMC::Cli::Config.micro # returns {} if there isn't a saved config
51
52 override(conf, 'vmx', true) do
53 locate_vmx(Micro.platform)
54 end
55
56 override(conf, 'vmrun', true) do
57 VMC::Micro::VMrun.locate(Micro.platform)
58 end
59
60 override(conf, 'password') do
61 @password = ask("Please enter your Micro Cloud Foundry VM password (vcap user) password", :echo => "*")
62 end
63
64 conf['platform'] = Micro.platform
65
66 conf
67 end
68
69 # Save the cleartext password if --save is supplied.
70 # Note: it is due to vix we have to use a cleartext password :(
71 # Only if --password is used and not --save is the password deleted from the
72 # config file before it is stored to disk.
73 def store_config(config)
74 if @options[:save]
75 warn("cleartext password saved in: #{VMC::Cli::Config::MICRO_FILE}")
76 elsif @options[:password] || @password
77 config.delete('password')
78 end
79
80 VMC::Cli::Config.store_micro(config)
81 end
82
83 # override with command line arguments and yield the block in case the option isn't set
84 def override(config, option, escape=false, &blk)
85 # override if given on the command line
86 if opt = @options[option.to_sym]
87 opt = VMC::Micro.escape_path(opt) if escape
88 config[option] = opt
89 end
90 config[option] = yield unless config[option]
91 end
92
93 def locate_vmx(platform)
94 paths = YAML.load_file(VMC::Micro.config_file('paths.yml'))
95 vmx_paths = paths[platform.to_s]['vmx']
96 vmx = VMC::Micro.locate_file('micro.vmx', 'micro', vmx_paths)
97 err "Unable to locate micro.vmx, please supply --vmx option" unless vmx
98 vmx
99 end
100
101 def self.platform
102 case RUBY_PLATFORM
103 when /darwin/ # x86_64-darwin11.2.0
104 :darwin
105 when /linux/ # x86_64-linux
106 :linux
107 when /mingw|mswin32|cygwin/ # i386-mingw32
108 :windows
109 else
110 RUBY_PLATFORM
111 end
112 end
113
114 end
115end
0116
=== modified file 'lib/cli/commands/misc.rb'
--- lib/cli/commands/misc.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/misc.rb 2012-06-13 01:37:18 +0000
@@ -30,14 +30,15 @@
30 client = VMC::Client.new(target_url)30 client = VMC::Client.new(target_url)
31 unless client.target_valid?31 unless client.target_valid?
32 if prompt_ok32 if prompt_ok
33 display "Host is not valid: '#{target_url}'".red33 display "Host is not available or is not valid: '#{target_url}'".red
34 show_response = ask "Would you like see the response [yN]? "34 show_response = ask "Would you like see the response?",
35 display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response.upcase == 'Y'35 :default => false
36 display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response
36 end37 end
37 exit(false)38 exit(false)
38 else39 else
39 VMC::Cli::Config.store_target(target_url)40 VMC::Cli::Config.store_target(target_url)
40 say "Succesfully targeted to [#{target_url}]".green41 say "Successfully targeted to [#{target_url}]".green
41 end42 end
42 end43 end
4344
4445
=== modified file 'lib/cli/commands/services.rb'
--- lib/cli/commands/services.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/services.rb 2012-06-13 01:37:18 +0000
@@ -1,11 +1,16 @@
1require "uuidtools"
2
1module VMC::Cli::Command3module VMC::Cli::Command
24
3 class Services < Base5 class Services < Base
4 include VMC::Cli::ServicesHelper6 include VMC::Cli::ServicesHelper
7 include VMC::Cli::TunnelHelper
58
6 def services9 def services
7 ss = client.services_info10 ss = client.services_info
8 ps = client.services11 ps = client.services
12 ps.sort! {|a, b| a[:name] <=> b[:name] }
13
9 if @options[:json]14 if @options[:json]
10 services = { :system => ss, :provisioned => ps }15 services = { :system => ss, :provisioned => ps }
11 return display JSON.pretty_generate(services)16 return display JSON.pretty_generate(services)
@@ -18,15 +23,15 @@
18 unless no_prompt || service23 unless no_prompt || service
19 services = client.services_info24 services = client.services_info
20 err 'No services available to provision' if services.empty?25 err 'No services available to provision' if services.empty?
21 choose do |menu|26 service = ask(
22 menu.prompt = 'Please select one you wish to provision: '27 "Which service would you like to provision?",
23 menu.select_by = :index_or_name28 { :indexed => true,
24 services.each do |service_type, value|29 :choices =>
25 value.each do |vendor, version|30 services.values.collect { |type|
26 menu.choice(vendor.to_s) { service = vendor.to_s }31 type.keys.collect(&:to_s)
27 end32 }.flatten
28 end33 }
29 end34 )
30 end35 end
31 name = @options[:name] unless name36 name = @options[:name] unless name
32 unless name37 unless name
@@ -42,13 +47,12 @@
42 unless no_prompt || service47 unless no_prompt || service
43 user_services = client.services48 user_services = client.services
44 err 'No services available to delete' if user_services.empty?49 err 'No services available to delete' if user_services.empty?
45 choose do |menu|50 service = ask(
46 menu.prompt = 'Please select one you wish to delete: '51 "Which service would you like to delete?",
47 menu.select_by = :index_or_name52 { :indexed => true,
48 user_services.each do |s|53 :choices => user_services.collect { |s| s[:name] }
49 menu.choice(s[:name]) { service = s[:name] }54 }
50 end55 )
51 end
52 end56 end
53 err "Service name required." unless service57 err "Service name required." unless service
54 display "Deleting service [#{service}]: ", false58 display "Deleting service [#{service}]: ", false
@@ -80,5 +84,97 @@
80 check_app_for_restart(dest_app)84 check_app_for_restart(dest_app)
81 end85 end
8286
87 def tunnel(service=nil, client_name=nil)
88 unless defined? Caldecott
89 display "To use `vmc tunnel', you must first install Caldecott:"
90 display ""
91 display "\tgem install caldecott"
92 display ""
93 display "Note that you'll need a C compiler. If you're on OS X, Xcode"
94 display "will provide one. If you're on Windows, try DevKit."
95 display ""
96 display "This manual step will be removed in the future."
97 display ""
98 err "Caldecott is not installed."
99 end
100
101 ps = client.services
102 err "No services available to tunnel to" if ps.empty?
103
104 unless service
105 choices = ps.collect { |s| s[:name] }.sort
106 service = ask(
107 "Which service to tunnel to?",
108 :choices => choices,
109 :indexed => true
110 )
111 end
112
113 info = ps.select { |s| s[:name] == service }.first
114
115 err "Unknown service '#{service}'" unless info
116
117 port = pick_tunnel_port(@options[:port] || 10000)
118
119 raise VMC::Client::AuthError unless client.logged_in?
120
121 if not tunnel_pushed?
122 display "Deploying tunnel application '#{tunnel_appname}'."
123 auth = UUIDTools::UUID.random_create.to_s
124 push_caldecott(auth)
125 bind_service_banner(service, tunnel_appname, false)
126 start_caldecott
127 else
128 auth = tunnel_auth
129 end
130
131 if not tunnel_healthy?(auth)
132 display "Redeploying tunnel application '#{tunnel_appname}'."
133
134 # We don't expect caldecott not to be running, so take the
135 # most aggressive restart method.. delete/re-push
136 client.delete_app(tunnel_appname)
137 invalidate_tunnel_app_info
138
139 push_caldecott(auth)
140 bind_service_banner(service, tunnel_appname, false)
141 start_caldecott
142 end
143
144 if not tunnel_bound?(service)
145 bind_service_banner(service, tunnel_appname)
146 end
147
148 conn_info = tunnel_connection_info info[:vendor], service, auth
149 display_tunnel_connection_info(conn_info)
150 display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
151 start_tunnel(port, conn_info, auth)
152
153 clients = get_clients_for(info[:vendor])
154
155 if clients.empty?
156 client_name ||= "none"
157 else
158 client_name ||= ask(
159 "Which client would you like to start?",
160 :choices => ["none"] + clients.keys,
161 :indexed => true
162 )
163 end
164
165 if client_name == "none"
166 wait_for_tunnel_end
167 else
168 wait_for_tunnel_start(port)
169 unless start_local_prog(clients, client_name, conn_info, port)
170 err "'#{client_name}' execution failed; is it in your $PATH?"
171 end
172 end
173 end
174
175 def get_clients_for(type)
176 conf = VMC::Cli::Config.clients
177 conf[type] || {}
178 end
83 end179 end
84end180end
85181
=== modified file 'lib/cli/commands/user.rb'
--- lib/cli/commands/user.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/commands/user.rb 2012-06-13 01:37:18 +0000
@@ -12,19 +12,24 @@
12 def login(email=nil)12 def login(email=nil)
13 email = @options[:email] unless email13 email = @options[:email] unless email
14 password = @options[:password]14 password = @options[:password]
15 tries = 015 tries ||= 0
16 email = ask("Email: ") unless no_prompt || email16
17 password = ask("Password: ") {|q| q.echo = '*'} unless no_prompt || password17 unless no_prompt
18 display "Attempting login to [#{target_url}]" if target_url
19 email ||= ask("Email")
20 password ||= ask("Password", :echo => "*")
21 end
22
18 err "Need a valid email" unless email23 err "Need a valid email" unless email
19 err "Need a password" unless password24 err "Need a password" unless password
20 login_and_save_token(email, password)25 login_and_save_token(email, password)
21 say "Successfully logged into [#{target_url}]".green26 say "Successfully logged into [#{target_url}]".green
22 rescue VMC::Client::TargetError27 rescue VMC::Client::TargetError
23 display "Problem with login, invalid account or password.".red28 display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
24 retry if (tries += 1) < 3 && prompt_ok && !@options[:password]29 retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
25 exit 130 exit 1
26 rescue => e31 rescue => e
27 display "Problem with login, #{e}, try again or register for an account.".red32 display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
28 exit 133 exit 1
29 end34 end
3035
@@ -39,8 +44,8 @@
39 err "Need to be logged in to change password." unless email44 err "Need to be logged in to change password." unless email
40 say "Changing password for '#{email}'\n"45 say "Changing password for '#{email}'\n"
41 unless no_prompt46 unless no_prompt
42 password = ask("New Password: ") {|q| q.echo = '*'}47 password = ask "New Password", :echo => "*"
43 password2 = ask("Verify Password: ") {|q| q.echo = '*'}48 password2 = ask "Verify Password", :echo => "*"
44 err "Passwords did not match, try again" if password != password249 err "Passwords did not match, try again" if password != password2
45 end50 end
46 err "Password required" unless password51 err "Password required" unless password
@@ -52,7 +57,7 @@
5257
53 def login_and_save_token(email, password)58 def login_and_save_token(email, password)
54 token = client.login(email, password)59 token = client.login(email, password)
55 VMC::Cli::Config.store_token(token)60 VMC::Cli::Config.store_token(token, @options[:token_file])
56 end61 end
5762
58 end63 end
5964
=== modified file 'lib/cli/config.rb'
--- lib/cli/config.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/config.rb 2012-06-13 01:37:18 +0000
@@ -8,56 +8,60 @@
8 class Config8 class Config
99
10 DEFAULT_TARGET = 'api.vcap.me'10 DEFAULT_TARGET = 'api.vcap.me'
11 DEFAULT_SUGGEST = 'vcap.me'
1211
13 TARGET_FILE = '~/.vmc_target'12 TARGET_FILE = '~/.vmc_target'
14 TOKEN_FILE = '~/.vmc_token'13 TOKEN_FILE = '~/.vmc_token'
15 INSTANCES_FILE = '~/.vmc_instances'14 INSTANCES_FILE = '~/.vmc_instances'
16 ALIASES_FILE = '~/.vmc_aliases'15 ALIASES_FILE = '~/.vmc_aliases'
16 CLIENTS_FILE = '~/.vmc_clients'
17 MICRO_FILE = '~/.vmc_micro'
18
19 STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
1720
18 class << self21 class << self
19 attr_accessor :colorize22 attr_accessor :colorize
20 attr_accessor :output23 attr_accessor :output
21 attr_accessor :trace24 attr_accessor :trace
22 attr_accessor :nozip25 attr_accessor :nozip
23 attr_reader :suggest_url
2426
25 def target_url27 def target_url
26 return @target_url if @target_url28 return @target_url if @target_url
27 target_file = File.expand_path(TARGET_FILE)29 target_file = File.expand_path(TARGET_FILE)
28 if File.exists? target_file30 if File.exists? target_file
29 @target_url = File.read(target_file).strip!31 @target_url = lock_and_read(target_file).strip
30 ha = @target_url.split('.')
31 ha.shift
32 @suggest_url = ha.join('.')
33 @suggest_url = DEFAULT_SUGGEST if @suggest_url.empty?
34 else32 else
35 @target_url = DEFAULT_TARGET33 @target_url = DEFAULT_TARGET
36 @suggest_url = DEFAULT_SUGGEST
37 end34 end
38 @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url35 @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
39 @target_url = @target_url.gsub(/\/+$/, '')36 @target_url = @target_url.gsub(/\/+$/, '')
40 @target_url37 @target_url
41 end38 end
4239
40 def base_of(url)
41 url.sub(/^[^\.]+\./, "")
42 end
43
44 def suggest_url
45 @suggest_url ||= base_of(target_url)
46 end
47
43 def store_target(target_host)48 def store_target(target_host)
44 target_file = File.expand_path(TARGET_FILE)49 target_file = File.expand_path(TARGET_FILE)
45 File.open(target_file, 'w+') { |f| f.puts target_host }50 lock_and_write(target_file, target_host)
46 FileUtils.chmod 0600, target_file
47 end51 end
4852
49 def all_tokens53 def all_tokens(token_file_path=nil)
50 token_file = File.expand_path(TOKEN_FILE)54 token_file = File.expand_path(token_file_path || TOKEN_FILE)
51 return nil unless File.exists? token_file55 return nil unless File.exists? token_file
52 contents = File.read(token_file).strip56 contents = lock_and_read(token_file).strip
53 JSON.parse(contents)57 JSON.parse(contents)
54 end58 end
5559
56 alias :targets :all_tokens60 alias :targets :all_tokens
5761
58 def auth_token62 def auth_token(token_file_path=nil)
59 return @token if @token63 return @token if @token
60 tokens = all_tokens64 tokens = all_tokens(token_file_path)
61 @token = tokens[target_url] if tokens65 @token = tokens[target_url] if tokens
62 end66 end
6367
@@ -65,24 +69,23 @@
65 FileUtils.rm_f(File.expand_path(TOKEN_FILE))69 FileUtils.rm_f(File.expand_path(TOKEN_FILE))
66 end70 end
6771
68 def store_token(token)72 def store_token(token, token_file_path=nil)
69 tokens = all_tokens || {}73 tokens = all_tokens(token_file_path) || {}
70 tokens[target_url] = token74 tokens[target_url] = token
71 token_file = File.expand_path(TOKEN_FILE)75 token_file = File.expand_path(token_file_path || TOKEN_FILE)
72 File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }76 lock_and_write(token_file, tokens.to_json)
73 FileUtils.chmod 0600, token_file
74 end77 end
7578
76 def instances79 def instances
77 instances_file = File.expand_path(INSTANCES_FILE)80 instances_file = File.expand_path(INSTANCES_FILE)
78 return nil unless File.exists? instances_file81 return nil unless File.exists? instances_file
79 contents = File.read(instances_file).strip82 contents = lock_and_read(instances_file).strip
80 JSON.parse(contents)83 JSON.parse(contents)
81 end84 end
8285
83 def store_instances(instances)86 def store_instances(instances)
84 instances_file = File.expand_path(INSTANCES_FILE)87 instances_file = File.expand_path(INSTANCES_FILE)
85 File.open(instances_file, 'w') { |f| f.write(instances.to_json) }88 lock_and_write(instances_file, instances.to_json)
86 end89 end
8790
88 def aliases91 def aliases
@@ -100,6 +103,66 @@
100 File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}103 File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
101 end104 end
102105
106 def micro
107 micro_file = File.expand_path(MICRO_FILE)
108 return {} unless File.exists? micro_file
109 contents = lock_and_read(micro_file).strip
110 JSON.parse(contents)
111 end
112
113 def store_micro(micro)
114 micro_file = File.expand_path(MICRO_FILE)
115 lock_and_write(micro_file, micro.to_json)
116 end
117
118 def deep_merge(a, b)
119 merge = proc do |_, old, new|
120 if new.is_a?(Hash) and old.is_a?(Hash)
121 old.merge(new, &merge)
122 else
123 new
124 end
125 end
126
127 a.merge(b, &merge)
128 end
129
130 def clients
131 return @clients if @clients
132
133 stock = YAML.load_file(STOCK_CLIENTS)
134 clients = File.expand_path CLIENTS_FILE
135 if File.exists? clients
136 user = YAML.load_file(clients)
137 @clients = deep_merge(stock, user)
138 else
139 @clients = stock
140 end
141 end
142
143 def lock_and_read(file)
144 File.open(file, File::RDONLY) {|f|
145 if defined? JRUBY_VERSION
146 f.flock(File::LOCK_SH)
147 else
148 f.flock(File::LOCK_EX)
149 end
150 contents = f.read
151 f.flock(File::LOCK_UN)
152 contents
153 }
154 end
155
156 def lock_and_write(file, contents)
157 File.open(file, File::RDWR | File::CREAT, 0600) {|f|
158 f.flock(File::LOCK_EX)
159 f.rewind
160 f.puts contents
161 f.flush
162 f.truncate(f.pos)
163 f.flock(File::LOCK_UN)
164 }
165 end
103 end166 end
104167
105 def initialize(work_dir = Dir.pwd)168 def initialize(work_dir = Dir.pwd)
106169
=== added file 'lib/cli/console_helper.rb'
--- lib/cli/console_helper.rb 1970-01-01 00:00:00 +0000
+++ lib/cli/console_helper.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,160 @@
1require 'net/telnet'
2require 'readline'
3
4module VMC::Cli
5 module ConsoleHelper
6
7 def console_connection_info(appname)
8 app = client.app_info(appname)
9 fw = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model])
10 if !fw.console
11 err "'#{appname}' is a #{fw.name} application. " +
12 "Console access is not supported for #{fw.name} applications."
13 end
14 instances_info_envelope = client.app_instances(appname)
15 instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
16
17 instances_info = instances_info_envelope[:instances] || []
18 err "No running instances for [#{appname}]" if instances_info.empty?
19
20 entry = instances_info[0]
21 if !entry[:console_port]
22 begin
23 client.app_files(appname, '/app/cf-rails-console')
24 err "Console port not provided for [#{appname}]. Try restarting the app."
25 rescue VMC::Client::TargetError, VMC::Client::NotFound
26 err "Console access not supported for [#{appname}]. " +
27 "Please redeploy your app to enable support."
28 end
29 end
30 conn_info = {'hostname' => entry[:console_ip], 'port' => entry[:console_port]}
31 end
32
33 def start_local_console(port, appname)
34 auth_info = console_credentials(appname)
35 display "Connecting to '#{appname}' console: ", false
36 prompt = console_login(auth_info, port)
37 display "OK".green
38 display "\n"
39 initialize_readline
40 run_console prompt
41 end
42
43 def console_login(auth_info, port)
44 if !auth_info["username"] || !auth_info["password"]
45 err "Unable to verify console credentials."
46 end
47 @telnet_client = telnet_client(port)
48 prompt = nil
49 err_msg = "Login attempt timed out."
50 5.times do
51 begin
52 results = @telnet_client.login("Name"=>auth_info["username"],
53 "Password"=>auth_info["password"])
54 lines = results.sub("Login: Password: ", "").split("\n")
55 last_line = lines.pop
56 if last_line =~ /[$%#>] \z/n
57 prompt = last_line
58 elsif last_line =~ /Login failed/
59 err_msg = last_line
60 end
61 break
62 rescue TimeoutError
63 sleep 1
64 rescue EOFError
65 #This may happen if we login right after app starts
66 close_console
67 sleep 5
68 @telnet_client = telnet_client(port)
69 end
70 display ".", false
71 end
72 unless prompt
73 close_console
74 err err_msg
75 end
76 prompt
77 end
78
79 def send_console_command(cmd)
80 results = @telnet_client.cmd(cmd)
81 results.split("\n")
82 end
83
84 def console_credentials(appname)
85 content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
86 YAML.load(content)
87 end
88
89 def close_console
90 @telnet_client.close
91 end
92
93 def console_tab_completion_data(cmd)
94 begin
95 results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
96 results.chomp.split(",")
97 rescue TimeoutError
98 [] #Just return empty results if timeout occurred on tab completion
99 end
100 end
101
102 private
103 def telnet_client(port)
104 Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
105 end
106
107 def readline_with_history(prompt)
108 line = Readline::readline(prompt)
109 return nil if line == nil || line == 'quit' || line == 'exit'
110 Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
111 line
112 end
113
114 def run_console(prompt)
115 prev = trap("INT") { |x| exit_console; prev.call(x); exit }
116 prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
117 loop do
118 cmd = readline_with_history(prompt)
119 if(cmd == nil)
120 exit_console
121 break
122 end
123 prompt = send_console_command_display_results(cmd, prompt)
124 end
125 end
126
127 def exit_console
128 #TimeoutError expected, as exit doesn't return anything
129 @telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
130 close_console
131 end
132
133 def send_console_command_display_results(cmd, prompt)
134 begin
135 lines = send_console_command cmd
136 #Assumes the last line is a prompt
137 prompt = lines.pop
138 lines.each {|line| display line if line != cmd}
139 rescue TimeoutError
140 display "Timed out sending command to server.".red
141 rescue EOFError
142 err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
143 end
144 prompt
145 end
146
147 def initialize_readline
148 if Readline.respond_to?("basic_word_break_characters=")
149 Readline.basic_word_break_characters= " \t\n`><=;|&{("
150 end
151 Readline.completion_append_character = nil
152 #Assumes that sending a String ending with tab will return a non-empty
153 #String of comma-separated completion options, terminated by a new line
154 #For example, "app.\t" might result in "to_s,nil?,etc\n"
155 Readline.completion_proc = proc {|s|
156 console_tab_completion_data s
157 }
158 end
159 end
160end
0161
=== modified file 'lib/cli/core_ext.rb'
--- lib/cli/core_ext.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/core_ext.rb 2012-06-13 01:37:18 +0000
@@ -38,6 +38,10 @@
38 raise VMC::Cli::CliExit, "#{prefix}#{message}"38 raise VMC::Cli::CliExit, "#{prefix}#{message}"
39 end39 end
4040
41 def warn(msg)
42 say "#{"[WARNING]".yellow} #{msg}"
43 end
44
41 def quit(message = nil)45 def quit(message = nil)
42 raise VMC::Cli::GracefulExit, message46 raise VMC::Cli::GracefulExit, message
43 end47 end
@@ -64,7 +68,6 @@
64 return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)68 return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
65 return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))69 return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
66 end70 end
67
68end71end
6972
70module VMCStringExtensions73module VMCStringExtensions
7174
=== modified file 'lib/cli/frameworks.rb'
--- lib/cli/frameworks.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/frameworks.rb 2012-06-13 01:37:18 +0000
@@ -6,47 +6,78 @@
6 DEFAULT_MEM = '256M'6 DEFAULT_MEM = '256M'
77
8 FRAMEWORKS = {8 FRAMEWORKS = {
9 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application'}],9 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application', :console=>true}],
10 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],10 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
11 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],11 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
12 'Roo' => ['spring', { :mem => '512M', :description => 'Java SpringSource Roo Application'}],12 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
13 'JavaWeb' => ['spring', { :mem => '512M', :description => 'Java Web Application'}],13 'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}],
14 'Standalone' => ['standalone', { :mem => '64M', :description => 'Standalone Application'}],
14 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],15 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
15 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],16 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],
17 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
18 'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '64M', :description => 'Erlang/OTP Rebar Application'}],
19 'WSGI' => ['wsgi', { :mem => '64M', :description => 'Python WSGI Application'}],
20 'Django' => ['django', { :mem => '128M', :description => 'Python Django Application'}],
21 'dotNet' => ['dotNet', { :mem => '128M', :description => '.Net Web Application'}],
22 'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}],
23 'Play' => ['play', { :mem => '256M', :description => 'Play Framework Application'}]
16 }24 }
1725
18 class << self26 class << self
1927
20 def known_frameworks28 def known_frameworks(available_frameworks)
21 FRAMEWORKS.keys29 frameworks = []
30 FRAMEWORKS.each do |key,fw|
31 frameworks << key if available_frameworks.include? [fw[0]]
32 end
33 frameworks
22 end34 end
2335
24 def lookup(name)36 def lookup(name)
25 return Framework.new(*FRAMEWORKS[name])37 return create(*FRAMEWORKS[name])
26 end38 end
2739
28 def detect(path)40 def lookup_by_framework(name)
41 FRAMEWORKS.each do |key,fw|
42 return create(fw[0],fw[1]) if fw[0] == name
43 end
44 end
45
46 def create(name,opts)
47 if name == "standalone"
48 return StandaloneFramework.new(name, opts)
49 else
50 return Framework.new(name,opts)
51 end
52 end
53
54 def detect(path, available_frameworks)
55 if !File.directory? path
56 if path.end_with?('.war')
57 return detect_framework_from_war path
58 elsif path.end_with?('.zip')
59 return detect_framework_from_zip path, available_frameworks
60 elsif available_frameworks.include?(["standalone"])
61 return Framework.lookup('Standalone')
62 else
63 return nil
64 end
65 end
29 Dir.chdir(path) do66 Dir.chdir(path) do
30
31 # Rails67 # Rails
32 if File.exist?('config/environment.rb')68 if File.exist?('config/environment.rb')
33 return Framework.lookup('Rails')69 return Framework.lookup('Rails')
3470
35 # Java71 # Rack
72 elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
73 return Framework.lookup('Rack')
74
75 # Java Web Apps
36 elsif Dir.glob('*.war').first76 elsif Dir.glob('*.war').first
37 war_file = Dir.glob('*.war').first77 return detect_framework_from_war(Dir.glob('*.war').first)
38 contents = ZipUtil.entry_lines(war_file)
3978
40 # Spring Variations79 elsif File.exist?('WEB-INF/web.xml')
41 if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/80 return detect_framework_from_war
42 return Framework.lookup('Grails')
43 elsif contents =~ /WEB-INF\/classes\/org\/springframework/
44 return Framework.lookup('Spring')
45 elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
46 return Framework.lookup('Spring')
47 else
48 return Framework.lookup('JavaWeb')
49 end
5081
51 # Simple Ruby Apps82 # Simple Ruby Apps
52 elsif !Dir.glob('*.rb').empty?83 elsif !Dir.glob('*.rb').empty?
@@ -55,43 +86,180 @@
55 next if matched_file86 next if matched_file
56 File.open(fname, 'r') do |f|87 File.open(fname, 'r') do |f|
57 str = f.read # This might want to be limited88 str = f.read # This might want to be limited
58 matched_file = fname if (str && str.match(/^\s*require\s*['"]sinatra['"]/))89 matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra['"]/))
59 end90 end
60 end91 end
61 if matched_file92 if matched_file
93 # Sinatra apps
62 f = Framework.lookup('Sinatra')94 f = Framework.lookup('Sinatra')
63 f.exec = "ruby #{matched_file}"95 f.exec = "ruby #{matched_file}"
64 return f96 return f
65 end97 end
6698
99 # PHP
100 elsif !Dir.glob('*.php').empty?
101 return Framework.lookup('PHP')
102
103 # Erlang/OTP using Rebar
104 elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
105 return Framework.lookup('Erlang/OTP Rebar')
106
107 # Python Django
108 # XXX: not all django projects keep settings.py in top-level directory
109 elsif File.exist?('manage.py') && File.exist?('settings.py')
110 return Framework.lookup('Django')
111
112 # Python
113 elsif !Dir.glob('wsgi.py').empty?
114 return Framework.lookup('WSGI')
115
116 # .Net
117 elsif !Dir.glob('web.config').empty?
118 return Framework.lookup('dotNet')
119
67 # Node.js120 # Node.js
68 elsif !Dir.glob('*.js').empty?121 elsif !Dir.glob('*.js').empty?
69 # Fixme, make other files work too..122 if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
70 if File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
71 return Framework.lookup('Node')123 return Framework.lookup('Node')
72 end124 end
125
126 # Play or Standalone Apps
127 elsif Dir.glob('*.zip').first
128 zip_file = Dir.glob('*.zip').first
129 return detect_framework_from_zip zip_file, available_frameworks
73 end130 end
74 end131
75 nil132 # Default to Standalone if no other match was made
76 end133 return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"])
77134 end
135 end
136
137 def detect_framework_from_war(war_file=nil)
138 if war_file
139 contents = ZipUtil.entry_lines(war_file)
140 else
141 #assume we are working with current dir
142 contents = Dir['**/*'].join("\n")
143 end
144
145 # Spring/Lift Variations
146 if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
147 return Framework.lookup('Grails')
148 elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
149 return Framework.lookup('Lift')
150 elsif contents =~ /WEB-INF\/classes\/org\/springframework/
151 return Framework.lookup('Spring')
152 elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
153 return Framework.lookup('Spring')
154 elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
155 return Framework.lookup('Spring')
156 else
157 return Framework.lookup('JavaWeb')
158 end
159 end
160
161 def detect_framework_from_zip(zip_file, available_frameworks)
162 contents = ZipUtil.entry_lines(zip_file)
163 detect_framework_from_zip_contents(contents, available_frameworks)
164 end
165
166 def detect_framework_from_zip_contents(contents, available_frameworks)
167 if available_frameworks.include?(["play"]) && contents =~ /lib\/play\..*\.jar/
168 return Framework.lookup('Play')
169 elsif available_frameworks.include?(["standalone"])
170 return Framework.lookup('Standalone')
171 end
172 end
78 end173 end
79174
80 attr_reader :name, :description, :memory175 attr_reader :name, :description, :console
81 attr_accessor :exec176 attr_accessor :exec
82177
83 alias :mem :memory
84
85 def initialize(framework=nil, opts={})178 def initialize(framework=nil, opts={})
86 @name = framework || DEFAULT_FRAMEWORK179 @name = framework || DEFAULT_FRAMEWORK
87 @memory = opts[:mem] || DEFAULT_MEM180 @memory = opts[:mem] || DEFAULT_MEM
88 @description = opts[:description] || 'Unknown Application Type'181 @description = opts[:description] || 'Unknown Application Type'
89 @exec = opts[:exec]182 @exec = opts[:exec]
183 @console = opts[:console] || false
90 end184 end
91185
92 def to_s186 def to_s
93 description187 description
94 end188 end
189
190 def require_url?
191 true
192 end
193
194 def require_start_command?
195 false
196 end
197
198 def prompt_for_runtime?
199 false
200 end
201
202 def default_runtime(path)
203 nil
204 end
205
206 def memory(runtime=nil)
207 @memory
208 end
209
210 alias :mem :memory
211 end
212
213 class StandaloneFramework < Framework
214 def require_url?
215 false
216 end
217
218 def require_start_command?
219 true
220 end
221
222 def prompt_for_runtime?
223 true
224 end
225
226 def default_runtime(path)
227 if !File.directory? path
228 if path =~ /\.(jar|class)$/
229 return "java"
230 elsif path =~ /\.(rb)$/
231 return "ruby18"
232 elsif path =~ /\.(zip)$/
233 return detect_runtime_from_zip path
234 end
235 else
236 Dir.chdir(path) do
237 return "ruby18" if not Dir.glob('**/*.rb').empty?
238 if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty?
239 return "java"
240 elsif Dir.glob('*.zip').first
241 zip_file = Dir.glob('*.zip').first
242 return detect_runtime_from_zip zip_file
243 end
244 end
245 end
246 return nil
247 end
248
249 def memory(runtime=nil)
250 default_mem = @memory
251 default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php"
252 default_mem = '512M' if runtime == "java" || runtime == "java7"
253 default_mem
254 end
255
256 private
257 def detect_runtime_from_zip(zip_file)
258 contents = ZipUtil.entry_lines(zip_file)
259 if contents =~ /\.(jar)$/
260 return "java"
261 end
262 end
95 end263 end
96264
97end265end
98266
=== added file 'lib/cli/manifest_helper.rb'
--- lib/cli/manifest_helper.rb 1970-01-01 00:00:00 +0000
+++ lib/cli/manifest_helper.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,302 @@
1require "set"
2
3module VMC::Cli::ManifestHelper
4 include VMC::Cli::ServicesHelper
5
6 DEFAULTS = {
7 "url" => "${name}.${target-base}",
8 "mem" => "128M",
9 "instances" => 1
10 }
11
12 MANIFEST = "manifest.yml"
13
14 YES_SET = Set.new(["y", "Y", "yes", "YES"])
15
16 # take a block and call it once for each app to push/update.
17 # with @application and @app_info set appropriately
18 def each_app(panic=true)
19 if @manifest and all_apps = @manifest["applications"]
20 where = File.expand_path(@path)
21 single = false
22
23 all_apps.each do |path, info|
24 app = File.expand_path("../" + path, manifest_file)
25 if where.start_with?(app)
26 @application = app
27 @app_info = info
28 yield info["name"]
29 single = true
30 break
31 end
32 end
33
34 unless single
35 if where == File.expand_path("../", manifest_file)
36 ordered_by_deps(all_apps).each do |path, info|
37 app = File.expand_path("../" + path, manifest_file)
38 @application = app
39 @app_info = info
40 yield info["name"]
41 end
42 else
43 err "Path '#{@path}' is not known to manifest '#{manifest_file}'."
44 end
45 end
46 else
47 @application = @path
48 @app_info = @manifest
49 if @app_info
50 yield @app_info["name"]
51 elsif panic
52 err "No applications."
53 end
54 end
55
56 nil
57 ensure
58 @application = nil
59 @app_info = nil
60 end
61
62 def interact(many=false)
63 @manifest ||= {}
64 configure_app(many)
65 end
66
67 def target_manifest
68 @options[:manifest] || MANIFEST
69 end
70
71 def save_manifest(save_to = nil)
72 save_to ||= target_manifest
73
74 File.open(save_to, "w") do |f|
75 f.write @manifest.to_yaml
76 end
77
78 say "Manifest written to #{save_to}."
79 end
80
81 def configure_app(many=false)
82 name = manifest("name") ||
83 set(ask("Application Name", :default => manifest("name")), "name")
84
85
86
87 if manifest "framework"
88 framework = VMC::Cli::Framework.lookup_by_framework manifest("framework","name")
89 else
90 framework = detect_framework
91 set framework.name, "framework", "name"
92 set(
93 { "mem" => framework.mem,
94 "description" => framework.description,
95 "exec" => framework.exec
96 },
97 "framework",
98 "info"
99 )
100 end
101
102 default_runtime = manifest "runtime"
103 if not default_runtime
104 default_runtime = framework.default_runtime(@application)
105 set(detect_runtime(default_runtime), "runtime") if framework.prompt_for_runtime?
106 end
107 default_command = manifest "command"
108 set ask("Start Command", :default => default_command), "command" if framework.require_start_command?
109
110 url_template = manifest("url") || DEFAULTS["url"]
111 url_resolved = url_template.dup
112 resolve_lexically(url_resolved)
113
114 if !framework.require_url?
115 url_resolved = "None"
116 end
117 url = ask("Application Deployed URL", :default => url_resolved)
118
119 if url == url_resolved && url != "None"
120 url = url_template
121 end
122
123 # common error case is for prompted users to answer y or Y or yes or
124 # YES to this ask() resulting in an unintended URL of y. Special
125 # case this common error
126 url = url_resolved if YES_SET.member? url
127
128 if(url == "None")
129 url = nil
130 end
131
132 set url, "url"
133
134 default_mem = manifest("mem")
135 default_mem = framework.memory(manifest("runtime")) if not default_mem
136 set ask(
137 "Memory reservation",
138 :default =>
139 default_mem ||
140 DEFAULTS["mem"],
141 :choices => ["128M", "256M", "512M", "1G", "2G"]
142 ), "mem"
143
144 set ask(
145 "How many instances?",
146 :default => manifest("instances") || DEFAULTS["instances"]
147 ), "instances"
148
149 unless manifest "services"
150 user_services = client.services
151 user_services.sort! {|a, b| a[:name] <=> b[:name] }
152
153 unless user_services.empty?
154 if ask "Bind existing services to '#{name}'?", :default => false
155 bind_services(user_services)
156 end
157 end
158
159 services = client.services_info
160 unless services.empty?
161 if ask "Create services to bind to '#{name}'?", :default => false
162 create_services(services.values.collect(&:keys).flatten)
163 end
164 end
165 end
166
167 if many and ask("Configure for another application?", :default => false)
168 @application = ask "Application path?"
169 configure_app
170 end
171 end
172
173 def set(what, *where)
174 where.unshift "applications", @application
175
176 which = @manifest
177 where.each_with_index do |k, i|
178 if i + 1 == where.size
179 which[k] = what
180 else
181 which = (which[k] ||= {})
182 end
183 end
184
185 what
186 end
187
188 # Detect the appropriate framework.
189 def detect_framework(prompt_ok = true)
190 framework = VMC::Cli::Framework.detect(@application, frameworks_info)
191 framework_correct = ask("Detected a #{framework}, is this correct?", :default => true) if prompt_ok && framework
192 if prompt_ok && (framework.nil? || !framework_correct)
193 display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
194 framework = nil if !framework_correct
195 framework = VMC::Cli::Framework.lookup(
196 ask(
197 "Select Application Type",
198 :indexed => true,
199 :default => framework,
200 :choices => VMC::Cli::Framework.known_frameworks(frameworks_info)
201 )
202 )
203 display "Selected #{framework}"
204 end
205
206 framework
207 end
208
209 # Detect the appropriate runtime.
210 def detect_runtime(default, prompt_ok=true)
211 runtime = nil
212 runtime_keys=[]
213 runtimes_info.keys.each {|runtime_key| runtime_keys << runtime_key.dup }
214 runtime_keys.sort!
215 if prompt_ok
216 runtime = ask(
217 "Select Runtime",
218 :indexed => true,
219 :default => default,
220 :choices => runtime_keys
221 )
222 display "Selected #{runtime}"
223 end
224 runtime
225 end
226
227 def bind_services(user_services, chosen = 0)
228 svcname = ask(
229 "Which one?",
230 :indexed => true,
231 :choices => user_services.collect { |p| p[:name] })
232
233 svc = user_services.find { |p| p[:name] == svcname }
234
235 set svc[:vendor], "services", svcname, "type"
236
237 if chosen + 1 < user_services.size && ask("Bind another?", :default => false)
238 bind_services(user_services, chosen + 1)
239 end
240 end
241
242 def create_services(services)
243 svcs = services.collect(&:to_s).sort!
244
245 configure_service(
246 ask(
247 "What kind of service?",
248 :indexed => true,
249 :choices => svcs
250 )
251 )
252
253 if ask "Create another?", :default => false
254 create_services(services)
255 end
256 end
257
258 def configure_service(vendor)
259 default_name = random_service_name(vendor)
260 name = ask "Specify the name of the service", :default => default_name
261
262 set vendor, "services", name, "type"
263 end
264
265 private
266 def ordered_by_deps(apps, abspaths = nil, processed = Set[])
267 unless abspaths
268 abspaths = {}
269 apps.each do |p, i|
270 ep = File.expand_path("../" + p, manifest_file)
271 abspaths[ep] = i
272 end
273 end
274
275 ordered = []
276 apps.each do |path, info|
277 epath = File.expand_path("../" + path, manifest_file)
278
279 if deps = info["depends-on"]
280 dep_apps = {}
281 deps.each do |dep|
282 edep = File.expand_path("../" + dep, manifest_file)
283
284 err "Circular dependency detected." if processed.include? edep
285
286 dep_apps[dep] = abspaths[edep]
287 end
288
289 processed.add(epath)
290
291 ordered += ordered_by_deps(dep_apps, abspaths, processed)
292 ordered << [path, info]
293 elsif not processed.include? epath
294 ordered << [path, info]
295 processed.add(epath)
296 end
297 end
298
299 ordered
300 end
301
302end
0303
=== modified file 'lib/cli/runner.rb'
--- lib/cli/runner.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/runner.rb 2012-06-13 01:37:18 +0000
@@ -31,6 +31,7 @@
31 opts.on('--passwd PASS') { |pass| @options[:password] = pass }31 opts.on('--passwd PASS') { |pass| @options[:password] = pass }
32 opts.on('--pass PASS') { |pass| @options[:password] = pass }32 opts.on('--pass PASS') { |pass| @options[:password] = pass }
33 opts.on('--password PASS') { |pass| @options[:password] = pass }33 opts.on('--password PASS') { |pass| @options[:password] = pass }
34 opts.on('--token-file TOKEN_FILE') { |token_file| @options[:token_file] = token_file }
34 opts.on('--app NAME') { |name| @options[:name] = name }35 opts.on('--app NAME') { |name| @options[:name] = name }
35 opts.on('--name NAME') { |name| @options[:name] = name }36 opts.on('--name NAME') { |name| @options[:name] = name }
36 opts.on('--bind BIND') { |bind| @options[:bind] = bind }37 opts.on('--bind BIND') { |bind| @options[:bind] = bind }
@@ -48,8 +49,21 @@
48 opts.on('-t [TKEY]') { |tkey| @options[:trace] = tkey || true }49 opts.on('-t [TKEY]') { |tkey| @options[:trace] = tkey || true }
49 opts.on('--trace [TKEY]') { |tkey| @options[:trace] = tkey || true }50 opts.on('--trace [TKEY]') { |tkey| @options[:trace] = tkey || true }
5051
52 # start application in debug mode
53 opts.on('-d [MODE]') { |mode| @options[:debug] = mode || "run" }
54 opts.on('--debug [MODE]') { |mode| @options[:debug] = mode || "run" }
55
56 # override manifest file
57 opts.on('-m FILE') { |file| @options[:manifest] = file }
58 opts.on('--manifest FILE') { |file| @options[:manifest] = file }
59
51 opts.on('-q', '--quiet') { @options[:quiet] = true }60 opts.on('-q', '--quiet') { @options[:quiet] = true }
5261
62 # micro cloud options
63 opts.on('--vmx FILE') { |file| @options[:vmx] = file }
64 opts.on('--vmrun FILE') { |file| @options[:vmrun] = file }
65 opts.on('--save') { @options[:save] = true }
66
53 # Don't use builtin zip67 # Don't use builtin zip
54 opts.on('--no-zip') { @options[:nozip] = true }68 opts.on('--no-zip') { @options[:nozip] = true }
55 opts.on('--nozip') { @options[:nozip] = true }69 opts.on('--nozip') { @options[:nozip] = true }
@@ -73,6 +87,8 @@
73 opts.on('-v', '--version') { set_cmd(:misc, :version) }87 opts.on('-v', '--version') { set_cmd(:misc, :version) }
74 opts.on('-h', '--help') { puts "#{command_usage}\n"; exit }88 opts.on('-h', '--help') { puts "#{command_usage}\n"; exit }
7589
90 opts.on('--port PORT') { |port| @options[:port] = port }
91
76 opts.on('--runtime RUNTIME') { |rt| @options[:runtime] = rt }92 opts.on('--runtime RUNTIME') { |rt| @options[:runtime] = rt }
7793
78 # deprecated94 # deprecated
@@ -107,7 +123,6 @@
107 def convert_options!123 def convert_options!
108 # make sure certain options are valid and in correct form.124 # make sure certain options are valid and in correct form.
109 @options[:instances] = Integer(@options[:instances]) if @options[:instances]125 @options[:instances] = Integer(@options[:instances]) if @options[:instances]
110 @options[:instance] = Integer(@options[:instance]) if @options[:instance]
111 end126 end
112127
113 def set_cmd(namespace, action, args_range=0)128 def set_cmd(namespace, action, args_range=0)
@@ -204,6 +219,10 @@
204 usage('vmc delete-user <user>')219 usage('vmc delete-user <user>')
205 set_cmd(:admin, :delete_user, 1)220 set_cmd(:admin, :delete_user, 1)
206221
222 when 'users'
223 usage('vmc users')
224 set_cmd(:admin, :users)
225
207 when 'apps'226 when 'apps'
208 usage('vmc apps')227 usage('vmc apps')
209 set_cmd(:apps, :apps)228 set_cmd(:apps, :apps)
@@ -214,19 +233,15 @@
214233
215 when 'start'234 when 'start'
216 usage('vmc start <appname>')235 usage('vmc start <appname>')
217 set_cmd(:apps, :start, 1)236 set_cmd(:apps, :start, @args.size == 1 ? 1 : 0)
218237
219 when 'stop'238 when 'stop'
220 usage('vmc stop <appname>')239 usage('vmc stop <appname>')
221 set_cmd(:apps, :stop, 1)240 set_cmd(:apps, :stop, @args.size == 1 ? 1 : 0)
222241
223 when 'restart'242 when 'restart'
224 usage('vmc restart <appname>')243 usage('vmc restart <appname>')
225 set_cmd(:apps, :restart, 1)244 set_cmd(:apps, :restart, @args.size == 1 ? 1 : 0)
226
227 when 'rename'
228 usage('vmc rename <appname> <newname>')
229 set_cmd(:apps, :rename, 2)
230245
231 when 'mem'246 when 'mem'
232 usage('vmc mem <appname> [memsize]')247 usage('vmc mem <appname> [memsize]')
@@ -238,7 +253,7 @@
238253
239 when 'stats'254 when 'stats'
240 usage('vmc stats <appname>')255 usage('vmc stats <appname>')
241 set_cmd(:apps, :stats, 1)256 set_cmd(:apps, :stats, @args.size == 1 ? 1 : 0)
242257
243 when 'map'258 when 'map'
244 usage('vmc map <appname> <url>')259 usage('vmc map <appname> <url>')
@@ -269,12 +284,12 @@
269 set_cmd(:apps, :logs, 1)284 set_cmd(:apps, :logs, 1)
270285
271 when 'instances', 'scale'286 when 'instances', 'scale'
272 if @args.size == 1287 if @args.size > 1
288 usage('vmc instances <appname> <num|delta>')
289 set_cmd(:apps, :instances, 2)
290 else
273 usage('vmc instances <appname>')291 usage('vmc instances <appname>')
274 set_cmd(:apps, :instances, 1)292 set_cmd(:apps, :instances, 1)
275 else
276 usage('vmc instances <appname> <num|delta>')
277 set_cmd(:apps, :instances, 2)
278 end293 end
279294
280 when 'crashes'295 when 'crashes'
@@ -286,7 +301,7 @@
286 set_cmd(:apps, :crashlogs, 1)301 set_cmd(:apps, :crashlogs, 1)
287302
288 when 'push'303 when 'push'
289 usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--no-start]')304 usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--runtime RUNTIME] [--no-start]')
290 if @args.size == 1305 if @args.size == 1
291 set_cmd(:apps, :push, 1)306 set_cmd(:apps, :push, 1)
292 else307 else
@@ -295,7 +310,7 @@
295310
296 when 'update'311 when 'update'
297 usage('vmc update <appname> [--path PATH]')312 usage('vmc update <appname> [--path PATH]')
298 set_cmd(:apps, :update, 1)313 set_cmd(:apps, :update, @args.size == 1 ? 1 : 0)
299314
300 when 'services'315 when 'services'
301 usage('vmc services')316 usage('vmc services')
@@ -360,6 +375,22 @@
360 usage('vmc unalias <alias>')375 usage('vmc unalias <alias>')
361 set_cmd(:misc, :unalias, 1)376 set_cmd(:misc, :unalias, 1)
362377
378 when 'tunnel'
379 usage('vmc tunnel [servicename] [clientcmd] [--port port]')
380 set_cmd(:services, :tunnel, 0) if @args.size == 0
381 set_cmd(:services, :tunnel, 1) if @args.size == 1
382 set_cmd(:services, :tunnel, 2) if @args.size == 2
383
384 when 'rails-console'
385 usage('vmc rails-console <appname>')
386 set_cmd(:apps, :console, 1)
387
388 when 'micro'
389 usage('vmc micro <online|offline|status> [--password password] [--save] [--vmx file] [--vmrun executable]')
390 if %w[online offline status].include?(@args[0])
391 set_cmd(:micro, @args[0].to_sym, 1)
392 end
393
363 when 'help'394 when 'help'
364 display_help if @args.size == 0395 display_help if @args.size == 0
365 @help_only = true396 @help_only = true
@@ -374,6 +405,14 @@
374 @args = @args.unshift('--options')405 @args = @args.unshift('--options')
375 parse_options!406 parse_options!
376407
408 when 'manifest'
409 usage('vmc manifest')
410 set_cmd(:manifest, :edit)
411
412 when 'extend-manifest'
413 usage('vmc extend-manifest')
414 set_cmd(:manifest, :extend, 1)
415
377 else416 else
378 if verb417 if verb
379 display "vmc: Unknown command [#{verb}]"418 display "vmc: Unknown command [#{verb}]"
@@ -407,8 +446,7 @@
407446
408 def run447 def run
409448
410 trap('TERM') { print "\nInterrupted\n"; exit(false)}449 trap('TERM') { print "\nTerminated\n"; exit(false)}
411 trap('INT') { print "\nInterrupted\n"; exit(false)}
412450
413 parse_options!451 parse_options!
414452
@@ -423,7 +461,8 @@
423 parse_command!461 parse_command!
424462
425 if @namespace && @action463 if @namespace && @action
426 eval("VMC::Cli::Command::#{@namespace.to_s.capitalize}").new(@options).send(@action.to_sym, *@args)464 cmd = VMC::Cli::Command.const_get(@namespace.to_s.capitalize)
465 cmd.new(@options).send(@action, *@args.collect(&:dup))
427 elsif @help_only || @usage466 elsif @help_only || @usage
428 display_usage467 display_usage
429 else468 else
@@ -432,6 +471,10 @@
432 end471 end
433472
434 rescue OptionParser::InvalidOption => e473 rescue OptionParser::InvalidOption => e
474 puts(e.message.red)
475 puts("\n")
476 puts(basic_usage)
477 @exit_status = false
435 rescue OptionParser::AmbiguousOption => e478 rescue OptionParser::AmbiguousOption => e
436 puts(e.message.red)479 puts(e.message.red)
437 puts("\n")480 puts("\n")
@@ -464,7 +507,10 @@
464 puts e.message.red507 puts e.message.red
465 puts e.backtrace508 puts e.backtrace
466 @exit_status = false509 @exit_status = false
467 rescue => e510 rescue Interrupt => e
511 say("\nInterrupted".red)
512 @exit_status = false
513 rescue Exception => e
468 puts e.message.red514 puts e.message.red
469 puts e.backtrace515 puts e.backtrace
470 @exit_status = false516 @exit_status = false
471517
=== modified file 'lib/cli/services_helper.rb'
--- lib/cli/services_helper.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/services_helper.rb 2012-06-13 01:37:18 +0000
@@ -8,15 +8,19 @@
88
9 return display "No system services available" if services.empty?9 return display "No system services available" if services.empty?
1010
11 displayed_services = []
12 services.each do |service_type, value|
13 value.each do |vendor, version|
14 version.each do |version_str, service|
15 displayed_services << [ vendor, version_str, service[:description] ]
16 end
17 end
18 end
19 displayed_services.sort! { |a, b| a.first.to_s <=> b.first.to_s}
20
11 services_table = table do |t|21 services_table = table do |t|
12 t.headings = 'Service', 'Version', 'Description'22 t.headings = 'Service', 'Version', 'Description'
13 services.each do |service_type, value|23 displayed_services.each { |s| t << s }
14 value.each do |vendor, version|
15 version.each do |version_str, service|
16 t << [ vendor, version_str, service[:description] ]
17 end
18 end
19 end
20 end24 end
21 display services_table25 display services_table
22 end26 end
@@ -46,19 +50,25 @@
46 end50 end
4751
48 def bind_service_banner(service, appname, check_restart=true)52 def bind_service_banner(service, appname, check_restart=true)
49 display "Binding Service: ", false53 display "Binding Service [#{service}]: ", false
50 client.bind_service(service, appname)54 client.bind_service(service, appname)
51 display 'OK'.green55 display 'OK'.green
52 check_app_for_restart(appname) if check_restart56 check_app_for_restart(appname) if check_restart
53 end57 end
5458
55 def unbind_service_banner(service, appname, check_restart=true)59 def unbind_service_banner(service, appname, check_restart=true)
56 display "Unbinding Service: ", false60 display "Unbinding Service [#{service}]: ", false
57 client.unbind_service(service, appname)61 client.unbind_service(service, appname)
58 display 'OK'.green62 display 'OK'.green
59 check_app_for_restart(appname) if check_restart63 check_app_for_restart(appname) if check_restart
60 end64 end
6165
66 def delete_service_banner(service)
67 display "Deleting service [#{service}]: ", false
68 client.delete_service(service)
69 display 'OK'.green
70 end
71
62 def random_service_name(service)72 def random_service_name(service)
63 r = "%04x" % [rand(0x0100000)]73 r = "%04x" % [rand(0x0100000)]
64 "#{service.to_s}-#{r}"74 "#{service.to_s}-#{r}"
6575
=== added file 'lib/cli/tunnel_helper.rb'
--- lib/cli/tunnel_helper.rb 1970-01-01 00:00:00 +0000
+++ lib/cli/tunnel_helper.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,332 @@
1# Copyright (c) 2009-2011 VMware, Inc.
2
3require 'addressable/uri'
4
5begin
6 require 'caldecott'
7rescue LoadError
8end
9
10module VMC::Cli
11 module TunnelHelper
12 PORT_RANGE = 10
13
14 HELPER_APP = File.expand_path("../../../caldecott_helper", __FILE__)
15
16 # bump this AND the version info reported by HELPER_APP/server.rb
17 # this is to keep the helper in sync with any updates here
18 HELPER_VERSION = '0.0.4'
19
20 def tunnel_uniquename
21 random_service_name(tunnel_appname)
22 end
23
24 def tunnel_appname
25 "caldecott"
26 end
27
28 def tunnel_app_info
29 return @tun_app_info if @tunnel_app_info
30 begin
31 @tun_app_info = client.app_info(tunnel_appname)
32 rescue => e
33 @tun_app_info = nil
34 end
35 end
36
37 def tunnel_auth
38 tunnel_app_info[:env].each do |e|
39 name, val = e.split("=", 2)
40 return val if name == "CALDECOTT_AUTH"
41 end
42 nil
43 end
44
45 def tunnel_url
46 return @tunnel_url if @tunnel_url
47
48 tun_url = tunnel_app_info[:uris][0]
49
50 ["https", "http"].each do |scheme|
51 url = "#{scheme}://#{tun_url}"
52 begin
53 RestClient.get(url)
54
55 # https failed
56 rescue Errno::ECONNREFUSED
57
58 # we expect a 404 since this request isn't auth'd
59 rescue RestClient::ResourceNotFound
60 return @tunnel_url = url
61 end
62 end
63
64 err "Cannot determine URL for #{tun_url}"
65 end
66
67 def invalidate_tunnel_app_info
68 @tunnel_url = nil
69 @tunnel_app_info = nil
70 end
71
72 def tunnel_pushed?
73 not tunnel_app_info.nil?
74 end
75
76 def tunnel_healthy?(token)
77 return false unless tunnel_app_info[:state] == 'STARTED'
78
79 begin
80 response = RestClient.get(
81 "#{tunnel_url}/info",
82 "Auth-Token" => token
83 )
84
85 info = JSON.parse(response)
86 if info["version"] == HELPER_VERSION
87 true
88 else
89 stop_caldecott
90 false
91 end
92 rescue RestClient::Exception
93 stop_caldecott
94 false
95 end
96 end
97
98 def tunnel_bound?(service)
99 tunnel_app_info[:services].include?(service)
100 end
101
102 def tunnel_connection_info(type, service, token)
103 display "Getting tunnel connection info: ", false
104 response = nil
105 10.times do
106 begin
107 response = RestClient.get(tunnel_url + "/" + VMC::Client.path("services", service), "Auth-Token" => token)
108 break
109 rescue RestClient::Exception
110 sleep 1
111 end
112
113 display ".", false
114 end
115
116 unless response
117 err "Expected remote tunnel to know about #{service}, but it doesn't"
118 end
119
120 display "OK".green
121
122 info = JSON.parse(response)
123 case type
124 when "rabbitmq"
125 uri = Addressable::URI.parse info["url"]
126 info["hostname"] = uri.host
127 info["port"] = uri.port
128 info["vhost"] = uri.path[1..-1]
129 info["user"] = uri.user
130 info["password"] = uri.password
131 info.delete "url"
132
133 # we use "db" as the "name" for mongo
134 # existing "name" is junk
135 when "mongodb"
136 info["name"] = info["db"]
137 info.delete "db"
138
139 # our "name" is irrelevant for redis
140 when "redis"
141 info.delete "name"
142 end
143
144 ['hostname', 'port', 'password'].each do |k|
145 err "Could not determine #{k} for #{service}" if info[k].nil?
146 end
147
148 info
149 end
150
151 def display_tunnel_connection_info(info)
152 display ''
153 display "Service connection info: "
154
155 to_show = [nil, nil, nil] # reserved for user, pass, db name
156 info.keys.each do |k|
157 case k
158 when "host", "hostname", "port", "node_id"
159 # skip
160 when "user", "username"
161 # prefer "username" over "user"
162 to_show[0] = k unless to_show[0] == "username"
163 when "password"
164 to_show[1] = k
165 when "name"
166 to_show[2] = k
167 else
168 to_show << k
169 end
170 end
171 to_show.compact!
172
173 align_len = to_show.collect(&:size).max + 1
174
175 to_show.each do |k|
176 # TODO: modify the server services rest call to have explicit knowledge
177 # about the items to return. It should return all of them if
178 # the service is unknown so that we don't have to do this weird
179 # filtering.
180 display " #{k.ljust align_len}: ", false
181 display "#{info[k]}".yellow
182 end
183 display ''
184 end
185
186 def start_tunnel(local_port, conn_info, auth)
187 @local_tunnel_thread = Thread.new do
188 Caldecott::Client.start({
189 :local_port => local_port,
190 :tun_url => tunnel_url,
191 :dst_host => conn_info['hostname'],
192 :dst_port => conn_info['port'],
193 :log_file => STDOUT,
194 :log_level => ENV["VMC_TUNNEL_DEBUG"] || "ERROR",
195 :auth_token => auth,
196 :quiet => true
197 })
198 end
199
200 at_exit { @local_tunnel_thread.kill }
201 end
202
203
204
205 def pick_tunnel_port(port)
206 original = port
207
208 PORT_RANGE.times do |n|
209 begin
210 TCPSocket.open('localhost', port)
211 port += 1
212 rescue
213 return port
214 end
215 end
216
217 grab_ephemeral_port
218 end
219
220 def grab_ephemeral_port
221 socket = TCPServer.new('0.0.0.0', 0)
222 socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
223 Socket.do_not_reverse_lookup = true
224 port = socket.addr[1]
225 socket.close
226 return port
227 end
228
229 def wait_for_tunnel_start(port)
230 10.times do |n|
231 begin
232 client = TCPSocket.open('localhost', port)
233 display '' if n > 0
234 client.close
235 return true
236 rescue => e
237 display "Waiting for local tunnel to become available", false if n == 0
238 display '.', false
239 sleep 1
240 end
241 end
242 err "Could not connect to local tunnel."
243 end
244
245 def wait_for_tunnel_end
246 display "Open another shell to run command-line clients or"
247 display "use a UI tool to connect using the displayed information."
248 display "Press Ctrl-C to exit..."
249 @local_tunnel_thread.join
250 end
251
252 def resolve_symbols(str, info, local_port)
253 str.gsub(/\$\{\s*([^\}]+)\s*\}/) do
254 case $1
255 when "host"
256 # TODO: determine proper host
257 "localhost"
258 when "port"
259 local_port
260 when "user", "username"
261 info["username"]
262 else
263 info[$1] || ask($1)
264 end
265 end
266 end
267
268 def start_local_prog(clients, command, info, port)
269 client = clients[File.basename(command)]
270
271 cmdline = "#{command} "
272
273 case client
274 when Hash
275 cmdline << resolve_symbols(client["command"], info, port)
276 client["environment"].each do |e|
277 if e =~ /([^=]+)=(["']?)([^"']*)\2/
278 ENV[$1] = resolve_symbols($3, info, port)
279 else
280 err "Invalid environment variable: #{e}"
281 end
282 end
283 when String
284 cmdline << resolve_symbols(client, info, port)
285 else
286 err "Unknown client info: #{client.inspect}."
287 end
288
289 display "Launching '#{cmdline}'"
290 display ''
291
292 system(cmdline)
293 end
294
295 def push_caldecott(token)
296 client.create_app(
297 tunnel_appname,
298 { :name => tunnel_appname,
299 :staging => {:framework => "sinatra"},
300 :uris => ["#{tunnel_uniquename}.#{target_base}"],
301 :instances => 1,
302 :resources => {:memory => 64},
303 :env => ["CALDECOTT_AUTH=#{token}"]
304 }
305 )
306
307 apps_cmd.send(:upload_app_bits, tunnel_appname, HELPER_APP)
308
309 invalidate_tunnel_app_info
310 end
311
312 def stop_caldecott
313 apps_cmd.stop(tunnel_appname)
314
315 invalidate_tunnel_app_info
316 end
317
318 def start_caldecott
319 apps_cmd.start(tunnel_appname)
320
321 invalidate_tunnel_app_info
322 end
323
324 private
325
326 def apps_cmd
327 a = Command::Apps.new(@options)
328 a.client client
329 a
330 end
331 end
332end
0333
=== modified file 'lib/cli/usage.rb'
--- lib/cli/usage.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/usage.rb 2012-06-13 01:37:18 +0000
@@ -38,17 +38,18 @@
38 push [appname] --url Set the url for the application38 push [appname] --url Set the url for the application
39 push [appname] --instances <N> Set the expected number <N> of instances39 push [appname] --instances <N> Set the expected number <N> of instances
40 push [appname] --mem M Set the memory reservation for the application40 push [appname] --mem M Set the memory reservation for the application
41 push [appname] --runtime RUNTIME Set the runtime to use for the application
42 push [appname] --debug [MODE] Push application and start in a debug mode
41 push [appname] --no-start Do not auto-start the application43 push [appname] --no-start Do not auto-start the application
4244
43 Application Operations45 Application Operations
44 start <appname> Start the application46 start <appname> [--debug [MODE]] Start the application
45 stop <appname> Stop the application47 stop <appname> Stop the application
46 restart <appname> Restart the application48 restart <appname> [--debug [MODE]] Restart the application
47 delete <appname> Delete the application49 delete <appname> Delete the application
48 rename <appname> <newname> Rename the application
4950
50 Application Updates51 Application Updates
51 update <appname> [--path] Update the application bits52 update <appname> [--path,--debug [MODE]] Update the application bits
52 mem <appname> [memsize] Update the memory reservation for an application53 mem <appname> [memsize] Update the memory reservation for an application
53 map <appname> <url> Register the application to the url54 map <appname> <url> Register the application to the url
54 unmap <appname> <url> Unregister the application from the url55 unmap <appname> <url> Unregister the application from the url
@@ -76,6 +77,8 @@
76 bind-service <servicename> <appname> Bind a service to an application77 bind-service <servicename> <appname> Bind a service to an application
77 unbind-service <servicename> <appname> Unbind service from the application78 unbind-service <servicename> <appname> Unbind service from the application
78 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>79 clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
80 tunnel <servicename> [--port] Create a local tunnel to a service
81 tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client
7982
80 Administration83 Administration
81 user Display user account information84 user Display user account information
@@ -88,6 +91,15 @@
88 runtimes Display the supported runtimes of the target system91 runtimes Display the supported runtimes of the target system
89 frameworks Display the recognized frameworks of the target system92 frameworks Display the recognized frameworks of the target system
9093
94 Micro Cloud Foundry
95 micro status Display Micro Cloud Foundry VM status
96 micro offline Configure Micro Cloud Foundry VM for offline mode
97 micro online Configure Micro Cloud Foundry VM for online mode
98 [--vmx file] Path to micro.vmx
99 [--vmrun executable] Path to vmrun executable
100 [--password cleartext] Cleartext password for guest VM vcap user
101 [--save] Save cleartext password in ~/.vmc_micro
102
91 Misc103 Misc
92 aliases List aliases104 aliases List aliases
93 alias <alias[=]command> Create an alias for a command105 alias <alias[=]command> Create an alias for a command
94106
=== modified file 'lib/cli/version.rb'
--- lib/cli/version.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/version.rb 2012-06-13 01:37:18 +0000
@@ -2,6 +2,6 @@
2 module Cli2 module Cli
3 # This version number is used as the RubyGem release version.3 # This version number is used as the RubyGem release version.
4 # The internal VMC version number is VMC::VERSION.4 # The internal VMC version number is VMC::VERSION.
5 VERSION = '0.3.10'5 VERSION = '0.3.18'
6 end6 end
7end7end
88
=== modified file 'lib/cli/zip_util.rb'
--- lib/cli/zip_util.rb 2011-06-17 13:38:56 +0000
+++ lib/cli/zip_util.rb 2012-06-13 01:37:18 +0000
@@ -10,7 +10,7 @@
10 class << self10 class << self
1111
12 def to_dev_null12 def to_dev_null
13 if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']13 if WINDOWS
14 'nul'14 'nul'
15 else15 else
16 '/dev/null'16 '/dev/null'
1717
=== modified file 'lib/vmc/client.rb'
--- lib/vmc/client.rb 2011-06-17 13:38:56 +0000
+++ lib/vmc/client.rb 2012-06-13 01:37:18 +0000
@@ -11,6 +11,7 @@
1111
12require 'rubygems'12require 'rubygems'
13require 'json/pure'13require 'json/pure'
14require 'open-uri'
1415
15require File.expand_path('../const', __FILE__)16require File.expand_path('../const', __FILE__)
1617
@@ -24,7 +25,7 @@
24 attr_accessor :trace25 attr_accessor :trace
2526
26 # Error codes27 # Error codes
27 VMC_HTTP_ERROR_CODES = [ 400, 403, 404, 500 ]28 VMC_HTTP_ERROR_CODES = [ 400, 500 ]
2829
29 # Errors30 # Errors
30 class BadTarget < RuntimeError; end31 class BadTarget < RuntimeError; end
@@ -38,7 +39,7 @@
38 def initialize(target_url=VMC::DEFAULT_TARGET, auth_token=nil)39 def initialize(target_url=VMC::DEFAULT_TARGET, auth_token=nil)
39 target_url = "http://#{target_url}" unless /^https?/ =~ target_url40 target_url = "http://#{target_url}" unless /^https?/ =~ target_url
40 target_url = target_url.gsub(/\/+$/, '')41 target_url = target_url.gsub(/\/+$/, '')
41 @target = target_url42 @target = target_url
42 @auth_token = auth_token43 @auth_token = auth_token
43 end44 end
4445
@@ -59,7 +60,11 @@
59 # Global listing of services that are available on the target system60 # Global listing of services that are available on the target system
60 def services_info61 def services_info
61 check_login_status62 check_login_status
62 json_get(VMC::GLOBAL_SERVICES_PATH)63 json_get(path(VMC::GLOBAL_SERVICES_PATH))
64 end
65
66 def runtimes_info
67 json_get(path(VMC::GLOBAL_RUNTIMES_PATH))
63 end68 end
6469
65 ######################################################70 ######################################################
@@ -81,7 +86,7 @@
8186
82 def update_app(name, manifest)87 def update_app(name, manifest)
83 check_login_status88 check_login_status
84 json_put("#{VMC::APPS_PATH}/#{name}", manifest)89 json_put(path(VMC::APPS_PATH, name), manifest)
85 end90 end
8691
87 def upload_app(name, zipfile, resource_manifest=nil)92 def upload_app(name, zipfile, resource_manifest=nil)
@@ -98,27 +103,29 @@
98 upload_data[:application] = file103 upload_data[:application] = file
99 end104 end
100 upload_data[:resources] = resource_manifest.to_json if resource_manifest105 upload_data[:resources] = resource_manifest.to_json if resource_manifest
101 http_post("#{VMC::APPS_PATH}/#{name}/application", upload_data)106 http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
107 rescue RestClient::ServerBrokeConnection
108 retry
102 end109 end
103110
104 def delete_app(name)111 def delete_app(name)
105 check_login_status112 check_login_status
106 http_delete("#{VMC::APPS_PATH}/#{name}")113 http_delete(path(VMC::APPS_PATH, name))
107 end114 end
108115
109 def app_info(name)116 def app_info(name)
110 check_login_status117 check_login_status
111 json_get("#{VMC::APPS_PATH}/#{name}")118 json_get(path(VMC::APPS_PATH, name))
112 end119 end
113120
114 def app_update_info(name)121 def app_update_info(name)
115 check_login_status122 check_login_status
116 json_get("#{VMC::APPS_PATH}/#{name}/update")123 json_get(path(VMC::APPS_PATH, name, "update"))
117 end124 end
118125
119 def app_stats(name)126 def app_stats(name)
120 check_login_status127 check_login_status
121 stats_raw = json_get("#{VMC::APPS_PATH}/#{name}/stats")128 stats_raw = json_get(path(VMC::APPS_PATH, name, "stats"))
122 stats = []129 stats = []
123 stats_raw.each_pair do |k, entry|130 stats_raw.each_pair do |k, entry|
124 # Skip entries with no stats131 # Skip entries with no stats
@@ -132,20 +139,20 @@
132139
133 def app_instances(name)140 def app_instances(name)
134 check_login_status141 check_login_status
135 json_get("#{VMC::APPS_PATH}/#{name}/instances")142 json_get(path(VMC::APPS_PATH, name, "instances"))
136 end143 end
137144
138 def app_crashes(name)145 def app_crashes(name)
139 check_login_status146 check_login_status
140 json_get("#{VMC::APPS_PATH}/#{name}/crashes")147 json_get(path(VMC::APPS_PATH, name, "crashes"))
141 end148 end
142149
143 # List the directory or download the actual file indicated by150 # List the directory or download the actual file indicated by
144 # the path.151 # the path.
145 def app_files(name, path, instance=0)152 def app_files(name, path, instance='0')
146 check_login_status153 check_login_status
147 url = "#{VMC::APPS_PATH}/#{name}/instances/#{instance}/files/#{path}"154 path = path.gsub('//', '/')
148 url.gsub!('//', '/')155 url = path(VMC::APPS_PATH, name, "instances", instance, "files", path)
149 _, body, headers = http_get(url)156 _, body, headers = http_get(url)
150 body157 body
151 end158 end
@@ -185,7 +192,7 @@
185192
186 raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash193 raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
187 service_hash[:name] = name194 service_hash[:name] = name
188 json_post(VMC::SERVICES_PATH, service_hash)195 json_post(path(VMC::SERVICES_PATH), service_hash)
189 end196 end
190197
191 def delete_service(name)198 def delete_service(name)
@@ -193,7 +200,7 @@
193 svcs = services || []200 svcs = services || []
194 names = svcs.collect { |s| s[:name] }201 names = svcs.collect { |s| s[:name] }
195 raise TargetError, "Service [#{name}] not a valid service" unless names.include? name202 raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
196 http_delete("#{VMC::SERVICES_PATH}/#{name}")203 http_delete(path(VMC::SERVICES_PATH, name))
197 end204 end
198205
199 def bind_service(service, appname)206 def bind_service(service, appname)
@@ -263,7 +270,7 @@
263 # Auth token can be retained and used in creating270 # Auth token can be retained and used in creating
264 # new clients, avoiding login.271 # new clients, avoiding login.
265 def login(user, password)272 def login(user, password)
266 status, body, headers = json_post("#{VMC::USERS_PATH}/#{user}/tokens", {:password => password})273 status, body, headers = json_post(path(VMC::USERS_PATH, user, "tokens"), {:password => password})
267 response_info = json_parse(body)274 response_info = json_parse(body)
268 if response_info275 if response_info
269 @user = user276 @user = user
@@ -274,10 +281,10 @@
274 # sets the password for the current logged user281 # sets the password for the current logged user
275 def change_password(new_password)282 def change_password(new_password)
276 check_login_status283 check_login_status
277 user_info = json_get("#{VMC::USERS_PATH}/#{@user}")284 user_info = json_get(path(VMC::USERS_PATH, @user))
278 if user_info285 if user_info
279 user_info[:password] = new_password286 user_info[:password] = new_password
280 json_put("#{VMC::USERS_PATH}/#{@user}", user_info)287 json_put(path(VMC::USERS_PATH, @user), user_info)
281 end288 end
282 end289 end
283290
@@ -293,18 +300,34 @@
293 @proxy = proxy300 @proxy = proxy
294 end301 end
295302
303 def users
304 check_login_status
305 json_get(VMC::USERS_PATH)
306 end
307
296 def add_user(user_email, password)308 def add_user(user_email, password)
297 json_post(VMC::USERS_PATH, { :email => user_email, :password => password })309 json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
298 end310 end
299311
300 def delete_user(user_email)312 def delete_user(user_email)
301 http_delete("#{VMC::USERS_PATH}/#{user_email}")313 check_login_status
314 http_delete(path(VMC::USERS_PATH, user_email))
302 end315 end
303316
304 ######################################################317 ######################################################
305318
319 def self.path(*path)
320 path.flatten.collect { |x|
321 URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
322 }.join("/")
323 end
324
306 private325 private
307326
327 def path(*args, &blk)
328 self.class.path(*args, &blk)
329 end
330
308 def json_get(url)331 def json_get(url)
309 status, body, headers = http_get(url, 'application/json')332 status, body, headers = http_get(url, 'application/json')
310 json_parse(body)333 json_parse(body)
@@ -357,12 +380,12 @@
357 end380 end
358381
359 req = {382 req = {
360 :method => method, :url => "#{@target}#{path}",383 :method => method, :url => "#{@target}/#{path}",
361 :payload => payload, :headers => headers384 :payload => payload, :headers => headers, :multipart => true
362 }385 }
363 status, body, response_headers = perform_http_request(req)386 status, body, response_headers = perform_http_request(req)
364387
365 if VMC_HTTP_ERROR_CODES.include?(status)388 if request_failed?(status)
366 # FIXME, old cc returned 400 on not found for file access389 # FIXME, old cc returned 400 on not found for file access
367 err = (status == 404 || status == 400) ? NotFound : TargetError390 err = (status == 404 || status == 400) ? NotFound : TargetError
368 raise err, parse_error_message(status, body)391 raise err, parse_error_message(status, body)
@@ -373,8 +396,13 @@
373 raise BadTarget, "Cannot access target (%s)" % [ e.message ]396 raise BadTarget, "Cannot access target (%s)" % [ e.message ]
374 end397 end
375398
399 def request_failed?(status)
400 VMC_HTTP_ERROR_CODES.detect{|error_code| status >= error_code}
401 end
402
376 def perform_http_request(req)403 def perform_http_request(req)
377 RestClient.proxy = ENV['https_proxy'] || ENV['http_proxy']404 proxy_uri = URI.parse(req[:url]).find_proxy()
405 RestClient.proxy = proxy_uri.to_s if proxy_uri
378406
379 # Setup tracing if needed407 # Setup tracing if needed
380 unless trace.nil?408 unless trace.nil?
@@ -388,9 +416,17 @@
388 puts '>>>'416 puts '>>>'
389 puts "PROXY: #{RestClient.proxy}" if RestClient.proxy417 puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
390 puts "REQUEST: #{req[:method]} #{req[:url]}"418 puts "REQUEST: #{req[:method]} #{req[:url]}"
391 puts "RESPONSE_HEADERS: #{response.headers}"419 puts "RESPONSE_HEADERS:"
420 response.headers.each do |key, value|
421 puts " #{key} : #{value}"
422 end
392 puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]423 puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
393 puts "RESPONSE: [#{response.code}] #{response.body}"424 puts "RESPONSE: [#{response.code}]"
425 begin
426 puts JSON.pretty_generate(JSON.parse(response.body))
427 rescue
428 puts "#{response.body}"
429 end
394 puts '<<<'430 puts '<<<'
395 end431 end
396 end432 end
397433
=== modified file 'lib/vmc/const.rb'
--- lib/vmc/const.rb 2011-06-17 13:38:56 +0000
+++ lib/vmc/const.rb 2012-06-13 01:37:18 +0000
@@ -5,17 +5,18 @@
5 VERSION = '0.3.2'5 VERSION = '0.3.2'
66
7 # Targets7 # Targets
8 DEFAULT_TARGET = 'http://api.cloudfoundry.com'8 DEFAULT_TARGET = 'https://api.cloudfoundry.com'
9 DEFAULT_LOCAL_TARGET = 'http://api.vcap.me'9 DEFAULT_LOCAL_TARGET = 'http://api.vcap.me'
1010
11 # General Paths11 # General Paths
12 INFO_PATH = '/info'12 INFO_PATH = 'info'
13 GLOBAL_SERVICES_PATH = '/info/services'13 GLOBAL_SERVICES_PATH = ['info', 'services']
14 RESOURCES_PATH = '/resources'14 GLOBAL_RUNTIMES_PATH = ['info', 'runtimes']
15 RESOURCES_PATH = 'resources'
1516
16 # User specific paths17 # User specific paths
17 APPS_PATH = '/apps'18 APPS_PATH = 'apps'
18 SERVICES_PATH = '/services'19 SERVICES_PATH = 'services'
19 USERS_PATH = '/users'20 USERS_PATH = 'users'
2021
21end22end
2223
=== added directory 'lib/vmc/micro'
=== added file 'lib/vmc/micro.rb'
--- lib/vmc/micro.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,56 @@
1require 'find'
2
3module VMC::Micro
4 def config_file(file)
5 File.join(File.dirname(__FILE__), '..', '..', 'config', 'micro', file)
6 end
7
8 def escape_path(path)
9 path = File.expand_path(path)
10 if RUBY_PLATFORM =~ /mingw|mswin32|cygwin/
11 if path.include?(' ')
12 return '"' + path + '"'
13 else
14 return path
15 end
16 else
17 return path.gsub(' ', '\ ')
18 end
19 end
20
21 def locate_file(file, directory, search_paths)
22 search_paths.each do |path|
23 expanded_path = File.expand_path(path)
24 if File.exists?(expanded_path)
25 Find.find(expanded_path) do |current|
26 if File.directory?(current) && current.include?(directory)
27 full_path = File.join(current, file)
28 return self.escape_path(full_path) if File.exists?(full_path)
29 end
30 end
31 end
32 end
33
34 false
35 end
36
37 def run_command(command, args=nil)
38 # TODO switch to using posix-spawn instead
39 result = %x{#{command} #{args} 2>&1}
40 unless $?.exitstatus == 0
41 if block_given?
42 yield
43 else
44 raise "failed to execute #{command} #{args}:\n#{result}"
45 end
46 else
47 result.split(/\n/)
48 end
49 end
50
51 module_function :config_file
52 module_function :escape_path
53 module_function :locate_file
54 module_function :run_command
55
56end
057
=== added directory 'lib/vmc/micro/switcher'
=== added file 'lib/vmc/micro/switcher/base.rb'
--- lib/vmc/micro/switcher/base.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/switcher/base.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,97 @@
1require 'interact'
2
3module VMC::Micro::Switcher
4 class Base
5 include Interactive
6
7 def initialize(config)
8 @config = config
9
10 @vmrun = VMC::Micro::VMrun.new(config)
11 unless @vmrun.running?
12 if ask("Micro Cloud Foundry VM is not running. Do you want to start it?", :choices => ['y', 'n']) == 'y'
13 display "Starting Micro Cloud Foundry VM: ", false
14 @vmrun.start
15 say "done".green
16 else
17 err "Micro Cloud Foundry VM needs to be running."
18 end
19 end
20
21 err "Micro Cloud Foundry VM initial setup needs to be completed before using 'vmc micro'" unless @vmrun.ready?
22 end
23
24 def offline
25 unless @vmrun.offline?
26 # save online connection type so we can restore it later
27 @config['online_connection_type'] = @vmrun.connection_type
28
29 if (@config['online_connection_type'] != 'nat')
30 if ask("Reconfigure Micro Cloud Foundry VM network to nat mode and reboot?", :choices => ['y', 'n']) == 'y'
31 display "Rebooting Micro Cloud Foundry VM: ", false
32 @vmrun.connection_type = 'nat'
33 @vmrun.reset
34 say "done".green
35 else
36 err "Aborted"
37 end
38 end
39
40 display "Setting Micro Cloud Foundry VM to offline mode: ", false
41 @vmrun.offline!
42 say "done".green
43 display "Setting host DNS server: ", false
44
45 @config['domain'] = @vmrun.domain
46 @config['ip'] = @vmrun.ip
47 set_nameserver(@config['domain'], @config['ip'])
48 say "done".green
49 else
50 say "Micro Cloud Foundry VM already in offline mode".yellow
51 end
52 end
53
54 def online
55 if @vmrun.offline?
56 current_connection_type = @vmrun.connection_type
57 @config['online_connection_type'] ||= current_connection_type
58
59 if (@config['online_connection_type'] != current_connection_type)
60 # TODO handle missing connection type in saved config
61 question = "Reconfigure Micro Cloud Foundry VM network to #{@config['online_connection_type']} mode and reboot?"
62 if ask(question, :choices => ['y', 'n']) == 'y'
63 display "Rebooting Micro Cloud Foundry VM: ", false
64 @vmrun.connection_type = @config['online_connection_type']
65 @vmrun.reset
66 say "done".green
67 else
68 err "Aborted"
69 end
70 end
71
72 display "Unsetting host DNS server: ", false
73 # TODO handle missing domain and ip in saved config (look at the VM)
74 @config['domain'] ||= @vmrun.domain
75 @config['ip'] ||= @vmrun.ip
76 unset_nameserver(@config['domain'], @config['ip'])
77 say "done".green
78
79 display "Setting Micro Cloud Foundry VM to online mode: ", false
80 @vmrun.online!
81 say "done".green
82 else
83 say "Micro Cloud Foundry already in online mode".yellow
84 end
85 end
86
87 def status
88 mode = @vmrun.offline? ? 'offline' : 'online'
89 say "Micro Cloud Foundry VM currently in #{mode.green} mode"
90 # should the VMX path be unescaped?
91 say "VMX Path: #{@vmrun.vmx}"
92 say "Domain: #{@vmrun.domain.green}"
93 say "IP Address: #{@vmrun.ip.green}"
94 end
95 end
96
97end
098
=== added file 'lib/vmc/micro/switcher/darwin.rb'
--- lib/vmc/micro/switcher/darwin.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/switcher/darwin.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,19 @@
1module VMC::Micro::Switcher
2
3 class Darwin < Base
4 def adminrun(command)
5 VMC::Micro.run_command("osascript", "-e 'do shell script \"#{command}\" with administrator privileges'")
6 end
7
8 def set_nameserver(domain, ip)
9 File.open("/tmp/#{domain}", 'w') { |file| file.write("nameserver #{ip}") }
10 adminrun("mkdir -p /etc/resolver;mv /tmp/#{domain} /etc/resolver/")
11 end
12
13 def unset_nameserver(domain, ip)
14 err "domain missing" unless domain
15 adminrun("rm -f /etc/resolver/#{domain}")
16 end
17 end
18
19end
020
=== added file 'lib/vmc/micro/switcher/dummy.rb'
--- lib/vmc/micro/switcher/dummy.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/switcher/dummy.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,15 @@
1# only used for testing
2module VMC::Micro::Switcher
3
4 class Dummy < Base
5 def adminrun(command)
6 end
7
8 def set_nameserver(domain, ip)
9 end
10
11 def unset_nameserver(domain, ip)
12 end
13 end
14
15end
016
=== added file 'lib/vmc/micro/switcher/linux.rb'
--- lib/vmc/micro/switcher/linux.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/switcher/linux.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,16 @@
1module VMC::Micro::Switcher
2
3 class Linux < Base
4 def set_nameserver(domain, ip)
5 VMC::Micro.run_command("sudo", "sed -i'.backup' '1 i nameserver #{ip}' /etc/resolv.conf")
6 # lock resolv.conf so Network Manager doesn't clear out the file when offline
7 VMC::Micro.run_command("sudo", "chattr +i /etc/resolv.conf")
8 end
9
10 def unset_nameserver(domain, ip)
11 VMC::Micro.run_command("sudo", "chattr -i /etc/resolv.conf")
12 VMC::Micro.run_command("sudo", "sed -i'.backup' '/#{ip}/d' /etc/resolv.conf")
13 end
14 end
15
16end
017
=== added file 'lib/vmc/micro/switcher/windows.rb'
--- lib/vmc/micro/switcher/windows.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/switcher/windows.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,31 @@
1module VMC::Micro::Switcher
2
3 class Windows < Base
4 def version?
5 VMC::Micro.run_command("cmd", "/c ver").to_s.scan(/\d+\.\d+/).first.to_f
6 end
7
8 def adminrun(command, args=nil)
9 if version? > 5.2
10 require 'win32ole'
11 shell = WIN32OLE.new("Shell.Application")
12 shell.ShellExecute(command, args, nil, "runas", 0)
13 else
14 # on older version this will try to run the command, and if you don't have
15 # admin privilges it will tell you so and exit
16 VMC::Micro.run_command(command, args)
17 end
18 end
19
20 # TODO better method to figure out the interface name is to get the NAT ip and find the
21 # interface with the correct subnet
22 def set_nameserver(domain, ip)
23 adminrun("netsh", "interface ip set dns \"VMware Network Adapter VMnet8\" static #{ip}")
24 end
25
26 def unset_nameserver(domain, ip)
27 adminrun("netsh", "interface ip set dns \"VMware Network Adapter VMnet8\" static none")
28 end
29 end
30
31end
032
=== added file 'lib/vmc/micro/vmrun.rb'
--- lib/vmc/micro/vmrun.rb 1970-01-01 00:00:00 +0000
+++ lib/vmc/micro/vmrun.rb 2012-06-13 01:37:18 +0000
@@ -0,0 +1,158 @@
1module VMC::Micro
2 class VMrun
3 attr_reader :vmx, :vmrun
4
5 def initialize(config)
6 @platform = config['platform']
7 @user = 'root' # must use root as we muck around with system settings
8 @password = config['password']
9 @vmrun = config['vmrun']
10 @vmx = config['vmx']
11
12 # TODO honor TMPDIR
13 if @platform == :windows
14 @temp_dir = ENV['temp']
15 else
16 @temp_dir = '/tmp'
17 end
18 end
19
20 def connection_type
21 read_variable('ethernet0.connectionType')
22 end
23
24 def connection_type=(type)
25 write_variable("ethernet0.connectionType", type)
26 end
27
28 def nat?
29 connection_type == "nat"
30 end
31
32 def bridged?
33 connection_type == "bridged"
34 end
35
36 def domain
37 # switch to Dir.mktmpdir
38 state_config = VMC::Micro.escape_path(File.join(@temp_dir, 'state.yml'))
39 run('CopyFileFromGuestToHost', "/var/vcap/bosh/state.yml #{state_config}")
40 bosh_config = YAML.load_file(state_config)
41 bosh_config['properties']['domain']
42 end
43
44 def ip
45 # switch to Dir.mktmpdir
46 path = VMC::Micro.escape_path(VMC::Micro.config_file('refresh_ip.rb'))
47 ip_file = VMC::Micro.escape_path(File.join(@temp_dir, 'ip.txt'))
48 run('CopyFileFromHostToGuest', "#{path} /tmp/refresh_ip.rb")
49 run('runProgramInGuest', '/tmp/refresh_ip.rb')
50 run('CopyFileFromGuestToHost', "/tmp/ip.txt #{ip_file}")
51 File.open(ip_file, 'r') { |file| file.read }
52 end
53
54 def list
55 vms = run("list")
56 vms.delete_if { |line| line =~ /^Total/ }
57 vms.map { |line| VMC::Micro.escape_path(File.expand_path(line)) }
58 end
59
60 def offline?
61 command = "-gu #{@user} -gp #{@password} runProgramInGuest"
62 args = '/usr/bin/test -e /var/vcap/micro/offline'
63 # why not use run_command?
64 result = %x{#{@vmrun} #{command} #{@vmx} #{args}}
65
66 if result.include?('Guest program exited with non-zero exit code: 1')
67 return false
68 elsif $?.exitstatus == 0
69 return true
70 else
71 raise "failed to execute vmrun:\n#{result}"
72 end
73 end
74
75 def offline!
76 path = VMC::Micro.escape_path(VMC::Micro.config_file('offline.conf'))
77 run('CopyFileFromHostToGuest', "#{path} /etc/dnsmasq.d/offline.conf")
78 run('runProgramInGuest', '/usr/bin/touch /var/vcap/micro/offline')
79 restart_dnsmasq
80 end
81
82 def online!
83 run('runProgramInGuest', '/bin/rm -f /etc/dnsmasq.d/offline.conf')
84 run('runProgramInGuest', '/bin/rm -f /var/vcap/micro/offline')
85 restart_dnsmasq
86 end
87
88 # check to see if the micro cloud has been configured
89 # uses default password to check
90 def ready?
91 command = "-gu root -gp 'ca$hc0w' runProgramInGuest"
92 args = '/usr/bin/test -e /var/vcap/micro/micro.json'
93 result = %x{#{@vmrun} #{command} #{@vmx} #{args}}
94
95 if result.include?('Invalid user name or password for the guest OS') || $?.exitstatus == 0
96 return true
97 elsif $?.exitstatus == 1
98 return false
99 else
100 raise "failed to execute vmrun:\n#{result}"
101 end
102 end
103
104 def read_variable(var)
105 # TODO deal with non-ok return
106 run("readVariable", "runtimeConfig #{var}").first
107 end
108
109 def write_variable(var, value)
110 run('writeVariable', "runtimeConfig #{var} #{value}")
111 end
112
113 def reset
114 run('reset', 'soft')
115 end
116
117 def restart_dnsmasq
118 # restart command doesn't always work, start and stop seems to be more reliable
119 run('runProgramInGuest', '/etc/init.d/dnsmasq stop')
120 run('runProgramInGuest', '/etc/init.d/dnsmasq start')
121 end
122
123 def run(command, args=nil)
124 if command.include?('Guest')
125 command = "-gu #{@user} -gp #{@password} #{command}"
126 end
127 VMC::Micro.run_command(@vmrun, "#{command} #{@vmx} #{args}")
128 end
129
130 def running?
131 vms = list
132 if @platform == :windows
133 vms.map! { |x| x.downcase }
134 vms.include?(@vmx.downcase)
135 else
136 vms.include?(@vmx)
137 end
138 end
139
140 def start
141 run('start') unless running?
142 end
143
144 def stop
145 run('stop') if running?
146 end
147
148 def self.locate(platform)
149 paths = YAML.load_file(VMC::Micro.config_file('paths.yml'))
150 vmrun_paths = paths[platform.to_s]['vmrun']
151 vmrun_exe = @platform == :windows ? 'vmrun.exe' : 'vmrun'
152 vmrun = VMC::Micro.locate_file(vmrun_exe, "VMware", vmrun_paths)
153 err "Unable to locate vmrun, please supply --vmrun option" unless vmrun
154 vmrun
155 end
156 end
157
158end
0159
=== modified file 'metadata.yml'
--- metadata.yml 2011-06-17 13:38:56 +0000
+++ metadata.yml 2012-06-13 01:37:18 +0000
@@ -1,13 +1,13 @@
1--- !ruby/object:Gem::Specification 1--- !ruby/object:Gem::Specification
2name: vmc2name: vmc
3version: !ruby/object:Gem::Version 3version: !ruby/object:Gem::Version
4 hash: 74 hash: 55
5 prerelease: 5 prerelease:
6 segments: 6 segments:
7 - 07 - 0
8 - 38 - 3
9 - 109 - 18
10 version: 0.3.1010 version: 0.3.18
11platform: ruby11platform: ruby
12authors: 12authors:
13- VMware13- VMware
@@ -15,8 +15,7 @@
15bindir: bin15bindir: bin
16cert_chain: []16cert_chain: []
1717
18date: 2011-04-11 00:00:00 -07:0018date: 2012-05-30 00:00:00 Z
19default_executable:
20dependencies: 19dependencies:
21- !ruby/object:Gem::Dependency 20- !ruby/object:Gem::Dependency
22 name: json_pure21 name: json_pure
@@ -24,7 +23,7 @@
24 requirement: &id001 !ruby/object:Gem::Requirement 23 requirement: &id001 !ruby/object:Gem::Requirement
25 none: false24 none: false
26 requirements: 25 requirements:
27 - - ~>26 - - ">="
28 - !ruby/object:Gem::Version 27 - !ruby/object:Gem::Version
29 hash: 128 hash: 1
30 segments: 29 segments:
@@ -32,31 +31,39 @@
32 - 531 - 5
33 - 132 - 1
34 version: 1.5.133 version: 1.5.1
34 - - <
35 - !ruby/object:Gem::Version
36 hash: 11
37 segments:
38 - 1
39 - 7
40 - 0
41 version: 1.7.0
35 type: :runtime42 type: :runtime
36 version_requirements: *id00143 version_requirements: *id001
37- !ruby/object:Gem::Dependency 44- !ruby/object:Gem::Dependency
38 name: rubyzip245 name: rubyzip
39 prerelease: false46 prerelease: false
40 requirement: &id002 !ruby/object:Gem::Requirement 47 requirement: &id002 !ruby/object:Gem::Requirement
41 none: false48 none: false
42 requirements: 49 requirements:
43 - - ~>50 - - ~>
44 - !ruby/object:Gem::Version 51 - !ruby/object:Gem::Version
45 hash: 1352 hash: 51
46 segments: 53 segments:
47 - 2
48 - 054 - 0
49 - 155 - 9
50 version: 2.0.156 - 4
57 version: 0.9.4
51 type: :runtime58 type: :runtime
52 version_requirements: *id00259 version_requirements: *id002
53- !ruby/object:Gem::Dependency 60- !ruby/object:Gem::Dependency
54 name: highline61 name: rest-client
55 prerelease: false62 prerelease: false
56 requirement: &id003 !ruby/object:Gem::Requirement 63 requirement: &id003 !ruby/object:Gem::Requirement
57 none: false64 none: false
58 requirements: 65 requirements:
59 - - ~>66 - - ">="
60 - !ruby/object:Gem::Version 67 - !ruby/object:Gem::Version
61 hash: 1368 hash: 13
62 segments: 69 segments:
@@ -64,52 +71,100 @@
64 - 671 - 6
65 - 172 - 1
66 version: 1.6.173 version: 1.6.1
74 - - <
75 - !ruby/object:Gem::Version
76 hash: 11
77 segments:
78 - 1
79 - 7
80 - 0
81 version: 1.7.0
67 type: :runtime82 type: :runtime
68 version_requirements: *id00383 version_requirements: *id003
69- !ruby/object:Gem::Dependency 84- !ruby/object:Gem::Dependency
70 name: rest-client85 name: terminal-table
71 prerelease: false86 prerelease: false
72 requirement: &id004 !ruby/object:Gem::Requirement 87 requirement: &id004 !ruby/object:Gem::Requirement
73 none: false88 none: false
74 requirements: 89 requirements:
75 - - ">="90 - - ~>
76 - !ruby/object:Gem::Version 91 - !ruby/object:Gem::Version
77 hash: 1392 hash: 3
78 segments: 93 segments:
79 - 194 - 1
80 - 695 - 4
81 - 196 - 2
82 version: 1.6.197 version: 1.4.2
83 - - <
84 - !ruby/object:Gem::Version
85 hash: 11
86 segments:
87 - 1
88 - 7
89 - 0
90 version: 1.7.0
91 type: :runtime98 type: :runtime
92 version_requirements: *id00499 version_requirements: *id004
93- !ruby/object:Gem::Dependency 100- !ruby/object:Gem::Dependency
94 name: terminal-table101 name: interact
95 prerelease: false102 prerelease: false
96 requirement: &id005 !ruby/object:Gem::Requirement 103 requirement: &id005 !ruby/object:Gem::Requirement
97 none: false104 none: false
98 requirements: 105 requirements:
99 - - ~>106 - - ~>
100 - !ruby/object:Gem::Version 107 - !ruby/object:Gem::Version
101 hash: 3108 hash: 15
102 segments: 109 segments:
103 - 1110 - 0
104 - 4111 - 4
105 - 2112 - 0
106 version: 1.4.2113 version: 0.4.0
107 type: :runtime114 type: :runtime
108 version_requirements: *id005115 version_requirements: *id005
109- !ruby/object:Gem::Dependency 116- !ruby/object:Gem::Dependency
117 name: addressable
118 prerelease: false
119 requirement: &id006 !ruby/object:Gem::Requirement
120 none: false
121 requirements:
122 - - ~>
123 - !ruby/object:Gem::Version
124 hash: 11
125 segments:
126 - 2
127 - 2
128 - 6
129 version: 2.2.6
130 type: :runtime
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: