vmc 0.0.8 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +8 -3
- data/README.md +83 -0
- data/Rakefile +11 -65
- data/bin/vmc +3 -2
- data/lib/cli/commands/admin.rb +57 -0
- data/lib/cli/commands/apps.rb +828 -0
- data/lib/cli/commands/base.rb +56 -0
- data/lib/cli/commands/misc.rb +99 -0
- data/lib/cli/commands/services.rb +84 -0
- data/lib/cli/commands/user.rb +60 -0
- data/lib/cli/config.rb +109 -0
- data/lib/cli/core_ext.rb +119 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +97 -0
- data/lib/cli/runner.rb +437 -0
- data/lib/cli/services_helper.rb +74 -0
- data/lib/cli/usage.rb +94 -0
- data/lib/cli/version.rb +5 -0
- data/lib/cli/zip_util.rb +61 -0
- data/lib/cli.rb +30 -0
- data/lib/vmc/client.rb +415 -0
- data/lib/vmc/const.rb +19 -0
- data/lib/vmc.rb +2 -1589
- data/spec/assets/app_info.txt +9 -0
- data/spec/assets/app_listings.txt +9 -0
- data/spec/assets/bad_create_app.txt +9 -0
- data/spec/assets/delete_app.txt +9 -0
- data/spec/assets/global_service_listings.txt +9 -0
- data/spec/assets/good_create_app.txt +9 -0
- data/spec/assets/good_create_service.txt +9 -0
- data/spec/assets/info_authenticated.txt +27 -0
- data/spec/assets/info_return.txt +15 -0
- data/spec/assets/info_return_bad.txt +16 -0
- data/spec/assets/login_fail.txt +9 -0
- data/spec/assets/login_success.txt +9 -0
- data/spec/assets/sample_token.txt +1 -0
- data/spec/assets/service_already_exists.txt +9 -0
- data/spec/assets/service_listings.txt +9 -0
- data/spec/assets/service_not_found.txt +9 -0
- data/spec/assets/user_info.txt +9 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/unit/cli_opts_spec.rb +73 -0
- data/spec/unit/client_spec.rb +284 -0
- metadata +114 -71
- data/README +0 -58
- data/lib/parse.rb +0 -719
- data/lib/vmc_base.rb +0 -205
- data/vendor/gems/httpclient/VERSION +0 -1
- data/vendor/gems/httpclient/lib/http-access2/cookie.rb +0 -1
- data/vendor/gems/httpclient/lib/http-access2/http.rb +0 -1
- data/vendor/gems/httpclient/lib/http-access2.rb +0 -53
- data/vendor/gems/httpclient/lib/httpclient/auth.rb +0 -522
- data/vendor/gems/httpclient/lib/httpclient/cacert.p7s +0 -1579
- data/vendor/gems/httpclient/lib/httpclient/cacert_sha1.p7s +0 -1579
- data/vendor/gems/httpclient/lib/httpclient/connection.rb +0 -84
- data/vendor/gems/httpclient/lib/httpclient/cookie.rb +0 -562
- data/vendor/gems/httpclient/lib/httpclient/http.rb +0 -867
- data/vendor/gems/httpclient/lib/httpclient/session.rb +0 -864
- data/vendor/gems/httpclient/lib/httpclient/ssl_config.rb +0 -417
- data/vendor/gems/httpclient/lib/httpclient/timeout.rb +0 -136
- data/vendor/gems/httpclient/lib/httpclient/util.rb +0 -86
- data/vendor/gems/httpclient/lib/httpclient.rb +0 -1020
- data/vendor/gems/httpclient/lib/tags +0 -908
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'terminal-table/import'
|
4
|
+
require 'highline/import'
|
5
|
+
|
6
|
+
module VMC::Cli
|
7
|
+
|
8
|
+
module Command
|
9
|
+
|
10
|
+
class Base
|
11
|
+
attr_reader :no_prompt, :prompt_ok
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
@options = options.dup
|
15
|
+
@no_prompt = @options[:noprompts]
|
16
|
+
@prompt_ok = !no_prompt
|
17
|
+
|
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.
|
24
|
+
if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
|
25
|
+
VMC::Cli::Config.colorize = false
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def client
|
31
|
+
return @client if @client
|
32
|
+
@client = VMC::Client.new(target_url, auth_token)
|
33
|
+
@client.trace = true if VMC::Cli::Config.trace
|
34
|
+
@client.proxy_for @options[:proxy] if @options[:proxy]
|
35
|
+
@client
|
36
|
+
end
|
37
|
+
|
38
|
+
def client_info
|
39
|
+
return @client_info if @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
|
+
end
|
47
|
+
|
48
|
+
def auth_token
|
49
|
+
return @auth_token if @auth_token
|
50
|
+
@auth_token = VMC::Cli::Config.auth_token
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module VMC::Cli::Command
|
2
|
+
|
3
|
+
class Misc < Base
|
4
|
+
def version
|
5
|
+
say "vmc #{VMC::Cli::VERSION}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def target
|
9
|
+
return display JSON.pretty_generate({:target => target_url}) if @options[:json]
|
10
|
+
banner "[#{target_url}]"
|
11
|
+
end
|
12
|
+
|
13
|
+
def targets
|
14
|
+
targets = VMC::Cli::Config.targets
|
15
|
+
return display JSON.pretty_generate(targets) if @options[:json]
|
16
|
+
return display 'None specified' if targets.empty?
|
17
|
+
targets_table = table do |t|
|
18
|
+
t.headings = 'Target', 'Authorization'
|
19
|
+
targets.each { |target, token| t << [target, token] }
|
20
|
+
end
|
21
|
+
display "\n"
|
22
|
+
display targets_table
|
23
|
+
end
|
24
|
+
|
25
|
+
alias :tokens :targets
|
26
|
+
|
27
|
+
def set_target(target_url)
|
28
|
+
target_url = "http://#{target_url}" unless /^https?/ =~ target_url
|
29
|
+
client = VMC::Client.new(target_url)
|
30
|
+
unless client.target_valid?
|
31
|
+
if prompt_ok
|
32
|
+
display "Host is not valid: '#{target_url}'".red
|
33
|
+
show_response = ask "Would you like see the response [yN]? "
|
34
|
+
display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response.upcase == 'Y'
|
35
|
+
end
|
36
|
+
exit(false)
|
37
|
+
else
|
38
|
+
VMC::Cli::Config.store_target(target_url)
|
39
|
+
say "Succesfully targeted to [#{target_url}]".green
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def info
|
44
|
+
info = client.info
|
45
|
+
return display JSON.pretty_generate(info) if @options[:json]
|
46
|
+
|
47
|
+
display "\n#{info[:description]}"
|
48
|
+
display "For support visit #{info[:support]}"
|
49
|
+
display ""
|
50
|
+
display "Target: #{target_url} (v#{info[:version]})"
|
51
|
+
display "Client: v#{VMC::Cli::VERSION}"
|
52
|
+
if info[:user]
|
53
|
+
display ''
|
54
|
+
display "User: #{info[:user]}"
|
55
|
+
end
|
56
|
+
if usage = info[:usage] and limits = info[:limits]
|
57
|
+
tmem = pretty_size(limits[:memory]*1024*1024)
|
58
|
+
mem = pretty_size(usage[:memory]*1024*1024)
|
59
|
+
tser = limits[:services]
|
60
|
+
ser = usage[:services]
|
61
|
+
tapps = limits[:apps] || 0
|
62
|
+
apps = usage[:apps] || 0
|
63
|
+
display "Usage: Memory (#{mem} of #{tmem} total)"
|
64
|
+
display " Services (#{ser} of #{tser} total)"
|
65
|
+
display " Apps (#{apps} of #{tapps} total)" if limits[:apps]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def aliases
|
70
|
+
aliases = VMC::Cli::Config.aliases
|
71
|
+
return display JSON.pretty_generate(aliases) if @options[:json]
|
72
|
+
return display "No Aliases" if aliases.empty?
|
73
|
+
atable = table do |t|
|
74
|
+
t.headings = 'Alias', "Command"
|
75
|
+
aliases.each { |k,v| t << [v, k] }
|
76
|
+
end
|
77
|
+
display "\n"
|
78
|
+
display atable
|
79
|
+
end
|
80
|
+
|
81
|
+
def alias(k, v=nil)
|
82
|
+
k,v = k.split('=') unless v
|
83
|
+
aliases = VMC::Cli::Config.aliases
|
84
|
+
aliases[k] = v
|
85
|
+
VMC::Cli::Config.store_aliases(aliases)
|
86
|
+
display "Successfully aliased '#{k}' to '#{v}'".green
|
87
|
+
end
|
88
|
+
|
89
|
+
def unalias(key)
|
90
|
+
aliases = VMC::Cli::Config.aliases
|
91
|
+
aliases.delete(key)
|
92
|
+
VMC::Cli::Config.store_aliases(aliases)
|
93
|
+
display "Successfully unaliased '#{key}'".green
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module VMC::Cli::Command
|
2
|
+
|
3
|
+
class Services < Base
|
4
|
+
include VMC::Cli::ServicesHelper
|
5
|
+
|
6
|
+
def services
|
7
|
+
ss = client.services_info
|
8
|
+
ps = client.services
|
9
|
+
if @options[:json]
|
10
|
+
services = { :system => ss, :provisioned => ps }
|
11
|
+
return display JSON.pretty_generate(services)
|
12
|
+
end
|
13
|
+
display_system_services(ss)
|
14
|
+
display_provisioned_services(ps)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_service(service=nil, name=nil, appname=nil)
|
18
|
+
unless no_prompt || service
|
19
|
+
services = client.services_info
|
20
|
+
err 'No services available to provision' if services.empty?
|
21
|
+
choose do |menu|
|
22
|
+
menu.prompt = 'Please select one you wish to provision: '
|
23
|
+
menu.select_by = :index_or_name
|
24
|
+
services.each do |service_type, value|
|
25
|
+
value.each do |vendor, version|
|
26
|
+
menu.choice(vendor.to_s) { service = vendor.to_s }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
name = @options[:name] unless name
|
32
|
+
unless name
|
33
|
+
name = random_service_name(service)
|
34
|
+
picked_name = true
|
35
|
+
end
|
36
|
+
create_service_banner(service, name, picked_name)
|
37
|
+
appname = @options[:bind] unless appname
|
38
|
+
bind_service_banner(name, appname) if appname
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_service(service=nil)
|
42
|
+
unless no_prompt || service
|
43
|
+
user_services = client.services
|
44
|
+
err 'No services available to delete' if user_services.empty?
|
45
|
+
choose do |menu|
|
46
|
+
menu.prompt = 'Please select one you wish to delete: '
|
47
|
+
menu.select_by = :index_or_name
|
48
|
+
user_services.each do |s|
|
49
|
+
menu.choice(s[:name]) { service = s[:name] }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
err "Service name required." unless service
|
54
|
+
display "Deleting service [#{service}]: ", false
|
55
|
+
client.delete_service(service)
|
56
|
+
display 'OK'.green
|
57
|
+
end
|
58
|
+
|
59
|
+
def bind_service(service, appname)
|
60
|
+
bind_service_banner(service, appname)
|
61
|
+
end
|
62
|
+
|
63
|
+
def unbind_service(service, appname)
|
64
|
+
unbind_service_banner(service, appname)
|
65
|
+
end
|
66
|
+
|
67
|
+
def clone_services(src_app, dest_app)
|
68
|
+
begin
|
69
|
+
src = client.app_info(src_app)
|
70
|
+
dest = client.app_info(dest_app)
|
71
|
+
rescue
|
72
|
+
end
|
73
|
+
|
74
|
+
err "Application '#{src_app}' does not exist" unless src
|
75
|
+
err "Application '#{dest_app}' does not exist" unless dest
|
76
|
+
|
77
|
+
services = src[:services]
|
78
|
+
err 'No services to clone' unless services && !services.empty?
|
79
|
+
services.each { |service| bind_service_banner(service, dest_app, false) }
|
80
|
+
check_app_for_restart(dest_app)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module VMC::Cli::Command
|
2
|
+
|
3
|
+
class User < Base
|
4
|
+
|
5
|
+
def info
|
6
|
+
info = client.info
|
7
|
+
username = info[:user] || 'N/A'
|
8
|
+
return display JSON.pretty_generate([username]) if @options[:json]
|
9
|
+
display "\n[#{username}]"
|
10
|
+
end
|
11
|
+
|
12
|
+
def login(email=nil)
|
13
|
+
email = @options[:email] unless email
|
14
|
+
password = @options[:password]
|
15
|
+
tries = 0
|
16
|
+
email = ask("Email: ") unless no_prompt || email
|
17
|
+
password = ask("Password: ") {|q| q.echo = '*'} unless no_prompt || password
|
18
|
+
err "Need a valid email" unless email
|
19
|
+
err "Need a password" unless password
|
20
|
+
login_and_save_token(email, password)
|
21
|
+
say "Successfully logged into [#{target_url}]".green
|
22
|
+
rescue VMC::Client::TargetError
|
23
|
+
display "Problem with login, invalid account or password.".red
|
24
|
+
retry if (tries += 1) < 3 && prompt_ok
|
25
|
+
exit 1
|
26
|
+
rescue => e
|
27
|
+
display "Problem with login, #{e}, try again or register for an account.".red
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def logout
|
32
|
+
VMC::Cli::Config.remove_token_file
|
33
|
+
say "Successfully logged out of [#{target_url}]".green
|
34
|
+
end
|
35
|
+
|
36
|
+
def change_password(password=nil)
|
37
|
+
info = client.info
|
38
|
+
email = info[:user]
|
39
|
+
err "Need to be logged in to change password." unless email
|
40
|
+
say "Changing password for '#{email}'\n"
|
41
|
+
unless no_prompt
|
42
|
+
password = ask("New Password: ") {|q| q.echo = '*'}
|
43
|
+
password2 = ask("Verify Password: ") {|q| q.echo = '*'}
|
44
|
+
err "Passwords did not match, try again" if password != password2
|
45
|
+
end
|
46
|
+
err "Password required" unless password
|
47
|
+
client.change_password(password)
|
48
|
+
say "\nSuccessfully changed password".green
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def login_and_save_token(email, password)
|
54
|
+
token = client.login(email, password)
|
55
|
+
VMC::Cli::Config.store_token(token)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/cli/config.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json/pure'
|
6
|
+
|
7
|
+
module VMC::Cli
|
8
|
+
class Config
|
9
|
+
|
10
|
+
DEFAULT_TARGET = 'api.vcap.me'
|
11
|
+
DEFAULT_SUGGEST = 'vcap.me'
|
12
|
+
|
13
|
+
TARGET_FILE = '~/.vmc_target'
|
14
|
+
TOKEN_FILE = '~/.vmc_token'
|
15
|
+
INSTANCES_FILE = '~/.vmc_instances'
|
16
|
+
ALIASES_FILE = '~/.vmc_aliases'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :colorize
|
20
|
+
attr_accessor :output
|
21
|
+
attr_accessor :trace
|
22
|
+
attr_accessor :nozip
|
23
|
+
attr_reader :suggest_url
|
24
|
+
|
25
|
+
def target_url
|
26
|
+
return @target_url if @target_url
|
27
|
+
target_file = File.expand_path(TARGET_FILE)
|
28
|
+
if File.exists? target_file
|
29
|
+
@target_url = File.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
|
+
else
|
35
|
+
@target_url = DEFAULT_TARGET
|
36
|
+
@suggest_url = DEFAULT_SUGGEST
|
37
|
+
end
|
38
|
+
@target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
|
39
|
+
@target_url
|
40
|
+
end
|
41
|
+
|
42
|
+
def store_target(target_host)
|
43
|
+
target_file = File.expand_path(TARGET_FILE)
|
44
|
+
File.open(target_file, 'w+') { |f| f.puts target_host }
|
45
|
+
FileUtils.chmod 0600, target_file
|
46
|
+
end
|
47
|
+
|
48
|
+
def all_tokens
|
49
|
+
token_file = File.expand_path(TOKEN_FILE)
|
50
|
+
return nil unless File.exists? token_file
|
51
|
+
contents = File.read(token_file).strip
|
52
|
+
JSON.parse(contents)
|
53
|
+
end
|
54
|
+
|
55
|
+
alias :targets :all_tokens
|
56
|
+
|
57
|
+
def auth_token
|
58
|
+
return @token if @token
|
59
|
+
tokens = all_tokens
|
60
|
+
@token = tokens[target_url] if tokens
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_token_file
|
64
|
+
FileUtils.rm_f(File.expand_path(TOKEN_FILE))
|
65
|
+
end
|
66
|
+
|
67
|
+
def store_token(token)
|
68
|
+
tokens = all_tokens || {}
|
69
|
+
tokens[target_url] = token
|
70
|
+
token_file = File.expand_path(TOKEN_FILE)
|
71
|
+
File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }
|
72
|
+
FileUtils.chmod 0600, token_file
|
73
|
+
end
|
74
|
+
|
75
|
+
def instances
|
76
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
77
|
+
return nil unless File.exists? instances_file
|
78
|
+
contents = File.read(instances_file).strip
|
79
|
+
JSON.parse(contents)
|
80
|
+
end
|
81
|
+
|
82
|
+
def store_instances(instances)
|
83
|
+
instances_file = File.expand_path(INSTANCES_FILE)
|
84
|
+
File.open(instances_file, 'w') { |f| f.write(instances.to_json) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def aliases
|
88
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
89
|
+
# bacward compatible
|
90
|
+
unless File.exists? aliases_file
|
91
|
+
old_aliases_file = File.expand_path('~/.vmc-aliases')
|
92
|
+
FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
|
93
|
+
end
|
94
|
+
aliases = YAML.load_file(aliases_file) rescue {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def store_aliases(aliases)
|
98
|
+
aliases_file = File.expand_path(ALIASES_FILE)
|
99
|
+
File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize(work_dir = Dir.pwd)
|
105
|
+
@work_dir = work_dir
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
data/lib/cli/core_ext.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module VMCExtensions
|
2
|
+
|
3
|
+
def say(message)
|
4
|
+
VMC::Cli::Config.output.puts(message) if VMC::Cli::Config.output
|
5
|
+
end
|
6
|
+
|
7
|
+
def header(message, filler = '-')
|
8
|
+
say "\n"
|
9
|
+
say message
|
10
|
+
say filler.to_s * message.size
|
11
|
+
end
|
12
|
+
|
13
|
+
def banner(message)
|
14
|
+
say "\n"
|
15
|
+
say message
|
16
|
+
end
|
17
|
+
|
18
|
+
def display(message, nl=true)
|
19
|
+
if nl
|
20
|
+
say message
|
21
|
+
else
|
22
|
+
if VMC::Cli::Config.output
|
23
|
+
VMC::Cli::Config.output.print(message)
|
24
|
+
VMC::Cli::Config.output.flush
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear(size=80)
|
30
|
+
return unless VMC::Cli::Config.output
|
31
|
+
VMC::Cli::Config.output.print("\r")
|
32
|
+
VMC::Cli::Config.output.print(" " * size)
|
33
|
+
VMC::Cli::Config.output.print("\r")
|
34
|
+
#VMC::Cli::Config.output.flush
|
35
|
+
end
|
36
|
+
|
37
|
+
def err(message, prefix='Error: ')
|
38
|
+
raise VMC::Cli::CliExit, "#{prefix}#{message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def quit(message = nil)
|
42
|
+
raise VMC::Cli::GracefulExit, message
|
43
|
+
end
|
44
|
+
|
45
|
+
def blank?
|
46
|
+
self.to_s.blank?
|
47
|
+
end
|
48
|
+
|
49
|
+
def uptime_string(delta)
|
50
|
+
num_seconds = delta.to_i
|
51
|
+
days = num_seconds / (60 * 60 * 24);
|
52
|
+
num_seconds -= days * (60 * 60 * 24);
|
53
|
+
hours = num_seconds / (60 * 60);
|
54
|
+
num_seconds -= hours * (60 * 60);
|
55
|
+
minutes = num_seconds / 60;
|
56
|
+
num_seconds -= minutes * 60;
|
57
|
+
"#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
|
58
|
+
end
|
59
|
+
|
60
|
+
def pretty_size(size, prec=1)
|
61
|
+
return 'NA' unless size
|
62
|
+
return "#{size}B" if size < 1024
|
63
|
+
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
|
64
|
+
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))
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module VMCStringExtensions
|
71
|
+
|
72
|
+
def red
|
73
|
+
colorize("\e[0m\e[31m")
|
74
|
+
end
|
75
|
+
|
76
|
+
def green
|
77
|
+
colorize("\e[0m\e[32m")
|
78
|
+
end
|
79
|
+
|
80
|
+
def yellow
|
81
|
+
colorize("\e[0m\e[33m")
|
82
|
+
end
|
83
|
+
|
84
|
+
def bold
|
85
|
+
colorize("\e[0m\e[1m")
|
86
|
+
end
|
87
|
+
|
88
|
+
def colorize(color_code)
|
89
|
+
if VMC::Cli::Config.colorize
|
90
|
+
"#{color_code}#{self}\e[0m"
|
91
|
+
else
|
92
|
+
self
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def blank?
|
97
|
+
self =~ /^\s*$/
|
98
|
+
end
|
99
|
+
|
100
|
+
def truncate(limit = 30)
|
101
|
+
return "" if self.blank?
|
102
|
+
etc = "..."
|
103
|
+
stripped = self.strip[0..limit]
|
104
|
+
if stripped.length > limit
|
105
|
+
stripped.gsub(/\s+?(\S+)?$/, "") + etc
|
106
|
+
else
|
107
|
+
stripped
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
class Object
|
114
|
+
include VMCExtensions
|
115
|
+
end
|
116
|
+
|
117
|
+
class String
|
118
|
+
include VMCStringExtensions
|
119
|
+
end
|
data/lib/cli/errors.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module VMC::Cli
|
2
|
+
|
3
|
+
class CliError < StandardError
|
4
|
+
def self.error_code(code = nil)
|
5
|
+
define_method(:error_code) { code }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class UnknownCommand < CliError; error_code(100); end
|
10
|
+
class TargetMissing < CliError; error_code(102); end
|
11
|
+
class TargetInaccessible < CliError; error_code(103); end
|
12
|
+
|
13
|
+
class TargetError < CliError; error_code(201); end
|
14
|
+
class AuthError < TargetError; error_code(202); end
|
15
|
+
|
16
|
+
class CliExit < CliError; error_code(400); end
|
17
|
+
class GracefulExit < CliExit; error_code(401); end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module VMC::Cli
|
2
|
+
|
3
|
+
class Framework
|
4
|
+
|
5
|
+
DEFAULT_FRAMEWORK = "http://b20nine.com/unknown"
|
6
|
+
DEFAULT_MEM = '256M'
|
7
|
+
|
8
|
+
FRAMEWORKS = {
|
9
|
+
'Rails' => ['rails/1.0', { :mem => '256M', :description => 'Rails Application'}],
|
10
|
+
'Spring' => ['spring_web/1.0', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
|
11
|
+
'Grails' => ['grails/1.0', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
|
12
|
+
'Roo' => ['spring_web/1.0', { :mem => '512M', :description => 'Java SpringSource Roo Application'}],
|
13
|
+
'JavaWeb' => ['spring_web/1.0', { :mem => '512M', :description => 'Java Web Application'}],
|
14
|
+
'Sinatra' => [DEFAULT_FRAMEWORK, { :mem => '128M', :description => 'Sinatra Application'}],
|
15
|
+
'Node' => ['nodejs/1.0', { :mem => '64M', :description => 'NodeJS Application'}],
|
16
|
+
}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def known_frameworks
|
21
|
+
FRAMEWORKS.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def lookup(name)
|
25
|
+
return Framework.new(*FRAMEWORKS[name])
|
26
|
+
end
|
27
|
+
|
28
|
+
def detect(path)
|
29
|
+
Dir.chdir(path) do
|
30
|
+
|
31
|
+
# Rails
|
32
|
+
if File.exist?('config/environment.rb')
|
33
|
+
return Framework.lookup('Rails')
|
34
|
+
|
35
|
+
# Java
|
36
|
+
elsif Dir.glob('*.war').first
|
37
|
+
war_file = Dir.glob('*.war').first
|
38
|
+
contents = ZipUtil.entry_lines(war_file)
|
39
|
+
|
40
|
+
# Spring Variations
|
41
|
+
if contents =~ /WEB-INF\/grails-app/
|
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
|
50
|
+
|
51
|
+
# Simple Ruby Apps
|
52
|
+
elsif !Dir.glob('*.rb').empty?
|
53
|
+
matched_file = nil
|
54
|
+
Dir.glob('*.rb').each do |fname|
|
55
|
+
next if matched_file
|
56
|
+
File.open(fname, 'r') do |f|
|
57
|
+
str = f.read # This might want to be limited
|
58
|
+
matched_file = fname if (str && str.match(/^\s*require\s*'sinatra'/))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if matched_file && !File.exist?('config.ru')
|
62
|
+
f = Framework.lookup('Sinatra')
|
63
|
+
f.exec = "ruby #{matched_file}"
|
64
|
+
return f
|
65
|
+
end
|
66
|
+
|
67
|
+
# Node.JS
|
68
|
+
elsif !Dir.glob('*.js').empty?
|
69
|
+
# Fixme, make other files work too..
|
70
|
+
if File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
|
71
|
+
return Framework.lookup('Node')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :name, :description, :memory
|
81
|
+
attr_accessor :exec
|
82
|
+
|
83
|
+
alias :mem :memory
|
84
|
+
|
85
|
+
def initialize(framework=nil, opts={})
|
86
|
+
@name = framework || DEFAULT_FRAMEWORK
|
87
|
+
@memory = opts[:mem] || DEFAULT_MEM
|
88
|
+
@description = opts[:description] || 'Unknown Application Type'
|
89
|
+
@exec = opts[:exec]
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
description
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|