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.
- data/Rakefile +84 -2
- data/lib/cli.rb +2 -3
- data/lib/cli/commands/admin.rb +9 -6
- data/lib/cli/commands/apps.rb +144 -111
- data/lib/cli/commands/base.rb +8 -15
- data/lib/cli/commands/misc.rb +3 -2
- data/lib/cli/commands/services.rb +15 -16
- data/lib/cli/commands/user.rb +11 -2
- data/lib/cli/config.rb +5 -1
- data/lib/cli/core_ext.rb +331 -0
- data/lib/cli/frameworks.rb +10 -4
- data/lib/cli/runner.rb +4 -0
- data/lib/cli/services_helper.rb +8 -2
- data/lib/cli/usage.rb +4 -3
- data/lib/cli/version.rb +1 -1
- data/lib/vmc/client.rb +4 -0
- data/lib/vmc/const.rb +1 -0
- metadata +13 -46
- data/spec/assets/app_info.txt +0 -9
- data/spec/assets/app_listings.txt +0 -9
- data/spec/assets/bad_create_app.txt +0 -9
- data/spec/assets/delete_app.txt +0 -9
- data/spec/assets/global_service_listings.txt +0 -9
- data/spec/assets/good_create_app.txt +0 -9
- data/spec/assets/good_create_service.txt +0 -9
- data/spec/assets/info_authenticated.txt +0 -27
- data/spec/assets/info_return.txt +0 -15
- data/spec/assets/info_return_bad.txt +0 -16
- data/spec/assets/list_users.txt +0 -13
- data/spec/assets/login_fail.txt +0 -9
- data/spec/assets/login_success.txt +0 -9
- data/spec/assets/sample_token.txt +0 -1
- data/spec/assets/service_already_exists.txt +0 -9
- data/spec/assets/service_gateway_fail.txt +0 -9
- data/spec/assets/service_listings.txt +0 -9
- data/spec/assets/service_not_found.txt +0 -9
- data/spec/assets/user_info.txt +0 -9
- data/spec/spec_helper.rb +0 -11
- data/spec/unit/cli_opts_spec.rb +0 -68
- data/spec/unit/client_spec.rb +0 -345
data/lib/cli/commands/base.rb
CHANGED
@@ -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
|
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
|
-
|
40
|
-
@client_info = client.info
|
32
|
+
@client_info ||= client.info
|
41
33
|
end
|
42
34
|
|
43
35
|
def target_url
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
data/lib/cli/commands/misc.rb
CHANGED
@@ -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
|
35
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
data/lib/cli/commands/user.rb
CHANGED
@@ -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
|
43
|
-
password2 = ask
|
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
|
data/lib/cli/config.rb
CHANGED
@@ -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)
|
data/lib/cli/core_ext.rb
CHANGED
@@ -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
|