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

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.
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