duostack 0.1.0 → 0.2.0
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 +1 -0
- data/bin/duostack +244 -135
- metadata +5 -5
data/Rakefile
CHANGED
|
@@ -8,6 +8,7 @@ Jeweler::Tasks.new do |gem|
|
|
|
8
8
|
gem.version = `bin/duostack version`.chomp
|
|
9
9
|
gem.summary = %Q{Duostack command line client}
|
|
10
10
|
gem.description = %Q{Duostack command line client: create and manage Duostack apps}
|
|
11
|
+
gem.homepage = "http://www.duostack.com/"
|
|
11
12
|
gem.email = "todd@toddeichel.com"
|
|
12
13
|
gem.authors = "Todd Eichel"
|
|
13
14
|
gem.require_paths = ['.'] # default is ["lib"] but we don't have that
|
data/bin/duostack
CHANGED
|
@@ -28,12 +28,16 @@ $client = File.basename(__FILE__)
|
|
|
28
28
|
module Duostack
|
|
29
29
|
class Client
|
|
30
30
|
|
|
31
|
-
VERSION = '0.
|
|
31
|
+
VERSION = '0.2.0'
|
|
32
32
|
DEPENDENCIES_LAST_MODIFIED = 1297154481
|
|
33
|
-
DEFAULT_CREDENTIALS_LOCATION = '~/.duostack'
|
|
34
33
|
USER_AGENT = "duostack-#{VERSION}"
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
DEFAULTS = {
|
|
36
|
+
:credentials_location => '~/.duostack',
|
|
37
|
+
:remote_name => 'duostack'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
FLAGS = [ # app and remote get special handling
|
|
37
41
|
'confirm',
|
|
38
42
|
'long',
|
|
39
43
|
'skip-dependency-checks'
|
|
@@ -56,7 +60,8 @@ module Duostack
|
|
|
56
60
|
'rake',
|
|
57
61
|
'config',
|
|
58
62
|
'env',
|
|
59
|
-
'access'
|
|
63
|
+
'access',
|
|
64
|
+
'domains'
|
|
60
65
|
],
|
|
61
66
|
:compound => [ # mult-part commands that expect subsequent arguments, must validate extra args on their own
|
|
62
67
|
'help',
|
|
@@ -64,7 +69,8 @@ module Duostack
|
|
|
64
69
|
'rake',
|
|
65
70
|
'config',
|
|
66
71
|
'env',
|
|
67
|
-
'access'
|
|
72
|
+
'access',
|
|
73
|
+
'domains'
|
|
68
74
|
]
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -74,8 +80,10 @@ module Duostack
|
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def run
|
|
77
|
-
@creds_file = get_flag_arg('creds')
|
|
78
|
-
@
|
|
83
|
+
@creds_file = get_flag_arg('creds') || DEFAULTS[:credentials_location]
|
|
84
|
+
@remote = get_flag_arg('remote') || DEFAULTS[:remote_name]
|
|
85
|
+
# get app from args if we can, otherwise it will be extracted from git remotes during require_app
|
|
86
|
+
@app_name = get_flag_arg('app')
|
|
79
87
|
@flags = parse_flags
|
|
80
88
|
@command = @args.shift || COMMANDS[:default]
|
|
81
89
|
|
|
@@ -179,12 +187,6 @@ module Duostack
|
|
|
179
187
|
# curl SSL
|
|
180
188
|
# handled inside api_host method
|
|
181
189
|
|
|
182
|
-
# expect (only if running console)
|
|
183
|
-
# TODO: just use ruby expect lib
|
|
184
|
-
if @command == 'console' && `which expect`.empty?
|
|
185
|
-
exit_with "missing dependency, please install Expect"
|
|
186
|
-
end
|
|
187
|
-
|
|
188
190
|
# touch .duostack so we know when deps were last checked (but don't create it if it doesn't exist)
|
|
189
191
|
require 'fileutils'
|
|
190
192
|
FileUtils.touch(filename) if File.exist?(filename)
|
|
@@ -194,7 +196,7 @@ module Duostack
|
|
|
194
196
|
# ensures the app can be identified from inspecting the git repo or command line args
|
|
195
197
|
def require_app
|
|
196
198
|
require_user # all app commands require a user
|
|
197
|
-
@app_name ||=
|
|
199
|
+
@app_name ||= app_name_from_git_remotes
|
|
198
200
|
unless @app_name
|
|
199
201
|
exit_with "run this command from a Duostack app folder or pass an app name with the --app argument"
|
|
200
202
|
end
|
|
@@ -209,7 +211,7 @@ module Duostack
|
|
|
209
211
|
ssh_key = require_ssh_key # all user commands require an SSH key
|
|
210
212
|
|
|
211
213
|
puts "First-time Duostack client setup"
|
|
212
|
-
print "Email
|
|
214
|
+
print "Email: "
|
|
213
215
|
username = $stdin.gets.chomp
|
|
214
216
|
password = `bash -c 'read -sp "Password: " passwd; echo $passwd'`.chomp
|
|
215
217
|
puts '' # clears the line after
|
|
@@ -245,7 +247,7 @@ module Duostack
|
|
|
245
247
|
# NOTE: @confirmed is set above in 'run' so we can be assured the app_name came from the --app flag
|
|
246
248
|
# if assessed confirmation here, the app could have been extracted from the git repo
|
|
247
249
|
unless @confirmed
|
|
248
|
-
exit_with "command requires confirmation, run again with '--confirm --app
|
|
250
|
+
exit_with "command requires confirmation, run again with '--confirm --app #{@app_name}'"
|
|
249
251
|
end
|
|
250
252
|
end
|
|
251
253
|
|
|
@@ -253,9 +255,103 @@ module Duostack
|
|
|
253
255
|
# utility methods
|
|
254
256
|
#########################################################################
|
|
255
257
|
|
|
256
|
-
def
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
def process_crud_command(opts)
|
|
259
|
+
|
|
260
|
+
# get action
|
|
261
|
+
action = @args.shift || 'list' # list is the default
|
|
262
|
+
action = 'list' if action == 'ls' # normalize shorthand for list
|
|
263
|
+
action = 'remove' if action == 'rm' # normalize shorthand for remove
|
|
264
|
+
|
|
265
|
+
# extract important things from opts, otherwise merge defaults
|
|
266
|
+
resource = opts[:resource]
|
|
267
|
+
resources = "#{resource}s" # may eventually need a better pluralizer
|
|
268
|
+
|
|
269
|
+
# default settings for opts. priority is:
|
|
270
|
+
# 1. hash under each action (e.g. opts[:actions][:list][:param])
|
|
271
|
+
# 2. general opts (e.g. opts[:param])
|
|
272
|
+
# 3. generic defaults specified here (e.g. `resources`, the pluralized resource name)
|
|
273
|
+
defaults = {
|
|
274
|
+
:error_message => opts[:error_message] || "<#{resource}>",
|
|
275
|
+
:param => opts[:param] || resources,
|
|
276
|
+
:additional_params => opts[:additonal_params] || nil,
|
|
277
|
+
:args_processor => opts[:args_processor] || lambda { |args| args }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# actions can be either a hash (containing action-specific options) or an array (accept the defaults)
|
|
281
|
+
if opts[:actions].respond_to?(:keys)
|
|
282
|
+
valid_actions = opts[:actions].keys.map { |x| x.to_s }
|
|
283
|
+
opts = defaults.merge(opts[:actions][action.to_sym])
|
|
284
|
+
else
|
|
285
|
+
valid_actions = opts[:actions]
|
|
286
|
+
opts = defaults.merge(opts)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ensure action is valid
|
|
291
|
+
unless valid_actions.include?(action)
|
|
292
|
+
exit_with "invalid argument for '#{@command}', try #{sentencize(valid_actions)}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# ensure we have an argument for add/remove actions which require it
|
|
296
|
+
if %w(add remove).include?(action)
|
|
297
|
+
# gather up and compose remaining args for add/remove operations
|
|
298
|
+
argument = opts[:args_processor].call(@args).join(' ')
|
|
299
|
+
@args.clear # clean up, since we processed every remaining arg
|
|
300
|
+
|
|
301
|
+
# warn and exit unless we have an argument to pass
|
|
302
|
+
if argument.empty?
|
|
303
|
+
exit_with "'#{@command} #{action}' requires an argument, try '#{@command} #{action} #{opts[:error_message]}'"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# clean up argument
|
|
307
|
+
argument = CGI::escape(argument)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# finally, process action
|
|
311
|
+
case action
|
|
312
|
+
when 'list'
|
|
313
|
+
print api_get("list_#{resources}", opts[:additional_params])
|
|
314
|
+
when 'add'
|
|
315
|
+
puts api_get("add_#{resource}", ["#{opts[:param]}=#{argument}", opts[:additional_params]].compact.join('&'))
|
|
316
|
+
when 'remove'
|
|
317
|
+
puts api_get("remove_#{resource}", ["#{opts[:param]}=#{argument}", opts[:additional_params]].compact.join('&'))
|
|
318
|
+
when 'clear'
|
|
319
|
+
require_confirmation
|
|
320
|
+
puts api_get("clear_#{resources}", opts[:additional_params])
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# attempts to get the app name out of git remotes
|
|
326
|
+
# if --remote is specified, it will use that assuming it's valid
|
|
327
|
+
# if not, it will look for any remote name that references duostack.net
|
|
328
|
+
def app_name_from_git_remotes
|
|
329
|
+
remotes = `git remote -v 2>/dev/null`.split("\n")
|
|
330
|
+
remotes.reject! { |line| line.split.last == '(fetch)' } # filter out fetch remotes (careful, does not exist in older git versions)
|
|
331
|
+
|
|
332
|
+
# find url of remote of specified name
|
|
333
|
+
valid_remotes = remotes.reject do |line|
|
|
334
|
+
line.split.first != @remote
|
|
335
|
+
end
|
|
336
|
+
remote_url = valid_remotes.first.split[1] unless valid_remotes.empty?
|
|
337
|
+
|
|
338
|
+
# correct url will be in the form "git@duostack.net:appname.git"
|
|
339
|
+
# if remote of specified name is a duostack remote, set app_name
|
|
340
|
+
if !remote_url.to_s.empty? && remote_url[0..16] == "git@duostack.net:" && remote_url[-4..-1] == ".git"
|
|
341
|
+
app_name = remote_url[17..-5]
|
|
342
|
+
else # the specified remote isn't for duostack; look for another (take first match)
|
|
343
|
+
remotes.each do |line|
|
|
344
|
+
result = line.scan(/git@duostack\.net:(\w+)\.git/)
|
|
345
|
+
if result.length > 0 && result[0].length > 0
|
|
346
|
+
app_name = result[0][0]
|
|
347
|
+
remote_name = line.split.first
|
|
348
|
+
warn_with "remote '#{@remote}' does not refer to Duostack, using remote '#{remote_name}' instead"
|
|
349
|
+
break
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
return app_name ||= nil
|
|
259
355
|
end
|
|
260
356
|
|
|
261
357
|
def ssh_key_location
|
|
@@ -324,6 +420,18 @@ module Duostack
|
|
|
324
420
|
end
|
|
325
421
|
end
|
|
326
422
|
|
|
423
|
+
def sentencize(array, conjunction='or')
|
|
424
|
+
# http://stackoverflow.com/questions/2038787/join-array-contents-into-an-english-list
|
|
425
|
+
case array.length
|
|
426
|
+
when 0, 1
|
|
427
|
+
array.first.to_s
|
|
428
|
+
when 2
|
|
429
|
+
"#{array.first} #{conjunction} #{array.last}"
|
|
430
|
+
else
|
|
431
|
+
"#{array[0..-2].join(', ')}, #{conjunction} #{array.last}"
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
327
435
|
def debug(message)
|
|
328
436
|
puts message if ENV['DSDEBUG']
|
|
329
437
|
end
|
|
@@ -374,16 +482,15 @@ module Duostack
|
|
|
374
482
|
exit_with "current directory is not a Git repository, run 'git init' first"
|
|
375
483
|
end
|
|
376
484
|
|
|
377
|
-
#
|
|
378
|
-
if
|
|
379
|
-
exit_with "
|
|
485
|
+
# make sure we're not going to step on an existing git remote
|
|
486
|
+
if `git remote`.chomp.split("\n").include?(@remote)
|
|
487
|
+
exit_with "there is already a Git remote named '#{@remote}', please remove it or pass a \ndifferent name with the --remote argument"
|
|
380
488
|
end
|
|
381
489
|
|
|
382
|
-
# create
|
|
383
|
-
# TODO: ensure there is not already a remote named duostack
|
|
490
|
+
# create, add remote
|
|
384
491
|
puts api_get('create_app', "app_name=#{name}")
|
|
385
|
-
`git remote add
|
|
386
|
-
puts "Git remote added, to push: 'git push
|
|
492
|
+
`git remote add #{@remote} git@duostack.net:#{name}.git 2>/dev/null`
|
|
493
|
+
puts "Git remote added, to push: 'git push #{@remote} master'"
|
|
387
494
|
end
|
|
388
495
|
|
|
389
496
|
|
|
@@ -420,6 +527,13 @@ module Duostack
|
|
|
420
527
|
|
|
421
528
|
|
|
422
529
|
def console
|
|
530
|
+
# TODO: just use ruby expect lib
|
|
531
|
+
|
|
532
|
+
# first check for expect dependency
|
|
533
|
+
if `which expect`.empty?
|
|
534
|
+
exit_with "missing dependency, please install Expect (http://expect.sourceforge.net/)"
|
|
535
|
+
end
|
|
536
|
+
|
|
423
537
|
exec("#{$dir}/.duostack-console-expect #{@app_name}")
|
|
424
538
|
end
|
|
425
539
|
|
|
@@ -454,104 +568,59 @@ module Duostack
|
|
|
454
568
|
|
|
455
569
|
|
|
456
570
|
def env
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
exit_with "'env add' requires an argument, try 'env add <name>=<value>'"
|
|
486
|
-
when 'remove', 'rm'
|
|
487
|
-
exit_with "'env #{command}' requires an argument, try 'env #{command} <name>'"
|
|
488
|
-
end
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
# clean up argument
|
|
492
|
-
argument = CGI::escape(argument)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
# finally, process command
|
|
496
|
-
case command
|
|
497
|
-
when 'list', 'ls'
|
|
498
|
-
truncate = !@flags.include?('long')
|
|
499
|
-
print api_get("list_envs", "truncate=#{truncate}")
|
|
500
|
-
when 'add'
|
|
501
|
-
puts api_get("add_env", "input=#{argument}")
|
|
502
|
-
when 'remove', 'rm'
|
|
503
|
-
puts api_get("remove_env", "name=#{argument}")
|
|
504
|
-
when 'clear'
|
|
505
|
-
require_confirmation
|
|
506
|
-
puts api_get("clear_envs")
|
|
507
|
-
end
|
|
571
|
+
process_crud_command({
|
|
572
|
+
:resource => 'env',
|
|
573
|
+
:actions => {
|
|
574
|
+
:list => {
|
|
575
|
+
:additional_params => "truncate=#{!@flags.include?('long')}"
|
|
576
|
+
},
|
|
577
|
+
:add => {
|
|
578
|
+
:error_message => '<name>=<value>',
|
|
579
|
+
:param => 'input',
|
|
580
|
+
:args_processor => lambda { |args|
|
|
581
|
+
# recompose strings and quote values because ruby strips out quotation marks in the args.
|
|
582
|
+
args.collect do |arg|
|
|
583
|
+
if arg.include?('=')
|
|
584
|
+
result = arg.split('=',2)
|
|
585
|
+
%Q(#{result[0]}="#{result[1].gsub('"', '\"')}")
|
|
586
|
+
else
|
|
587
|
+
arg
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
:remove => {
|
|
593
|
+
:error_message => '<name>',
|
|
594
|
+
:param => 'name'
|
|
595
|
+
},
|
|
596
|
+
:clear => {}
|
|
597
|
+
}
|
|
598
|
+
})
|
|
508
599
|
end
|
|
509
600
|
|
|
510
601
|
|
|
511
602
|
def access
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
unless %w(add list ls).include?(command)
|
|
519
|
-
exit_with "invalid argument for 'access', try list or add"
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# ensure we have an argument for add/remove commands which require it
|
|
523
|
-
if %w(add).include?(command)
|
|
524
|
-
# gather up and compose remaining args for add/remove operations
|
|
525
|
-
argument = @args.join(' ')
|
|
526
|
-
@args.clear # clean up, since we processed every remaining arg
|
|
527
|
-
|
|
528
|
-
# warn and exit unless we have an argument to pass
|
|
529
|
-
if argument.empty?
|
|
530
|
-
case command
|
|
531
|
-
when 'add'
|
|
532
|
-
exit_with "'access add' requires an argument, try 'access add <email>'"
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
# clean up argument
|
|
537
|
-
argument = CGI::escape(argument)
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
# finally, process command
|
|
541
|
-
case command
|
|
542
|
-
when 'list', 'ls'
|
|
543
|
-
print api_get("list_collabs")
|
|
544
|
-
when 'add'
|
|
545
|
-
puts api_get("add_collab", "emails=#{argument}")
|
|
546
|
-
end
|
|
603
|
+
process_crud_command({
|
|
604
|
+
:resource => 'collab',
|
|
605
|
+
:param => 'emails',
|
|
606
|
+
:error_message => '<email>',
|
|
607
|
+
:actions => %w(list add)
|
|
608
|
+
})
|
|
547
609
|
end
|
|
548
610
|
|
|
549
611
|
|
|
612
|
+
def domains
|
|
613
|
+
process_crud_command({
|
|
614
|
+
:resource => 'domain',
|
|
615
|
+
:actions => %w(list add remove)
|
|
616
|
+
})
|
|
617
|
+
end
|
|
618
|
+
|
|
550
619
|
|
|
551
620
|
module Help
|
|
552
621
|
|
|
553
|
-
# available help sections. first will be used as the default.
|
|
554
|
-
SECTIONS =
|
|
622
|
+
# available help sections. first will be used as the default, so unshift 'help' into first position.
|
|
623
|
+
SECTIONS = (COMMANDS[:general] + COMMANDS[:user] + COMMANDS[:app]).unshift('help')
|
|
555
624
|
|
|
556
625
|
class << self
|
|
557
626
|
|
|
@@ -569,33 +638,39 @@ module Duostack
|
|
|
569
638
|
def help
|
|
570
639
|
<<-EOF
|
|
571
640
|
|
|
572
|
-
Usage: #{$client} <command> [<args>] [--app <appname>]
|
|
641
|
+
Usage: #{$client} <command> [<args>] [--app <appname>] [--remote <remotename>]
|
|
573
642
|
|
|
574
643
|
App commands must be either run from an app folder (a Git repository with a
|
|
575
644
|
Duostack remote) or have the app specified with the "--app <appname>" flag. If
|
|
576
645
|
both are present, the "--app" flag takes precedence.
|
|
577
646
|
|
|
578
|
-
|
|
579
|
-
|
|
647
|
+
You can also use the "--remote" flag to set the app based on a specific Git
|
|
648
|
+
remote name. This is common when you run two Duostack Apps (e.g. staging and
|
|
649
|
+
production) from the same folder. This flag can also be used when running
|
|
650
|
+
"create" to have the client set up a custom remote name (default is "duostack").
|
|
651
|
+
|
|
652
|
+
The most common commands are listed below. For additional information on any of
|
|
653
|
+
them, run: #{$client} help <command>
|
|
580
654
|
|
|
581
655
|
General Commands:
|
|
582
|
-
help [<command>]
|
|
583
|
-
create <appname>
|
|
584
|
-
list
|
|
585
|
-
version
|
|
656
|
+
help [<command>] Show this help, or detailed help on <command>
|
|
657
|
+
create <appname> Initialize a Git repository as a Duostack App
|
|
658
|
+
list Show all apps associated with your account
|
|
659
|
+
version Show version of this Duostack client
|
|
586
660
|
|
|
587
661
|
App Commands:
|
|
588
|
-
logs
|
|
589
|
-
restart
|
|
590
|
-
ps
|
|
591
|
-
destroy
|
|
592
|
-
config [<name> [<setting>]]
|
|
593
|
-
env [<operation>]
|
|
594
|
-
access [<operation>]
|
|
662
|
+
logs Retrieve server logs
|
|
663
|
+
restart Restart instances
|
|
664
|
+
ps List instances with current status
|
|
665
|
+
destroy Destroy Duostack App and associated data
|
|
666
|
+
config [<name> [<setting>]] Show or set configuration options
|
|
667
|
+
env [<operation>] Manage environment variables
|
|
668
|
+
access [<operation>] Manage app collaborator access
|
|
669
|
+
domains [<operation>] Manage custom domains
|
|
595
670
|
|
|
596
671
|
App Commands - Ruby:
|
|
597
|
-
console
|
|
598
|
-
rake [<command>]
|
|
672
|
+
console Connect to IRB/Rails console
|
|
673
|
+
rake [<command>] Run a Rake command
|
|
599
674
|
|
|
600
675
|
EOF
|
|
601
676
|
end
|
|
@@ -603,18 +678,25 @@ module Duostack
|
|
|
603
678
|
def create
|
|
604
679
|
<<-EOF
|
|
605
680
|
|
|
606
|
-
Usage:
|
|
607
|
-
|
|
681
|
+
Usage:
|
|
682
|
+
#{$client} create <appname>
|
|
683
|
+
#{$client} create <appname> --remote <remotename>
|
|
684
|
+
|
|
685
|
+
Example:
|
|
686
|
+
#{$client} create myappname
|
|
687
|
+
#{$client} create myappname --remote staging
|
|
608
688
|
|
|
609
689
|
Arguments:
|
|
610
690
|
<appname> A name for your app (restrictions apply, see below)
|
|
691
|
+
<remotename> An optional name for the created Git remote
|
|
611
692
|
|
|
612
693
|
Creates a new Duostack App from the current directory with the given name.
|
|
613
694
|
|
|
614
695
|
The current directory must be initialized as a Git repository (run 'git init').
|
|
615
696
|
Upon running 'create', the app will be created on Duostack and a Git remote with
|
|
616
697
|
the name 'duostack' will be added to the local repository, which you can push to
|
|
617
|
-
to deploy your app: git push duostack master.
|
|
698
|
+
to deploy your app: git push duostack master. If a custom remote name is
|
|
699
|
+
specified with the '--remote' flag, that will be used instead.
|
|
618
700
|
|
|
619
701
|
The app name must be 4-16 characters long, alphanumeric, and must not start with
|
|
620
702
|
a number. The use of hyphens, underscores, or any other special characters is
|
|
@@ -767,8 +849,7 @@ module Duostack
|
|
|
767
849
|
#{$client} access lists app collaborators
|
|
768
850
|
#{$client} access add name@example.com grants access for name@example.com
|
|
769
851
|
|
|
770
|
-
Lists and adds app collaborator access for users (identified by
|
|
771
|
-
emails).
|
|
852
|
+
Lists and adds app collaborator access for users (identified by emails).
|
|
772
853
|
|
|
773
854
|
List: Lists collaborator emails who currently have access to the app. If none
|
|
774
855
|
are set, output will be blank.
|
|
@@ -779,6 +860,34 @@ module Duostack
|
|
|
779
860
|
EOF
|
|
780
861
|
end
|
|
781
862
|
|
|
863
|
+
def domains
|
|
864
|
+
<<-EOF
|
|
865
|
+
|
|
866
|
+
Usage:
|
|
867
|
+
#{$client} domains lists all custom domains
|
|
868
|
+
#{$client} domains add <domain> adds <domain> as a custom domain
|
|
869
|
+
#{$client} domains remove <domain> removes custom domain <domain>
|
|
870
|
+
|
|
871
|
+
Examples:
|
|
872
|
+
#{$client} domains lists all env vars
|
|
873
|
+
#{$client} domains add app.example.com adds app.example.com to app
|
|
874
|
+
#{$client} domains remove app.example.com removes app.example.com from app
|
|
875
|
+
|
|
876
|
+
Lists, adds, and removes custom domains
|
|
877
|
+
(http://docs.duostack.com/custom-domains).
|
|
878
|
+
|
|
879
|
+
List: Lists custom domains currently set on the app. If none are set, output
|
|
880
|
+
will be blank.
|
|
881
|
+
|
|
882
|
+
Add: Add one or more custom domains to the app. Add multiple by supplying
|
|
883
|
+
additional <domain> arguments separated by spaces.
|
|
884
|
+
|
|
885
|
+
Remove: Removes one or more custom domains from the app. Remove multiple by
|
|
886
|
+
supplying additional <domain> arguments separated by spaces.
|
|
887
|
+
|
|
888
|
+
EOF
|
|
889
|
+
end
|
|
890
|
+
|
|
782
891
|
def console
|
|
783
892
|
<<-EOF
|
|
784
893
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: duostack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
8
|
+
- 2
|
|
9
9
|
- 0
|
|
10
|
-
version: 0.
|
|
10
|
+
version: 0.2.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Todd Eichel
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-02-
|
|
18
|
+
date: 2011-02-17 00:00:00 -05:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies: []
|
|
21
21
|
|
|
@@ -34,7 +34,7 @@ files:
|
|
|
34
34
|
- bin/bash/.duostack-console-expect
|
|
35
35
|
- bin/duostack
|
|
36
36
|
has_rdoc: true
|
|
37
|
-
homepage:
|
|
37
|
+
homepage: http://www.duostack.com/
|
|
38
38
|
licenses: []
|
|
39
39
|
|
|
40
40
|
post_install_message:
|