vmc 0.3.13.beta.2 → 0.3.13.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Rakefile +84 -2
  2. data/lib/cli.rb +2 -3
  3. data/lib/cli/commands/admin.rb +9 -6
  4. data/lib/cli/commands/apps.rb +144 -111
  5. data/lib/cli/commands/base.rb +8 -15
  6. data/lib/cli/commands/misc.rb +3 -2
  7. data/lib/cli/commands/services.rb +15 -16
  8. data/lib/cli/commands/user.rb +11 -2
  9. data/lib/cli/config.rb +5 -1
  10. data/lib/cli/core_ext.rb +331 -0
  11. data/lib/cli/frameworks.rb +10 -4
  12. data/lib/cli/runner.rb +4 -0
  13. data/lib/cli/services_helper.rb +8 -2
  14. data/lib/cli/usage.rb +4 -3
  15. data/lib/cli/version.rb +1 -1
  16. data/lib/vmc/client.rb +4 -0
  17. data/lib/vmc/const.rb +1 -0
  18. metadata +13 -46
  19. data/spec/assets/app_info.txt +0 -9
  20. data/spec/assets/app_listings.txt +0 -9
  21. data/spec/assets/bad_create_app.txt +0 -9
  22. data/spec/assets/delete_app.txt +0 -9
  23. data/spec/assets/global_service_listings.txt +0 -9
  24. data/spec/assets/good_create_app.txt +0 -9
  25. data/spec/assets/good_create_service.txt +0 -9
  26. data/spec/assets/info_authenticated.txt +0 -27
  27. data/spec/assets/info_return.txt +0 -15
  28. data/spec/assets/info_return_bad.txt +0 -16
  29. data/spec/assets/list_users.txt +0 -13
  30. data/spec/assets/login_fail.txt +0 -9
  31. data/spec/assets/login_success.txt +0 -9
  32. data/spec/assets/sample_token.txt +0 -1
  33. data/spec/assets/service_already_exists.txt +0 -9
  34. data/spec/assets/service_gateway_fail.txt +0 -9
  35. data/spec/assets/service_listings.txt +0 -9
  36. data/spec/assets/service_not_found.txt +0 -9
  37. data/spec/assets/user_info.txt +0 -9
  38. data/spec/spec_helper.rb +0 -11
  39. data/spec/unit/cli_opts_spec.rb +0 -68
  40. data/spec/unit/client_spec.rb +0 -345
@@ -1,7 +1,6 @@
1
1
 
2
2
  require 'rubygems'
3
3
  require 'terminal-table/import'
4
- require 'highline/import'
5
4
 
6
5
  module VMC::Cli
7
6
 
@@ -15,16 +14,10 @@ module VMC::Cli
15
14
  @no_prompt = @options[:noprompts]
16
15
  @prompt_ok = !no_prompt
17
16
 
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
17
  # Suppress colorize on Windows systems for now.
24
- if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
18
+ if WINDOWS
25
19
  VMC::Cli::Config.colorize = false
26
20
  end
27
-
28
21
  end
29
22
 
30
23
  def client
@@ -36,18 +29,19 @@ module VMC::Cli
36
29
  end
37
30
 
38
31
  def client_info
39
- return @client_info if @client_info
40
- @client_info = client.info
32
+ @client_info ||= client.info
41
33
  end
42
34
 
43
35
  def target_url
44
- return @target_url if @target_url
45
- @target_url = VMC::Cli::Config.target_url
36
+ @target_url ||= VMC::Cli::Config.target_url
37
+ end
38
+
39
+ def target_base
40
+ @target_base ||= VMC::Cli::Config.suggest_url
46
41
  end
47
42
 
48
43
  def auth_token
49
- return @auth_token if @auth_token
50
- @auth_token = VMC::Cli::Config.auth_token
44
+ @auth_token ||= VMC::Cli::Config.auth_token
51
45
  end
52
46
 
53
47
  def runtimes_info
@@ -72,7 +66,6 @@ module VMC::Cli
72
66
  end
73
67
  @frameworks
74
68
  end
75
-
76
69
  end
77
70
  end
78
71
  end
@@ -31,8 +31,9 @@ module VMC::Cli::Command
31
31
  unless client.target_valid?
32
32
  if prompt_ok
33
33
  display "Host is not available or is not valid: '#{target_url}'".red
34
- show_response = ask "Would you like see the response [yN]? "
35
- display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response.upcase == 'Y'
34
+ show_response = ask "Would you like see the response?",
35
+ :default => false
36
+ display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response
36
37
  end
37
38
  exit(false)
38
39
  else
@@ -20,15 +20,15 @@ module VMC::Cli::Command
20
20
  unless no_prompt || service
21
21
  services = client.services_info
22
22
  err 'No services available to provision' if services.empty?
23
- choose do |menu|
24
- menu.prompt = 'Please select one you wish to provision: '
25
- menu.select_by = :index_or_name
26
- services.each do |service_type, value|
27
- value.each do |vendor, version|
28
- menu.choice(vendor.to_s) { service = vendor.to_s }
29
- end
30
- end
31
- end
23
+ service = ask(
24
+ "Which service would you like to provision?",
25
+ { :indexed => true,
26
+ :choices =>
27
+ services.values.collect { |type|
28
+ type.keys.collect(&:to_s)
29
+ }.flatten
30
+ }
31
+ )
32
32
  end
33
33
  name = @options[:name] unless name
34
34
  unless name
@@ -44,13 +44,12 @@ module VMC::Cli::Command
44
44
  unless no_prompt || service
45
45
  user_services = client.services
46
46
  err 'No services available to delete' if user_services.empty?
47
- choose do |menu|
48
- menu.prompt = 'Please select one you wish to delete: '
49
- menu.select_by = :index_or_name
50
- user_services.each do |s|
51
- menu.choice(s[:name]) { service = s[:name] }
52
- end
53
- end
47
+ service = ask(
48
+ "Which service would you like to delete?",
49
+ { :indexed => true,
50
+ :choices => user_services.collect { |s| s[:name] }
51
+ }
52
+ )
54
53
  end
55
54
  err "Service name required." unless service
56
55
  display "Deleting service [#{service}]: ", false
@@ -13,8 +13,17 @@ module VMC::Cli::Command
13
13
  email = @options[:email] unless email
14
14
  password = @options[:password]
15
15
  tries ||= 0
16
+ <<<<<<< HEAD
16
17
  email = ask("Email: ") unless no_prompt || email
17
18
  password = ask("Password: ") {|q| q.echo = '*'} unless no_prompt || password
19
+ =======
20
+
21
+ unless no_prompt
22
+ email ||= ask("Email")
23
+ password ||= ask("Password", :echo => "*")
24
+ end
25
+
26
+ >>>>>>> local-staging/master
18
27
  err "Need a valid email" unless email
19
28
  err "Need a password" unless password
20
29
  login_and_save_token(email, password)
@@ -39,8 +48,8 @@ module VMC::Cli::Command
39
48
  err "Need to be logged in to change password." unless email
40
49
  say "Changing password for '#{email}'\n"
41
50
  unless no_prompt
42
- password = ask("New Password: ") {|q| q.echo = '*'}
43
- password2 = ask("Verify Password: ") {|q| q.echo = '*'}
51
+ password = ask "New Password", :echo => "*"
52
+ password2 = ask "Verify Password", :echo => "*"
44
53
  err "Passwords did not match, try again" if password != password2
45
54
  end
46
55
  err "Password required" unless password
@@ -20,7 +20,6 @@ module VMC::Cli
20
20
  attr_accessor :output
21
21
  attr_accessor :trace
22
22
  attr_accessor :nozip
23
- attr_reader :suggest_url
24
23
 
25
24
  def target_url
26
25
  return @target_url if @target_url
@@ -40,6 +39,11 @@ module VMC::Cli
40
39
  @target_url
41
40
  end
42
41
 
42
+ def suggest_url
43
+ target_url
44
+ @suggest_url
45
+ end
46
+
43
47
  def store_target(target_host)
44
48
  target_file = File.expand_path(TARGET_FILE)
45
49
  lock_and_write(target_file, target_host)
@@ -38,6 +38,10 @@ module VMCExtensions
38
38
  raise VMC::Cli::CliExit, "#{prefix}#{message}"
39
39
  end
40
40
 
41
+ def warn(msg)
42
+ say "#{"[WARNING]".yellow} #{msg}"
43
+ end
44
+
41
45
  def quit(message = nil)
42
46
  raise VMC::Cli::GracefulExit, message
43
47
  end
@@ -65,6 +69,333 @@ module VMCExtensions
65
69
  return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
66
70
  end
67
71
 
72
+ # general-purpose interaction
73
+ #
74
+ # `question' is the prompt (without ": " at the end)
75
+ # `options' is a hash containing:
76
+ # :input - the input source (defaults to STDIN)
77
+ # :default - the default value, also used to attempt type conversion
78
+ # of the answer (e.g. numeric/boolean)
79
+ # :choices - a list of strings to choose from
80
+ # :indexed - whether to allow choosing from `:choices' by their index,
81
+ # best for when there are many choices
82
+ # :echo - a string to echo when showing the input;
83
+ # used for things like censoring password inpt
84
+ # :callback - a block used to override certain actions
85
+ #
86
+ # takes 4 arguments:
87
+ # action: the event, e.g. :up or [:key, X] where X is a
88
+ # string containing a single character
89
+ # answer: the current answer to the question; you'll
90
+ # probably mutate this
91
+ # position: the current offset from the start of the
92
+ # answer string, e.g. when typing in the middle
93
+ # of the input, this will be where you insert
94
+ # characters
95
+ # echo: the :echo option above, may be nil
96
+ #
97
+ # the block should return the updated `position', or nil if
98
+ # it didn't handle the event
99
+ def ask(question, options = {})
100
+ default = options[:default]
101
+ choices = options[:choices]
102
+ indexed = options[:indexed]
103
+ callback = options[:callback]
104
+ input = options[:input] || STDIN
105
+ echo = options[:echo]
106
+
107
+ if choices
108
+ VMCExtensions.ask_choices(input, question, default, choices, indexed, echo, &callback)
109
+ else
110
+ VMCExtensions.ask_default(input, question, default, echo, &callback)
111
+ end
112
+ end
113
+
114
+ ESCAPES = {
115
+ "[A" => :up, "H" => :up,
116
+ "[B" => :down, "P" => :down,
117
+ "[C" => :right, "M" => :right,
118
+ "[D" => :left, "K" => :left,
119
+ "[3~" => :delete, "S" => :delete,
120
+ "[H" => :home, "G" => :home,
121
+ "[F" => :end, "O" => :end
122
+ }
123
+
124
+ def handle_action(which, ans, pos, echo = nil)
125
+ if block_given?
126
+ res = yield which, ans, pos, echo
127
+ return res unless res.nil?
128
+ end
129
+
130
+ case which
131
+ when :up
132
+ # nothing
133
+
134
+ when :down
135
+ # nothing
136
+
137
+ when :tab
138
+ # nothing
139
+
140
+ when :right
141
+ unless pos == ans.size
142
+ display censor(ans[pos .. pos], echo), false
143
+ return pos + 1
144
+ end
145
+
146
+ when :left
147
+ unless pos == 0
148
+ display "\b", false
149
+ return pos - 1
150
+ end
151
+
152
+ when :delete
153
+ unless pos == ans.size
154
+ ans.slice!(pos, 1)
155
+ if WINDOWS
156
+ rest = ans[pos .. -1]
157
+ display(censor(rest, echo) + " \b" + ("\b" * rest.size), false)
158
+ else
159
+ display("\e[P", false)
160
+ end
161
+ end
162
+
163
+ when :home
164
+ display("\b" * pos, false)
165
+ return 0
166
+
167
+ when :end
168
+ display(censor(ans[pos .. -1], echo), false)
169
+ return ans.size
170
+
171
+ when :backspace
172
+ if pos > 0
173
+ ans.slice!(pos - 1, 1)
174
+
175
+ if WINDOWS
176
+ rest = ans[pos - 1 .. -1]
177
+ display("\b" + censor(rest, echo) + " \b" + ("\b" * rest.size), false)
178
+ else
179
+ display("\b\e[P", false)
180
+ end
181
+
182
+ return pos - 1
183
+ end
184
+
185
+ when :interrupt
186
+ raise Interrupt.new
187
+
188
+ when :eof
189
+ return false if ans.empty?
190
+
191
+ when :kill_word
192
+ if pos > 0
193
+ start = /[^\s]*\s*$/ =~ ans[0 .. pos]
194
+ length = pos - start
195
+ ans.slice!(start, length)
196
+ display("\b" * length + " " * length + "\b" * length, false)
197
+ return start
198
+ end
199
+
200
+ when Array
201
+ case which[0]
202
+ when :key
203
+ c = which[1]
204
+ rest = ans[pos .. -1]
205
+
206
+ ans.insert(pos, c)
207
+
208
+ display(censor(c + rest, echo) + ("\b" * rest.size), false)
209
+
210
+ return pos + 1
211
+ end
212
+ end
213
+
214
+ pos
215
+ end
216
+
217
+ def censor(str, with)
218
+ return str unless with
219
+ with * str.size
220
+ end
221
+
222
+ # ask a simple question, maybe with a default answer
223
+ #
224
+ # reads character-by-character, handling backspaces, and sending each
225
+ # character to a block if provided
226
+ def self.ask_default(input, question, default = nil, echo = nil, &callback)
227
+ while true
228
+ prompt(question, default)
229
+
230
+ ans = ""
231
+ pos = 0
232
+ escaped = false
233
+ escape_seq = ""
234
+
235
+ with_char_io(input) do
236
+ until pos == false or (c = get_character(input)) =~ /[\r\n]/
237
+ if c == "\e" || c == "\xE0"
238
+ escaped = true
239
+ elsif escaped
240
+ escape_seq << c
241
+
242
+ if cmd = ESCAPES[escape_seq]
243
+ pos = handle_action(cmd, ans, pos, echo, &callback)
244
+ escaped, escape_seq = false, ""
245
+ elsif ESCAPES.select { |k, v| k.start_with? escape_seq }.empty?
246
+ escaped, escape_seq = false, ""
247
+ end
248
+ elsif c == "\177" or c == "\b" # backspace
249
+ pos = handle_action(:backspace, ans, pos, echo, &callback)
250
+ elsif c == "\x01"
251
+ pos = handle_action(:home, ans, pos, echo, &callback)
252
+ elsif c == "\x03"
253
+ pos = handle_action(:interrupt, ans, pos, echo, &callback)
254
+ elsif c == "\x04"
255
+ pos = handle_action(:eof, ans, pos, echo, &callback)
256
+ elsif c == "\x05"
257
+ pos = handle_action(:end, ans, pos, echo, &callback)
258
+ elsif c == "\x17"
259
+ pos = handle_action(:kill_word, ans, pos, echo, &callback)
260
+ elsif c == "\t"
261
+ pos = handle_action(:tab, ans, pos, echo, &callback)
262
+ elsif c < " "
263
+ # ignore
264
+ else
265
+ pos = handle_action([:key, c], ans, pos, echo, &callback)
266
+ end
267
+ end
268
+ end
269
+
270
+ display "\n", false
271
+
272
+ if ans.empty?
273
+ return default unless default.nil?
274
+ else
275
+ return match_type(ans, default)
276
+ end
277
+ end
278
+ end
279
+
280
+ def self.ask_choices(input, question, default, choices, indexed = false, echo = nil, &callback)
281
+ msg = question.dup
282
+
283
+ if indexed
284
+ choices.each.with_index do |o, i|
285
+ say "#{i + 1}: #{o}"
286
+ end
287
+ else
288
+ msg << " (#{choices.collect(&:inspect).join ", "})"
289
+ end
290
+
291
+ while true
292
+ ans = ask_default(input, msg, default, echo, &callback)
293
+
294
+ matches = choices.select { |x| x.start_with? ans }
295
+
296
+ if matches.size == 1
297
+ return matches.first
298
+ elsif indexed and ans =~ /^\d+$/ and res = choices.to_a[ans.to_i - 1]
299
+ return res
300
+ elsif matches.size > 1
301
+ warn "Please disambiguate: #{matches.join " or "}?"
302
+ else
303
+ warn "Unknown answer, please try again!"
304
+ end
305
+ end
306
+ end
307
+
308
+ # display a question and show the default value
309
+ def self.prompt(question, default = nil)
310
+ msg = question.dup
311
+
312
+ case default
313
+ when true
314
+ msg << " [Yn]"
315
+ when false
316
+ msg << " [yN]"
317
+ else
318
+ msg << " [#{default.inspect}]" if default
319
+ end
320
+
321
+ display "#{msg}: ", false
322
+ end
323
+
324
+ # try to make `str' be the same class as `x'
325
+ def self.match_type(str, x)
326
+ case x
327
+ when Integer
328
+ str.to_i
329
+ when true, false
330
+ str.upcase.start_with? "Y"
331
+ else
332
+ str
333
+ end
334
+ end
335
+
336
+ # definitions for reading character-by-character
337
+ begin
338
+ require "Win32API"
339
+
340
+ def self.with_char_io(input)
341
+ yield
342
+ end
343
+
344
+ def self.get_character(input)
345
+ if input == STDIN
346
+ begin
347
+ Win32API.new("msvcrt", "_getch", [], "L").call.chr
348
+ rescue
349
+ Win32API.new("crtdll", "_getch", [], "L").call.chr
350
+ end
351
+ else
352
+ input.getc.chr
353
+ end
354
+ end
355
+ rescue LoadError
356
+ begin
357
+ require "termios"
358
+
359
+ def self.with_char_io(input)
360
+ return yield unless input.tty?
361
+
362
+ before = Termios.getattr(input)
363
+
364
+ new = before.dup
365
+ new.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
366
+ new.c_cc[Termios::VMIN] = 1
367
+
368
+ begin
369
+ Termios.setattr(input, Termios::TCSANOW, new)
370
+ yield
371
+ ensure
372
+ Termios.setattr(input, Termios::TCSANOW, before)
373
+ end
374
+ end
375
+
376
+ def self.get_character(input)
377
+ input.getc.chr
378
+ end
379
+ rescue LoadError
380
+ # set tty modes for the duration of a block, restoring them afterward
381
+ def self.with_char_io(input)
382
+ return yield unless input.tty?
383
+
384
+ begin
385
+ before = `stty -g`
386
+ system("stty raw -echo -icanon isig")
387
+ yield
388
+ ensure
389
+ system("stty #{before}")
390
+ end
391
+ end
392
+
393
+ # this assumes we're wrapped in #with_stty
394
+ def self.get_character(input)
395
+ input.getc.chr
396
+ end
397
+ end
398
+ end
68
399
  end
69
400
 
70
401
  module VMCStringExtensions