as 0.3.18.11

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of as might be problematic. Click here for more details.

Files changed (44) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +105 -0
  3. data/Rakefile +101 -0
  4. data/bin/as +6 -0
  5. data/caldecott_helper/Gemfile +10 -0
  6. data/caldecott_helper/Gemfile.lock +48 -0
  7. data/caldecott_helper/server.rb +43 -0
  8. data/config/clients.yml +17 -0
  9. data/config/micro/offline.conf +2 -0
  10. data/config/micro/paths.yml +22 -0
  11. data/config/micro/refresh_ip.rb +20 -0
  12. data/lib/cli/commands/admin.rb +80 -0
  13. data/lib/cli/commands/apps.rb +1208 -0
  14. data/lib/cli/commands/base.rb +233 -0
  15. data/lib/cli/commands/manifest.rb +56 -0
  16. data/lib/cli/commands/micro.rb +115 -0
  17. data/lib/cli/commands/misc.rb +140 -0
  18. data/lib/cli/commands/services.rb +217 -0
  19. data/lib/cli/commands/user.rb +65 -0
  20. data/lib/cli/config.rb +170 -0
  21. data/lib/cli/console_helper.rb +163 -0
  22. data/lib/cli/core_ext.rb +122 -0
  23. data/lib/cli/errors.rb +19 -0
  24. data/lib/cli/file_helper.rb +123 -0
  25. data/lib/cli/frameworks.rb +265 -0
  26. data/lib/cli/manifest_helper.rb +316 -0
  27. data/lib/cli/runner.rb +568 -0
  28. data/lib/cli/services_helper.rb +104 -0
  29. data/lib/cli/tunnel_helper.rb +336 -0
  30. data/lib/cli/usage.rb +125 -0
  31. data/lib/cli/version.rb +7 -0
  32. data/lib/cli/zip_util.rb +77 -0
  33. data/lib/cli.rb +48 -0
  34. data/lib/vmc/client.rb +558 -0
  35. data/lib/vmc/const.rb +27 -0
  36. data/lib/vmc/micro/switcher/base.rb +97 -0
  37. data/lib/vmc/micro/switcher/darwin.rb +19 -0
  38. data/lib/vmc/micro/switcher/dummy.rb +15 -0
  39. data/lib/vmc/micro/switcher/linux.rb +16 -0
  40. data/lib/vmc/micro/switcher/windows.rb +31 -0
  41. data/lib/vmc/micro/vmrun.rb +158 -0
  42. data/lib/vmc/micro.rb +56 -0
  43. data/lib/vmc.rb +3 -0
  44. metadata +270 -0
@@ -0,0 +1,217 @@
1
+ require "uuidtools"
2
+
3
+ module VMC::Cli::Command
4
+
5
+ class Services < Base
6
+ include VMC::Cli::ServicesHelper
7
+ include VMC::Cli::TunnelHelper
8
+
9
+ def services
10
+ ss = client.services_info
11
+ ps = client.services
12
+ ps.sort! {|a, b| a[:name] <=> b[:name] }
13
+
14
+ if @options[:json]
15
+ services = { :system => ss, :provisioned => ps }
16
+ return display JSON.pretty_generate(services)
17
+ end
18
+ display_system_services(ss)
19
+ display_provisioned_services(ps)
20
+ end
21
+
22
+ def create_service(service=nil, name=nil, appname=nil)
23
+
24
+ unless no_prompt || service
25
+ services = client.services_info
26
+ err 'No services available to provision' if services.empty?
27
+ service = ask(
28
+ "Which service would you like to provision?",
29
+ { :indexed => true,
30
+ :choices =>
31
+ services.values.collect { |type|
32
+ type.keys.collect(&:to_s)
33
+ }.flatten
34
+ }
35
+ )
36
+ end
37
+ name = @options[:name] unless name
38
+ unless name
39
+ name = random_service_name(service)
40
+ picked_name = true
41
+ end
42
+
43
+ if client.infra_supported?
44
+ unless no_prompt || @options[:infra]
45
+ @options[:infra] = client.infra_name_for_description(
46
+ ask("Select Infrastructure",
47
+ :indexed => true, :choices => client.infra_descriptions))
48
+ end
49
+ end
50
+
51
+ create_service_banner(service, name, picked_name, @options[:infra])
52
+ appname = @options[:bind] unless appname
53
+ bind_service_banner(name, appname) if appname
54
+ end
55
+
56
+ def delete_service(service=nil)
57
+ unless no_prompt || service
58
+ user_services = client.services
59
+ err 'No services available to delete' if user_services.empty?
60
+ service = ask(
61
+ "Which service would you like to delete?",
62
+ { :indexed => true,
63
+ :choices => user_services.collect { |s| s[:name] }
64
+ }
65
+ )
66
+ end
67
+ err "Service name required." unless service
68
+ display "Deleting service [#{service}]: ", false
69
+ client.delete_service(service)
70
+ display 'OK'.green
71
+ end
72
+
73
+ def bind_service(service, appname)
74
+ bind_service_banner(service, appname)
75
+ end
76
+
77
+ def unbind_service(service, appname)
78
+ unbind_service_banner(service, appname)
79
+ end
80
+
81
+ def clone_services(src_app, dest_app)
82
+ begin
83
+ src = client.app_info(src_app)
84
+ dest = client.app_info(dest_app)
85
+ rescue
86
+ end
87
+
88
+ err "Application '#{src_app}' does not exist" unless src
89
+ err "Application '#{dest_app}' does not exist" unless dest
90
+
91
+ services = src[:services]
92
+ err 'No services to clone' unless services && !services.empty?
93
+ services.each { |service| bind_service_banner(service, dest_app, false) }
94
+ check_app_for_restart(dest_app)
95
+ end
96
+
97
+ def export_service(service)
98
+ display "Exporting data from '#{service}': ", false
99
+ export_info = client.export_service(service)
100
+ if export_info
101
+ display 'OK'.green
102
+ puts export_info[:uri]
103
+ else
104
+ err "Export data from '#{service}': failed"
105
+ end
106
+ end
107
+
108
+ def import_service(service,url)
109
+ display "Importing data into '#{service}': ", false
110
+ import_info = client.import_service(service,url)
111
+ if import_info
112
+ display 'OK'.green
113
+ else
114
+ err "Import data into '#{service}' failed"
115
+ end
116
+ end
117
+
118
+ def tunnel(service=nil, client_name=nil)
119
+ unless defined? Caldecott
120
+ display "To use `as tunnel', you must first install Caldecott:"
121
+ display ""
122
+ display "\tgem install caldecott"
123
+ display ""
124
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
125
+ display "will provide one. If you're on Windows, try DevKit."
126
+ display ""
127
+ display "This manual step will be removed in the future."
128
+ display ""
129
+ err "Caldecott is not installed."
130
+ end
131
+
132
+ ps = client.services
133
+ err "No services available to tunnel to" if ps.empty?
134
+
135
+ unless service
136
+ choices = ps.collect { |s| s[:name] }.sort
137
+ service = ask(
138
+ "Which service to tunnel to?",
139
+ :choices => choices,
140
+ :indexed => true
141
+ )
142
+ end
143
+
144
+ info = ps.select { |s| s[:name] == service }.first
145
+
146
+ err "Unknown service '#{service}'" unless info
147
+
148
+ port = pick_tunnel_port(@options[:port] || 10000)
149
+
150
+ raise VMC::Client::AuthError unless client.logged_in?
151
+
152
+ infra_name = nil
153
+ if client.infra_supported?
154
+ infra_name = info[:infra] ? info[:infra][:name] : default_infra
155
+ err "Infra '#{infra_name}' is not valid" unless client.infra_valid?(infra_name)
156
+ end
157
+
158
+ if not tunnel_pushed?(infra_name)
159
+ display "Deploying tunnel application '#{tunnel_appname(infra_name)}'."
160
+ auth = UUIDTools::UUID.random_create.to_s
161
+ push_caldecott(auth,infra_name)
162
+ bind_service_banner(service, tunnel_appname(infra_name), false)
163
+ start_caldecott(infra_name)
164
+ else
165
+ auth = tunnel_auth(infra_name)
166
+ end
167
+
168
+ if not tunnel_healthy?(auth,infra_name)
169
+ display "Redeploying tunnel application '#{tunnel_appname(infra_name)}'."
170
+
171
+ # We don't expect caldecott not to be running, so take the
172
+ # most aggressive restart method.. delete/re-push
173
+ client.delete_app(tunnel_appname(infra_name))
174
+ invalidate_tunnel_app_info(infra_name)
175
+
176
+ push_caldecott(auth,infra_name)
177
+ bind_service_banner(service, tunnel_appname(infra_name), false)
178
+ start_caldecott(infra_name)
179
+ end
180
+
181
+ if not tunnel_bound?(service,infra_name)
182
+ bind_service_banner(service, tunnel_appname(infra_name))
183
+ end
184
+
185
+ conn_info = tunnel_connection_info info[:vendor], service, auth, infra_name
186
+ display_tunnel_connection_info(conn_info)
187
+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
188
+ start_tunnel(port, conn_info, auth, infra_name)
189
+
190
+ clients = get_clients_for(info[:vendor])
191
+
192
+ if clients.empty?
193
+ client_name ||= "none"
194
+ else
195
+ client_name ||= ask(
196
+ "Which client would you like to start?",
197
+ :choices => ["none"] + clients.keys,
198
+ :indexed => true
199
+ )
200
+ end
201
+
202
+ if client_name == "none"
203
+ wait_for_tunnel_end
204
+ else
205
+ wait_for_tunnel_start(port)
206
+ unless start_local_prog(clients, client_name, conn_info, port)
207
+ err "'#{client_name}' execution failed; is it in your $PATH?"
208
+ end
209
+ end
210
+ end
211
+
212
+ def get_clients_for(type)
213
+ conf = VMC::Cli::Config.clients
214
+ conf[type] || {}
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,65 @@
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
+
17
+ 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
+
23
+ err "Need a valid email" unless email
24
+ err "Need a password" unless password
25
+ login_and_save_token(email, password)
26
+ say "Successfully logged into [#{target_url}]".green
27
+ rescue VMC::Client::TargetError
28
+ display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
29
+ retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
30
+ exit 1
31
+ rescue => e
32
+ display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
33
+ exit 1
34
+ end
35
+
36
+ def logout
37
+ VMC::Cli::Config.remove_token_file
38
+ say "Successfully logged out of [#{target_url}]".green
39
+ end
40
+
41
+ def change_password(password=nil)
42
+ info = client_info
43
+ email = info[:user]
44
+ err "Need to be logged in to change password." unless email
45
+ say "Changing password for '#{email}'\n"
46
+ unless no_prompt
47
+ password = ask "New Password", :echo => "*"
48
+ password2 = ask "Verify Password", :echo => "*"
49
+ err "Passwords did not match, try again" if password != password2
50
+ end
51
+ err "Password required" unless password
52
+ client.change_password(password)
53
+ say "\nSuccessfully changed password".green
54
+ end
55
+
56
+ private
57
+
58
+ def login_and_save_token(email, password)
59
+ token = client.login(email, password)
60
+ VMC::Cli::Config.store_token(token, @options[:token_file])
61
+ end
62
+
63
+ end
64
+
65
+ end
data/lib/cli/config.rb ADDED
@@ -0,0 +1,170 @@
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.cloud.anotherservice.com'
11
+
12
+ TARGET_FILE = '~/.as_target'
13
+ TOKEN_FILE = '~/.as_token'
14
+ INSTANCES_FILE = '~/.as_instances'
15
+ ALIASES_FILE = '~/.as_aliases'
16
+ CLIENTS_FILE = '~/.as_clients'
17
+ MICRO_FILE = '~/.as_micro'
18
+
19
+ STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
20
+
21
+ class << self
22
+ attr_accessor :colorize
23
+ attr_accessor :output
24
+ attr_accessor :trace
25
+ attr_accessor :nozip
26
+ attr_accessor :infra
27
+
28
+ def target_url
29
+ return @target_url if @target_url
30
+ target_file = File.expand_path(TARGET_FILE)
31
+ if File.exists? target_file
32
+ @target_url = lock_and_read(target_file).strip
33
+ else
34
+ @target_url = DEFAULT_TARGET
35
+ end
36
+ @target_url = "http://#{@target_url}" unless /^http?/ =~ @target_url
37
+ @target_url = @target_url.gsub(/\/+$/, '')
38
+ @target_url
39
+ end
40
+
41
+ def base_of(url)
42
+ url.sub(/^[^\.]+\./, "")
43
+ end
44
+
45
+ def store_target(target_host)
46
+ target_file = File.expand_path(TARGET_FILE)
47
+ lock_and_write(target_file, target_host)
48
+ end
49
+
50
+ def all_tokens(token_file_path=nil)
51
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
52
+ return nil unless File.exists? token_file
53
+ contents = lock_and_read(token_file).strip
54
+ JSON.parse(contents)
55
+ end
56
+
57
+ alias :targets :all_tokens
58
+
59
+ def auth_token(token_file_path=nil)
60
+ return @token if @token
61
+ tokens = all_tokens(token_file_path)
62
+ @token = tokens[target_url] if tokens
63
+ end
64
+
65
+ def remove_token_file
66
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
67
+ end
68
+
69
+ def store_token(token, token_file_path=nil)
70
+ tokens = all_tokens(token_file_path) || {}
71
+ tokens[target_url] = token
72
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
73
+ lock_and_write(token_file, tokens.to_json)
74
+ end
75
+
76
+ def instances
77
+ instances_file = File.expand_path(INSTANCES_FILE)
78
+ return nil unless File.exists? instances_file
79
+ contents = lock_and_read(instances_file).strip
80
+ JSON.parse(contents)
81
+ end
82
+
83
+ def store_instances(instances)
84
+ instances_file = File.expand_path(INSTANCES_FILE)
85
+ lock_and_write(instances_file, instances.to_json)
86
+ end
87
+
88
+ def aliases
89
+ aliases_file = File.expand_path(ALIASES_FILE)
90
+ # bacward compatible
91
+ unless File.exists? aliases_file
92
+ old_aliases_file = File.expand_path('~/.vmc-aliases')
93
+ FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
94
+ end
95
+ aliases = YAML.load_file(aliases_file) rescue {}
96
+ end
97
+
98
+ def store_aliases(aliases)
99
+ aliases_file = File.expand_path(ALIASES_FILE)
100
+ File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
101
+ end
102
+
103
+ def micro
104
+ micro_file = File.expand_path(MICRO_FILE)
105
+ return {} unless File.exists? micro_file
106
+ contents = lock_and_read(micro_file).strip
107
+ JSON.parse(contents)
108
+ end
109
+
110
+ def store_micro(micro)
111
+ micro_file = File.expand_path(MICRO_FILE)
112
+ lock_and_write(micro_file, micro.to_json)
113
+ end
114
+
115
+ def deep_merge(a, b)
116
+ merge = proc do |_, old, new|
117
+ if new.is_a?(Hash) and old.is_a?(Hash)
118
+ old.merge(new, &merge)
119
+ else
120
+ new
121
+ end
122
+ end
123
+
124
+ a.merge(b, &merge)
125
+ end
126
+
127
+ def clients
128
+ return @clients if @clients
129
+
130
+ stock = YAML.load_file(STOCK_CLIENTS)
131
+ clients = File.expand_path CLIENTS_FILE
132
+ if File.exists? clients
133
+ user = YAML.load_file(clients)
134
+ @clients = deep_merge(stock, user)
135
+ else
136
+ @clients = stock
137
+ end
138
+ end
139
+
140
+ def lock_and_read(file)
141
+ File.open(file, File::RDONLY) {|f|
142
+ if defined? JRUBY_VERSION
143
+ f.flock(File::LOCK_SH)
144
+ else
145
+ f.flock(File::LOCK_EX)
146
+ end
147
+ contents = f.read
148
+ f.flock(File::LOCK_UN)
149
+ contents
150
+ }
151
+ end
152
+
153
+ def lock_and_write(file, contents)
154
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
155
+ f.flock(File::LOCK_EX)
156
+ f.rewind
157
+ f.puts contents
158
+ f.flush
159
+ f.truncate(f.pos)
160
+ f.flock(File::LOCK_UN)
161
+ }
162
+ end
163
+ end
164
+
165
+ def initialize(work_dir = Dir.pwd)
166
+ @work_dir = work_dir
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,163 @@
1
+ require 'net/telnet'
2
+ require 'readline'
3
+
4
+ module 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 = {
31
+ 'hostname' => entry[:console_ip],
32
+ 'port' => entry[:console_port]
33
+ }
34
+ end
35
+
36
+ def start_local_console(port, appname)
37
+ auth_info = console_credentials(appname)
38
+ display "Connecting to '#{appname}' console: ", false
39
+ prompt = console_login(auth_info, port)
40
+ display "OK".green
41
+ display "\n"
42
+ initialize_readline
43
+ run_console prompt
44
+ end
45
+
46
+ def console_login(auth_info, port)
47
+ if !auth_info["username"] || !auth_info["password"]
48
+ err "Unable to verify console credentials."
49
+ end
50
+ @telnet_client = telnet_client(port)
51
+ prompt = nil
52
+ err_msg = "Login attempt timed out."
53
+ 5.times do
54
+ begin
55
+ results = @telnet_client.login("Name"=>auth_info["username"],
56
+ "Password"=>auth_info["password"])
57
+ lines = results.sub("Login: Password: ", "").split("\n")
58
+ last_line = lines.pop
59
+ if last_line =~ /[$%#>] \z/n
60
+ prompt = last_line
61
+ elsif last_line =~ /Login failed/
62
+ err_msg = last_line
63
+ end
64
+ break
65
+ rescue TimeoutError
66
+ sleep 1
67
+ rescue EOFError
68
+ #This may happen if we login right after app starts
69
+ close_console
70
+ sleep 5
71
+ @telnet_client = telnet_client(port)
72
+ end
73
+ display ".", false
74
+ end
75
+ unless prompt
76
+ close_console
77
+ err err_msg
78
+ end
79
+ prompt
80
+ end
81
+
82
+ def send_console_command(cmd)
83
+ results = @telnet_client.cmd(cmd)
84
+ results.split("\n")
85
+ end
86
+
87
+ def console_credentials(appname)
88
+ content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
89
+ YAML.load(content)
90
+ end
91
+
92
+ def close_console
93
+ @telnet_client.close
94
+ end
95
+
96
+ def console_tab_completion_data(cmd)
97
+ begin
98
+ results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
99
+ results.chomp.split(",")
100
+ rescue TimeoutError
101
+ [] #Just return empty results if timeout occurred on tab completion
102
+ end
103
+ end
104
+
105
+ private
106
+ def telnet_client(port)
107
+ Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
108
+ end
109
+
110
+ def readline_with_history(prompt)
111
+ line = Readline::readline(prompt)
112
+ return nil if line == nil || line == 'quit' || line == 'exit'
113
+ Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
114
+ line
115
+ end
116
+
117
+ def run_console(prompt)
118
+ prev = trap("INT") { |x| exit_console; prev.call(x); exit }
119
+ prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
120
+ loop do
121
+ cmd = readline_with_history(prompt)
122
+ if(cmd == nil)
123
+ exit_console
124
+ break
125
+ end
126
+ prompt = send_console_command_display_results(cmd, prompt)
127
+ end
128
+ end
129
+
130
+ def exit_console
131
+ #TimeoutError expected, as exit doesn't return anything
132
+ @telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
133
+ close_console
134
+ end
135
+
136
+ def send_console_command_display_results(cmd, prompt)
137
+ begin
138
+ lines = send_console_command cmd
139
+ #Assumes the last line is a prompt
140
+ prompt = lines.pop
141
+ lines.each {|line| display line if line != cmd}
142
+ rescue TimeoutError
143
+ display "Timed out sending command to server.".red
144
+ rescue EOFError
145
+ err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
146
+ end
147
+ prompt
148
+ end
149
+
150
+ def initialize_readline
151
+ if Readline.respond_to?("basic_word_break_characters=")
152
+ Readline.basic_word_break_characters= " \t\n`><=;|&{("
153
+ end
154
+ Readline.completion_append_character = nil
155
+ #Assumes that sending a String ending with tab will return a non-empty
156
+ #String of comma-separated completion options, terminated by a new line
157
+ #For example, "app.\t" might result in "to_s,nil?,etc\n"
158
+ Readline.completion_proc = proc {|s|
159
+ console_tab_completion_data s
160
+ }
161
+ end
162
+ end
163
+ end