elasticdot 1.3.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/bin/elasticdot +5 -0
- data/lib/elasticdot.rb +3 -0
- data/lib/elasticdot/api.rb +60 -0
- data/lib/elasticdot/cli.rb +51 -0
- data/lib/elasticdot/command.rb +67 -0
- data/lib/elasticdot/command/addons.rb +29 -0
- data/lib/elasticdot/command/alias.rb +33 -0
- data/lib/elasticdot/command/apps.rb +71 -0
- data/lib/elasticdot/command/auth.rb +67 -0
- data/lib/elasticdot/command/base.rb +121 -0
- data/lib/elasticdot/command/config.rb +70 -0
- data/lib/elasticdot/command/db.rb +182 -0
- data/lib/elasticdot/command/domains.rb +55 -0
- data/lib/elasticdot/command/help.rb +106 -0
- data/lib/elasticdot/command/keys.rb +93 -0
- data/lib/elasticdot/command/logs.rb +22 -0
- data/lib/elasticdot/command/ps.rb +139 -0
- data/lib/elasticdot/command/services.rb +42 -0
- data/lib/elasticdot/initializers/string.rb +13 -0
- metadata +113 -0
data/bin/elasticdot
ADDED
data/lib/elasticdot.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
class ElasticDot::API
|
2
|
+
require 'restclient'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
attr_reader :email, :host
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@host = opts[:host] || 'https://api.elasticdot.com'
|
9
|
+
|
10
|
+
if opts[:email] and opts[:password]
|
11
|
+
@email, @pass = opts[:email], opts[:password]
|
12
|
+
else
|
13
|
+
@email, @pass = ElasticDot::Command::Auth.credentials
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def login
|
18
|
+
return unless (@email && @pass)
|
19
|
+
|
20
|
+
@pass = RestClient.post(
|
21
|
+
"#{@host}/auth",
|
22
|
+
email: @email, password: @pass
|
23
|
+
)
|
24
|
+
rescue => e
|
25
|
+
puts e.response
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(m, *args, &block)
|
30
|
+
unless ['get', 'post', 'put', 'delete'].include? m.to_s
|
31
|
+
raise NoMethodError, "undefined method: #{m}"
|
32
|
+
end
|
33
|
+
|
34
|
+
res = self.send('req', m, args)
|
35
|
+
JSON.parse res rescue ""
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def req(method, *args)
|
40
|
+
raise if args.empty?
|
41
|
+
args = args.shift
|
42
|
+
path = args.shift
|
43
|
+
|
44
|
+
resource = RestClient::Resource.new(
|
45
|
+
"#{@host}#{path}", user: @email, password: @pass
|
46
|
+
)
|
47
|
+
|
48
|
+
begin
|
49
|
+
resource.send method, (args.first || {})
|
50
|
+
rescue => e
|
51
|
+
if e.respond_to? :response
|
52
|
+
puts e.response
|
53
|
+
else
|
54
|
+
puts 'Something went wrong, we have been notified.'
|
55
|
+
end
|
56
|
+
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ElasticDot
|
2
|
+
libs = Dir[File.join(File.dirname(__FILE__), "initializers", "*.rb")]
|
3
|
+
libs.each { |file| require file }
|
4
|
+
|
5
|
+
require 'elasticdot/command'
|
6
|
+
|
7
|
+
class CLI
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
def self.parse(args)
|
11
|
+
options = {}
|
12
|
+
|
13
|
+
opt_parser = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: example.rb [options]"
|
15
|
+
|
16
|
+
opts.on("-a", "--app APP", "Specify the app involved") do |v|
|
17
|
+
options[:app] = v
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on("-d", "--database DATABASE", "Specify the database involved") do |v|
|
21
|
+
options[:db] = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on("-f", "--follow", "Continue running and print new events (off)") do |v|
|
25
|
+
options[:follow] = v
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-p", "--plan PLAN", "Specify pricing plan") do |v|
|
29
|
+
options[:plan] = v
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-c", "--extra-conf CONF", "Specify extra config file") do |v|
|
33
|
+
options[:conf] = v
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-h", "--help", "Show this help") do |v|
|
37
|
+
options[:help] = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
opt_parser.parse! args
|
42
|
+
|
43
|
+
[ARGV, options]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.run(args)
|
47
|
+
cmd, opts = parse ARGV
|
48
|
+
ElasticDot::Command.run cmd, opts
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ElasticDot::Command
|
2
|
+
require 'elasticdot/api'
|
3
|
+
require 'elasticdot/command/base'
|
4
|
+
|
5
|
+
libs = Dir[File.join(File.dirname(__FILE__), "command", "*.rb")]
|
6
|
+
libs.each { |file| require file }
|
7
|
+
|
8
|
+
def self.run(args, opts)
|
9
|
+
cmd = args.shift
|
10
|
+
|
11
|
+
unless cmd
|
12
|
+
ElasticDot::Command::Help.root_help
|
13
|
+
exit 0
|
14
|
+
end
|
15
|
+
|
16
|
+
klass, act = cmd.split ':', 2
|
17
|
+
klass = klass.downcase
|
18
|
+
|
19
|
+
if klass == 'help'
|
20
|
+
m = args.shift || 'root_help'
|
21
|
+
m = m.split(':')[0]
|
22
|
+
|
23
|
+
ElasticDot::Command::Help.send m
|
24
|
+
exit 0
|
25
|
+
end
|
26
|
+
|
27
|
+
unless (klass == 'login' or cmd == 'auth:login')
|
28
|
+
ElasticDot::Command::Auth.authenticate!
|
29
|
+
end
|
30
|
+
|
31
|
+
ElasticDot::Command::Base.api = ElasticDot::API.new
|
32
|
+
|
33
|
+
if ElasticDot::Command::Alias.respond_to? klass
|
34
|
+
ElasticDot::Command::Alias.send klass.downcase, args, opts
|
35
|
+
exit 0
|
36
|
+
end
|
37
|
+
|
38
|
+
act ||= 'list'
|
39
|
+
|
40
|
+
begin
|
41
|
+
klass = "ElasticDot::Command::#{klass.capitalize}".constantize
|
42
|
+
rescue
|
43
|
+
unless ElasticDot::Command::Base.respond_to? cmd
|
44
|
+
puts 'Invalid command: ' + cmd
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
48
|
+
return ElasticDot::Command::Base.send cmd
|
49
|
+
end
|
50
|
+
|
51
|
+
unless klass.respond_to? act
|
52
|
+
puts 'Invalid action ' + act
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
|
56
|
+
method_args = klass.method(act).arity
|
57
|
+
|
58
|
+
case method_args
|
59
|
+
when 0
|
60
|
+
klass.send act
|
61
|
+
when 1
|
62
|
+
klass.send act, opts
|
63
|
+
when 2
|
64
|
+
klass.send act, args, opts
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class ElasticDot::Command::Addons < ElasticDot::Command::Base
|
2
|
+
def self.add(addon, opts)
|
3
|
+
find_app! opts
|
4
|
+
|
5
|
+
addon, tier = addon[0].split ':', 2
|
6
|
+
|
7
|
+
puts "Configuring addon #{addon} for app #{@app}..."
|
8
|
+
|
9
|
+
api.post "/apps/#{@app}/addons/#{addon}", tier: tier
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.remove(addons, opts)
|
13
|
+
find_app! opts
|
14
|
+
|
15
|
+
addons.each do |addon|
|
16
|
+
addon = addon.split(':')[0]
|
17
|
+
|
18
|
+
puts "Removing addon #{addon} from app #{@app}..."
|
19
|
+
api.delete "/apps/#{@app}/addons/#{addon}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.list
|
24
|
+
addons = api.get '/addons'
|
25
|
+
|
26
|
+
puts '=== available'
|
27
|
+
addons.each { |a, i| puts a['name'] }
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class ElasticDot::Command::Alias < ElasticDot::Command::Base
|
2
|
+
def self.login(*opts)
|
3
|
+
ElasticDot::Command::Auth.login
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.logout(*opts)
|
7
|
+
ElasticDot::Command::Auth.logout
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.signup(*opts)
|
11
|
+
puts 'Please go to: https://s.elasticdot.com/signup'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.version(*opts)
|
15
|
+
puts 'ElasticDot CLI 1.3.3'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.ls(*opts)
|
19
|
+
ElasticDot::Command::Apps.list
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create(args, opts)
|
23
|
+
ElasticDot::Command::Apps.create args, opts
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.info(args, opts)
|
27
|
+
ElasticDot::Command::Apps.info opts
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.open(args, opts)
|
31
|
+
ElasticDot::Command::Apps.open opts
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class ElasticDot::Command::Apps < ElasticDot::Command::Base
|
2
|
+
def self.create(args, opts)
|
3
|
+
info = api.post '/domains', domain: args[0]
|
4
|
+
|
5
|
+
if info['error']
|
6
|
+
puts info['error']
|
7
|
+
exit 1
|
8
|
+
end
|
9
|
+
|
10
|
+
puts "Creating app #{info['app_name']}... done"
|
11
|
+
puts "http://#{info['app_name']}.elasticdot.io/ | #{info['app_repo']}"
|
12
|
+
|
13
|
+
create_git_remote 'elasticdot', info['app_repo']
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.open(opts)
|
17
|
+
require 'launchy'
|
18
|
+
|
19
|
+
find_app! opts
|
20
|
+
|
21
|
+
spinner "Opening #{@app}..." do
|
22
|
+
info = api.get "/domains/#{@app}"
|
23
|
+
Launchy.open info['live_address']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.destroy(opts)
|
28
|
+
find_app! opts
|
29
|
+
|
30
|
+
spinner "Destroying app #{@app}..." do
|
31
|
+
api.delete "/domains/#{@app}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.info(opts)
|
36
|
+
find_app! opts
|
37
|
+
|
38
|
+
h = api.get "/domains/#{@app}"
|
39
|
+
|
40
|
+
puts "=== #{@app}"
|
41
|
+
puts
|
42
|
+
puts "Git URL:\t#{h['git_repo']}"
|
43
|
+
puts "Owner Email:\t#{h['owner_email']}"
|
44
|
+
puts "Region:\t\tEU"
|
45
|
+
# puts "Slug Size:\t#{h['slug_size']}"
|
46
|
+
puts "Web URL:\thttp://#{h['live_address']}"
|
47
|
+
|
48
|
+
return unless (db = h['db'])
|
49
|
+
|
50
|
+
puts
|
51
|
+
puts "=== Database #{db['identifier']}"
|
52
|
+
puts "Plan: \t#{db['plan']['name']}"
|
53
|
+
puts "Nodes: \t#{db['plan']['nodes']}" unless db['plan']['shared']
|
54
|
+
puts "Version: \t#{db['version']}"
|
55
|
+
puts "Status: \t#{db['status']}"
|
56
|
+
puts "Name: \t#{db['name']}"
|
57
|
+
puts "User: \t#{db['user']}"
|
58
|
+
puts "Password: \t#{db['pass']}"
|
59
|
+
puts "URI: \t#{db['uri']}"
|
60
|
+
puts "Tables: \t#{db['tables']}"
|
61
|
+
puts "Disk Space Used:\t#{db['space_used']}"
|
62
|
+
puts "AVG CPU Load: \t#{db['cpu_load']}"
|
63
|
+
puts "Created at: \t#{db['created_at']}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.list
|
67
|
+
apps = api.get "/apps?type=web"
|
68
|
+
puts '=== My Apps'
|
69
|
+
apps.each { |app| puts app }
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class ElasticDot::Command::Auth < ElasticDot::Command::Base
|
2
|
+
require 'netrc'
|
3
|
+
|
4
|
+
def self.login
|
5
|
+
puts "Enter your ElasticDot credentials."
|
6
|
+
|
7
|
+
print "email: "
|
8
|
+
email = ask
|
9
|
+
|
10
|
+
print "password: "
|
11
|
+
|
12
|
+
echo_off
|
13
|
+
pass = ask_for_password
|
14
|
+
echo_on
|
15
|
+
|
16
|
+
api = ElasticDot::API.new(email: email, password: pass)
|
17
|
+
key = api.login
|
18
|
+
|
19
|
+
netrc.delete 'j.elasticops.com'
|
20
|
+
|
21
|
+
netrc['j.elasticops.com'] = email, key
|
22
|
+
netrc.save
|
23
|
+
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.logout
|
28
|
+
netrc.delete 'j.elasticops.com'
|
29
|
+
netrc.save
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.authenticate!
|
35
|
+
return true if authenticated?
|
36
|
+
login
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.credentials
|
40
|
+
netrc['j.elasticops.com']
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def self.netrc
|
45
|
+
@netrc ||= Netrc.read netrc_path
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.netrc_path
|
49
|
+
default = Netrc.default_path
|
50
|
+
encrypted = default + ".gpg"
|
51
|
+
|
52
|
+
File.exists?(encrypted) ? encrypted : default
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.ask_for_password
|
56
|
+
echo_off
|
57
|
+
password = ask
|
58
|
+
puts
|
59
|
+
echo_on
|
60
|
+
|
61
|
+
password
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.authenticated?
|
65
|
+
netrc['j.elasticops.com'] ? true : false
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class Module
|
2
|
+
def cattr_accessor(attribute_name)
|
3
|
+
class_eval <<-CODE
|
4
|
+
def self.#{attribute_name}
|
5
|
+
@@#{attribute_name} ||= nil
|
6
|
+
end
|
7
|
+
def self.#{attribute_name}=(value)
|
8
|
+
@@#{attribute_name} = value
|
9
|
+
end
|
10
|
+
CODE
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ElasticDot::Command::Base
|
15
|
+
cattr_accessor :api
|
16
|
+
|
17
|
+
protected
|
18
|
+
def self.echo_off
|
19
|
+
with_tty do
|
20
|
+
system "stty -echo"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.echo_on
|
25
|
+
with_tty do
|
26
|
+
system "stty echo"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.with_tty(&block)
|
31
|
+
return unless $stdin.isatty
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.ask
|
36
|
+
$stdin.gets.to_s.strip
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.find_app!(opts)
|
40
|
+
if opts[:app]
|
41
|
+
@app = opts[:app]
|
42
|
+
elsif app = extract_app_in_dir
|
43
|
+
@app = app
|
44
|
+
end
|
45
|
+
|
46
|
+
return true if @app
|
47
|
+
|
48
|
+
puts 'No app specified.'
|
49
|
+
puts 'Specify which app to use with --app APP.'
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.has_git?
|
54
|
+
%x{ git --version }
|
55
|
+
$?.success?
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.git(args)
|
59
|
+
return "" unless has_git?
|
60
|
+
flattened_args = [args].flatten.compact.join(" ")
|
61
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.create_git_remote(remote, url)
|
65
|
+
return if git('remote').split("\n").include?(remote)
|
66
|
+
return unless File.exists?(".git")
|
67
|
+
|
68
|
+
git "remote add #{remote} #{url}"
|
69
|
+
|
70
|
+
puts "Git remote #{remote} added"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.which(cmd)
|
74
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
75
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
76
|
+
exts.each { |ext|
|
77
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
78
|
+
return exe if File.executable? exe
|
79
|
+
}
|
80
|
+
end
|
81
|
+
return nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.extract_app_in_dir
|
85
|
+
if app = extract_app_from_git_config
|
86
|
+
return app
|
87
|
+
elsif app = extract_app_from_git_config('elasticdot')
|
88
|
+
return app
|
89
|
+
else
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.extract_app_from_git_config(remote = 'origin')
|
95
|
+
url = git "config remote.#{remote}.url"
|
96
|
+
return nil if url.empty?
|
97
|
+
|
98
|
+
if url =~ /^git@git\.elasticdot\.com:([\w\d\-\.]+)\.git$/
|
99
|
+
$1
|
100
|
+
else
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.spinner(desc, &block)
|
106
|
+
chars = %w{ | / - \\ }
|
107
|
+
|
108
|
+
t = Thread.new { block.call }
|
109
|
+
while t.alive?
|
110
|
+
print "#{desc} [#{chars[0]}]"
|
111
|
+
sleep 0.1
|
112
|
+
print "\r"
|
113
|
+
|
114
|
+
chars.push chars.shift
|
115
|
+
end
|
116
|
+
|
117
|
+
t.join
|
118
|
+
|
119
|
+
puts "#{desc} [ok]"
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class ElasticDot::Command::Config < ElasticDot::Command::Base
|
2
|
+
def self.set(vars, opts)
|
3
|
+
unless vars.size > 0 and vars.all? { |a| a.include?('=') }
|
4
|
+
puts "Usage: elasticdot config:set KEY1=VALUE1 [KEY2=VALUE2 ...]\nMust specify KEY and VALUE to set."
|
5
|
+
exit 1
|
6
|
+
end
|
7
|
+
|
8
|
+
vars = parse_vars! vars
|
9
|
+
|
10
|
+
find_app! opts
|
11
|
+
|
12
|
+
puts "Setting ENV vars..."
|
13
|
+
|
14
|
+
api.post "/apps/#{@app}/vars", vars: vars
|
15
|
+
|
16
|
+
vars.each { |k, v| puts "#{k}:\t#{v}" }
|
17
|
+
|
18
|
+
puts
|
19
|
+
puts 'Please use ps:restart command to restart your app when you\'re ready.'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.unset(vars, opts)
|
23
|
+
if vars.empty?
|
24
|
+
puts "Usage: elasticdot config:unset KEY1 [KEY2 ...]\nMust specify KEY to unset."
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
find_app! opts
|
29
|
+
|
30
|
+
puts "Unsetting ENV vars..."
|
31
|
+
|
32
|
+
api.post "/apps/#{@app}/vars/unset", vars: vars
|
33
|
+
|
34
|
+
puts
|
35
|
+
puts 'Please use ps:restart command to restart your app when you\'re ready.'
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.get(args, opts)
|
39
|
+
var = args.shift
|
40
|
+
find_app! opts
|
41
|
+
|
42
|
+
vars = api.get("/domains/#{@app}")['vars']
|
43
|
+
|
44
|
+
value = vars.select {|v| v['key_name'] == var }.first['value'] rescue nil
|
45
|
+
|
46
|
+
puts value if value
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.list(opts)
|
50
|
+
find_app! opts
|
51
|
+
|
52
|
+
puts "=== #{@app} Config Vars"
|
53
|
+
|
54
|
+
vars = api.get("/domains/#{@app}")['vars']
|
55
|
+
|
56
|
+
vars.each {|v| puts "#{v['key_name']}:\t#{v['value']}" }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def self.parse_vars!(vars)
|
61
|
+
parsed_vars = {}
|
62
|
+
|
63
|
+
vars.each do |var|
|
64
|
+
k, v = var.split '=', 2
|
65
|
+
parsed_vars[k] = v || ''
|
66
|
+
end
|
67
|
+
|
68
|
+
parsed_vars
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
class ElasticDot::Command::Db < ElasticDot::Command::Base
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
def self.promote(opts)
|
5
|
+
find_app! opts
|
6
|
+
find_db! opts
|
7
|
+
|
8
|
+
spinner "Promoting database..." do
|
9
|
+
info = api.post "/databases/#{@db}/promote", app: @app
|
10
|
+
end
|
11
|
+
|
12
|
+
spinner "Restarting dots..." do
|
13
|
+
loop do
|
14
|
+
sleep 3
|
15
|
+
info = api.get "/domains/#{@app}"
|
16
|
+
break if info['status'] == 'active'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.dump(opts)
|
22
|
+
unless which 'mysqldump'
|
23
|
+
puts 'MySQL client is not installed.'
|
24
|
+
puts 'Please install it to proceed.'
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
find_db! opts
|
29
|
+
|
30
|
+
info = api.get("/databases/#{@db}")
|
31
|
+
|
32
|
+
uri = URI.parse info['uri']
|
33
|
+
|
34
|
+
system "mysqldump --opt -c -u#{info['user']} -p#{info['pass']} -h#{uri.host} -P#{uri.port} #{info['name']}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.console(opts)
|
38
|
+
unless which 'mysql'
|
39
|
+
puts 'MySQL client is not installed.'
|
40
|
+
puts 'Please install it to proceed.'
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
find_db! opts
|
45
|
+
|
46
|
+
info = api.get("/databases/#{@db}")
|
47
|
+
|
48
|
+
uri = URI.parse info['uri']
|
49
|
+
|
50
|
+
puts 'Attaching... '
|
51
|
+
system "mysql -f -u#{info['user']} -p#{info['pass']} -h#{uri.host} -P#{uri.port} #{info['name']}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.import(args, opts)
|
55
|
+
unless which 'mysql'
|
56
|
+
puts 'MySQL client is not installed.'
|
57
|
+
puts 'Please install it to proceed.'
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
find_db! opts
|
62
|
+
|
63
|
+
dump = args.shift
|
64
|
+
unless dump
|
65
|
+
puts 'Please specify a dump file.'
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
unless File.exists? dump
|
70
|
+
puts "#{dump}: no such file or directory"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
|
74
|
+
info = api.get("/databases/#{@db}")
|
75
|
+
|
76
|
+
uri = URI.parse info['uri']
|
77
|
+
|
78
|
+
spinner 'Importing...' do
|
79
|
+
system "mysql -f -u#{info['user']} -p#{info['pass']} -h#{uri.host} -P#{uri.port} #{info['name']} < #{dump}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.create(opts)
|
84
|
+
find_plan! opts
|
85
|
+
|
86
|
+
params = {plan: @plan}
|
87
|
+
|
88
|
+
if conf = opts[:conf]
|
89
|
+
unless File.exists? conf
|
90
|
+
puts "#{conf}: no such file or directory"
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
|
94
|
+
f = File.read conf
|
95
|
+
|
96
|
+
params.merge!(conf: f)
|
97
|
+
end
|
98
|
+
|
99
|
+
info = api.post "/databases", params
|
100
|
+
|
101
|
+
spinner "Database #{info['identifier']} is provisioning..." do
|
102
|
+
until info['status'] == 'active'
|
103
|
+
sleep 3
|
104
|
+
info = api.get("/databases/#{info['identifier']}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.destroy(opts)
|
110
|
+
find_db! opts
|
111
|
+
|
112
|
+
print "Destroying database #{@db}... "
|
113
|
+
|
114
|
+
info = api.delete("/databases/#{@db}")
|
115
|
+
|
116
|
+
puts 'done'
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.info(opts)
|
120
|
+
find_db! opts
|
121
|
+
|
122
|
+
db = api.get("/databases/#{@db}")
|
123
|
+
|
124
|
+
puts "=== Database #{db['identifier']}"
|
125
|
+
puts "Plan: \t#{db['plan']['name']}"
|
126
|
+
puts "Nodes: \t#{db['plan']['nodes']}" unless db['plan']['shared']
|
127
|
+
puts "Version: \t#{db['version']}"
|
128
|
+
puts "Status: \t#{db['status']}"
|
129
|
+
puts "Name: \t#{db['name']}"
|
130
|
+
puts "User: \t#{db['user']}"
|
131
|
+
puts "Password: \t#{db['pass']}"
|
132
|
+
puts "URI: \t#{db['uri']}"
|
133
|
+
puts "Tables: \t#{db['tables']}"
|
134
|
+
puts "Disk Space Used:\t#{db['space_used']}"
|
135
|
+
puts "AVG CPU Load: \t#{db['cpu_load']}"
|
136
|
+
puts "Created at: \t#{db['created_at']}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.list(opts)
|
140
|
+
puts "=== database list"
|
141
|
+
|
142
|
+
list = api.get("/databases")
|
143
|
+
|
144
|
+
list.each {|db| puts db['identifier'] }
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def self.find_db!(opts)
|
149
|
+
@db = opts[:db]
|
150
|
+
return true if @db
|
151
|
+
|
152
|
+
app = opts[:app]
|
153
|
+
app ||= extract_app_in_dir
|
154
|
+
|
155
|
+
unless app
|
156
|
+
puts 'No db specified.'
|
157
|
+
puts 'Specify at least option --app or --database.'
|
158
|
+
exit 1
|
159
|
+
end
|
160
|
+
|
161
|
+
info = api.get "/domains/#{app}"
|
162
|
+
|
163
|
+
unless info['db']
|
164
|
+
puts 'This app has no database associated.'
|
165
|
+
puts 'Specify --database option.'
|
166
|
+
exit 1
|
167
|
+
end
|
168
|
+
|
169
|
+
@db = info['db']['identifier']
|
170
|
+
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.find_plan!(opts)
|
175
|
+
@plan = opts[:plan]
|
176
|
+
return true if @plan
|
177
|
+
|
178
|
+
puts 'No plan specified.'
|
179
|
+
puts 'Specify which plan to use with -p, --plan PLAN.'
|
180
|
+
exit 1
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class ElasticDot::Command::Domains < ElasticDot::Command::Base
|
2
|
+
def self.add(args, opts)
|
3
|
+
domain = args.shift
|
4
|
+
validate_domain! 'add', domain
|
5
|
+
|
6
|
+
app = opts[:app]
|
7
|
+
find_app! opts
|
8
|
+
|
9
|
+
puts "Adding #{domain} to #{@app}..."
|
10
|
+
|
11
|
+
api.post "/domains/#{@app}/aliases", alias: domain
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.remove(args, opts)
|
15
|
+
domain = args.shift
|
16
|
+
validate_domain! 'remove', 'domain'
|
17
|
+
|
18
|
+
app = opts[:app]
|
19
|
+
find_app! opts
|
20
|
+
|
21
|
+
puts "Removing #{domain} from #{@app}..."
|
22
|
+
|
23
|
+
api.delete "/domains/#{@app}/aliases/#{domain}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.clear(opts)
|
27
|
+
find_app! opts
|
28
|
+
|
29
|
+
domains = api.get("/domains/#{@app}")['aliases']
|
30
|
+
|
31
|
+
puts "Removing all domain names from #{@app}..."
|
32
|
+
domains.each do |d|
|
33
|
+
next if d['factory']
|
34
|
+
api.delete "/domains/#{@app}/aliases/#{d['name']}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.list(opts)
|
39
|
+
find_app! opts
|
40
|
+
|
41
|
+
domains = api.get("/domains/#{@app}")['aliases']
|
42
|
+
|
43
|
+
puts "=== #{@app} Domain Names"
|
44
|
+
domains.each {|d| puts d['name'] }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def self.validate_domain!(m, d)
|
49
|
+
return true if d
|
50
|
+
|
51
|
+
puts "Usage: elasticdot domains:#{m} DOMAIN"
|
52
|
+
puts "Must specify DOMAIN to add."
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class ElasticDot::Command::Help < ElasticDot::Command::Base
|
2
|
+
def self.root_help
|
3
|
+
puts <<-HELP
|
4
|
+
Usage: elasticdot COMMAND [--app APP] [command-specific-options]
|
5
|
+
|
6
|
+
Primary help topics, type "elasticdot help TOPIC" for more details:
|
7
|
+
|
8
|
+
apps # manage apps
|
9
|
+
keys # manage authentication keys
|
10
|
+
config # manage app config vars
|
11
|
+
domains # manage custom domains
|
12
|
+
db # manage databases
|
13
|
+
addons # manage addon resources
|
14
|
+
|
15
|
+
Additional topics:
|
16
|
+
|
17
|
+
help # list commands and display help
|
18
|
+
version # display version
|
19
|
+
HELP
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.apps
|
23
|
+
puts <<-HELP
|
24
|
+
Apps commands:
|
25
|
+
|
26
|
+
apps:create [NAME] # create a new app
|
27
|
+
apps:destroy # permanently destroy an app
|
28
|
+
apps:info # show detailed app information
|
29
|
+
apps:open # open the app in a web browser
|
30
|
+
apps:list # show app list
|
31
|
+
HELP
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.addons
|
35
|
+
puts <<-HELP
|
36
|
+
Addons commands:
|
37
|
+
|
38
|
+
addons:list # list all available addons
|
39
|
+
addons:add ADDON # install an addon
|
40
|
+
addons:remove ADDON1 [ADDON2 ...] # uninstall one or more addons
|
41
|
+
HELP
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.domains
|
45
|
+
puts <<-HELP
|
46
|
+
Domains commands:
|
47
|
+
|
48
|
+
domains:add DOMAIN # add a custom domain to an app
|
49
|
+
domains:clear # remove all custom domains from an app
|
50
|
+
domains:remove DOMAIN # remove a custom domain from an app
|
51
|
+
HELP
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.keys
|
55
|
+
puts <<-HELP
|
56
|
+
Keys commands:
|
57
|
+
|
58
|
+
keys:add [KEY] # add a key for the current user
|
59
|
+
keys:clear # remove all authentication keys from the current user
|
60
|
+
keys:remove KEY # remove a key from the current user
|
61
|
+
HELP
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.config
|
65
|
+
puts <<-HELP
|
66
|
+
Config commands:
|
67
|
+
|
68
|
+
config:get KEY # display a config value for an app
|
69
|
+
config:set KEY1=VALUE1 [KEY2=VALUE2 ...] # set one or more config vars
|
70
|
+
config:unset KEY1 [KEY2 ...] # unset one or more config vars
|
71
|
+
HELP
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.ps
|
75
|
+
puts <<-HELP
|
76
|
+
PS commands:
|
77
|
+
|
78
|
+
ps:resize web=TIER # resize dot to the given tier
|
79
|
+
ps:scale web=N [mode=scaling|manual] # scale dots by the given amount
|
80
|
+
ps:stop # stop all dots
|
81
|
+
ps:restart # restart all dots
|
82
|
+
HELP
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.db
|
86
|
+
puts <<-HELP
|
87
|
+
DB commands:
|
88
|
+
|
89
|
+
db:promote # sets DATABASE as your DATABASE_URL
|
90
|
+
db:dump # print DATABASE dump to stdout
|
91
|
+
db:console # open mysql console for DATABASE
|
92
|
+
db:import # import dump file to DATABASE
|
93
|
+
db:create # create new database
|
94
|
+
db:destroy # destroy DATABASE
|
95
|
+
db:info # show info for DATABASE
|
96
|
+
db:list # list all databases
|
97
|
+
HELP
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.method_missing(m, *args, &block)
|
101
|
+
unless self.respond_to? m
|
102
|
+
puts "Invalid command: #{m}"
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
class ElasticDot::Command::Keys < ElasticDot::Command::Base
|
2
|
+
def self.add
|
3
|
+
associate_or_generate_ssh_key
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.remove(keys, opts)
|
7
|
+
rkeys = api.get('/stats')['profile']['keys']
|
8
|
+
|
9
|
+
keys.each do |k|
|
10
|
+
rk = rkeys.select {|rk| rk['content'] =~ /#{k}/ }.first
|
11
|
+
|
12
|
+
unless rk
|
13
|
+
puts "Key not found, skipping #{k}..."
|
14
|
+
next
|
15
|
+
end
|
16
|
+
|
17
|
+
api.delete "/account/keys/#{rk['id']}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.clear
|
22
|
+
api.get('/stats')['profile']['keys'].each do |k|
|
23
|
+
api.delete "/account/keys/#{k['id']}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.list
|
28
|
+
puts "=== #{api.email} Keys"
|
29
|
+
keys = api.get('/stats')['profile']['keys'].each do |k|
|
30
|
+
puts k['content']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def self.associate_or_generate_ssh_key
|
36
|
+
public_keys = Dir.glob("#{Dir.home}/.ssh/*.pub").sort
|
37
|
+
|
38
|
+
case public_keys.length
|
39
|
+
when 0 then
|
40
|
+
puts "Could not find an existing public key."
|
41
|
+
print "Would you like to generate one? [Yn] "
|
42
|
+
|
43
|
+
if ask.strip.downcase == "y"
|
44
|
+
puts "Generating new SSH public key."
|
45
|
+
generate_ssh_key("id_rsa")
|
46
|
+
associate_key("#{Dir.home}/.ssh/id_rsa.pub")
|
47
|
+
end
|
48
|
+
when 1 then
|
49
|
+
puts "Found existing public key: #{public_keys.first}"
|
50
|
+
associate_key(public_keys.first)
|
51
|
+
else
|
52
|
+
puts "Found the following SSH public keys:"
|
53
|
+
public_keys.each_with_index do |key, index|
|
54
|
+
puts "#{index+1}) #{File.basename(key)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
print "Which would you like to use with your ElasticDot account? "
|
58
|
+
choice = ask.to_i - 1
|
59
|
+
chosen = public_keys[choice]
|
60
|
+
if choice == -1 || chosen.nil?
|
61
|
+
puts "Invalid choice"
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
|
65
|
+
associate_key(chosen)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.generate_ssh_key(keyfile)
|
70
|
+
ssh_dir = File.join(Dir.home, ".ssh")
|
71
|
+
unless File.exists?(ssh_dir)
|
72
|
+
FileUtils.mkdir_p ssh_dir
|
73
|
+
File.chmod(0700, ssh_dir)
|
74
|
+
end
|
75
|
+
|
76
|
+
output = `ssh-keygen -t rsa -N "" -f \"#{Dir.home}/.ssh/#{keyfile}\" 2>&1`
|
77
|
+
if ! $?.success?
|
78
|
+
puts "Could not generate key: #{output}"
|
79
|
+
exit 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.associate_key(key)
|
84
|
+
puts "Uploading SSH public key #{key}"
|
85
|
+
|
86
|
+
if File.exists?(key)
|
87
|
+
api.post '/account/keys', ssh_key: File.read(key)
|
88
|
+
else
|
89
|
+
puts "Could not upload SSH public key: key file '" + key + "' does not exist"
|
90
|
+
exit 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ElasticDot::Command::Logs < ElasticDot::Command::Base
|
2
|
+
def self.list(opts)
|
3
|
+
find_app! opts
|
4
|
+
|
5
|
+
max_id = nil
|
6
|
+
|
7
|
+
begin
|
8
|
+
res = api.get "/apps/#{@app}/logs?max_id=#{max_id}"
|
9
|
+
max_id, events = res['max_id'], res['events']
|
10
|
+
events.each {|e| puts e }
|
11
|
+
sleep 2
|
12
|
+
end while opts[:follow]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def self.apps_info(app)
|
17
|
+
info = api.get "/domains/#{app}"
|
18
|
+
|
19
|
+
app_tier = info['production'] ? 'production' : 'development'
|
20
|
+
{app_tier: app_tier, scaling: info['scaling'], tier: info['dot_tier']['name'], dots: info['min_dots'] }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
class ElasticDot::Command::Ps < ElasticDot::Command::Base
|
2
|
+
def self.resize(settings, opts)
|
3
|
+
find_app! opts
|
4
|
+
|
5
|
+
params = apps_info @app
|
6
|
+
|
7
|
+
web = nil
|
8
|
+
settings.each do |s|
|
9
|
+
p, v = s.split('=',2)
|
10
|
+
web = v and break if p == 'web'
|
11
|
+
end
|
12
|
+
|
13
|
+
unless web
|
14
|
+
puts 'At the moment you can only resize web processes'
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
params[:tier] = web
|
19
|
+
|
20
|
+
api.put "/websites/#{@app}/scaling", params
|
21
|
+
|
22
|
+
spinner "Resizing dots and restarting specified processes..." do
|
23
|
+
loop do
|
24
|
+
sleep 3
|
25
|
+
info = api.get "/domains/#{@app}"
|
26
|
+
break if info['status'] == 'active'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.scale(settings, opts)
|
32
|
+
find_app! opts
|
33
|
+
|
34
|
+
params = apps_info @app
|
35
|
+
|
36
|
+
web, mode = nil
|
37
|
+
settings.each do |s|
|
38
|
+
p, v = s.split('=',2)
|
39
|
+
web = v if p == 'web'
|
40
|
+
mode = v if p == 'mode'
|
41
|
+
end
|
42
|
+
|
43
|
+
unless web
|
44
|
+
puts 'Usage: elasticdot ps:scale web=N [mode=auto|manual]'
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
48
|
+
params[:scaling] = mode if mode
|
49
|
+
params[:dots] = web
|
50
|
+
|
51
|
+
api.put "/websites/#{@app}/scaling", params
|
52
|
+
info = apps_info @app
|
53
|
+
|
54
|
+
spinner "Scaling web processes..." do
|
55
|
+
loop do
|
56
|
+
sleep 3
|
57
|
+
info = api.get "/domains/#{@app}"
|
58
|
+
break if info['status'] == 'active'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "Now running #{info[:dots]} dots"
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.list(opts)
|
66
|
+
require 'time'
|
67
|
+
|
68
|
+
find_app! opts
|
69
|
+
|
70
|
+
loop do
|
71
|
+
|
72
|
+
info = api.get("/domains/#{@app}")
|
73
|
+
|
74
|
+
tier = info['dot_tier']
|
75
|
+
dots = info['dots']
|
76
|
+
|
77
|
+
puts "=== web (#{tier['name']}): #{info['procfile']}"
|
78
|
+
dots.each_with_index do |dot, i|
|
79
|
+
now = Time.now
|
80
|
+
elapsed = now - Time.parse(dot['started_at'])
|
81
|
+
since = time_ago(now - elapsed)
|
82
|
+
|
83
|
+
puts "web.#{i+1}: up #{since} cpu: #{dot['cpu_load']}%"
|
84
|
+
end
|
85
|
+
|
86
|
+
break unless opts[:follow]
|
87
|
+
sleep 2
|
88
|
+
system 'clear'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.restart(opts)
|
93
|
+
find_app! opts
|
94
|
+
|
95
|
+
api.post "/apps/#{@app}/restart"
|
96
|
+
|
97
|
+
spinner 'Restarting dots...' do
|
98
|
+
loop do
|
99
|
+
sleep 3
|
100
|
+
info = api.get "/domains/#{@app}"
|
101
|
+
break if info['status'] == 'active'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.stop(opts)
|
107
|
+
find_app! opts
|
108
|
+
|
109
|
+
spinner "Stopping dots..." do
|
110
|
+
api.post "/apps/#{@app}/stop"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def self.apps_info(app)
|
116
|
+
info = api.get "/domains/#{@app}"
|
117
|
+
|
118
|
+
app_tier = info['production'] ? 'production' : 'development'
|
119
|
+
{app_tier: app_tier, scaling: info['scaling'], tier: info['dot_tier']['name'], dots: info['min_dots'] }
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.time_ago(since)
|
123
|
+
if since.is_a?(String)
|
124
|
+
since = Time.parse(since)
|
125
|
+
end
|
126
|
+
|
127
|
+
elapsed = Time.now - since
|
128
|
+
|
129
|
+
message = since.strftime("%Y/%m/%d %H:%M:%S")
|
130
|
+
if elapsed <= 60
|
131
|
+
message << " (~ #{elapsed.floor}s ago)"
|
132
|
+
elsif elapsed <= (60 * 60)
|
133
|
+
message << " (~ #{(elapsed / 60).floor}m ago)"
|
134
|
+
elsif elapsed <= (60 * 60 * 25)
|
135
|
+
message << " (~ #{(elapsed / 60 / 60).floor}h ago)"
|
136
|
+
end
|
137
|
+
message
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ElasticDot::Command::Services < ElasticDot::Command::Base
|
2
|
+
def self.create(args, opts)
|
3
|
+
info = api.post '/domains', domain: args[0], type: 'service'
|
4
|
+
|
5
|
+
if info['error']
|
6
|
+
puts info['error']
|
7
|
+
exit 1
|
8
|
+
end
|
9
|
+
|
10
|
+
puts "Creating service app #{info['app_name']}... done"
|
11
|
+
puts info['app_repo']
|
12
|
+
|
13
|
+
create_git_remote 'elasticdot', info['app_repo']
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.destroy(opts)
|
17
|
+
find_app! opts
|
18
|
+
|
19
|
+
spinner "Destroying app #{@app}..." do
|
20
|
+
api.delete "/domains/#{@app}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.info(opts)
|
25
|
+
find_app! opts
|
26
|
+
|
27
|
+
h = api.get "/domains/#{@app}"
|
28
|
+
|
29
|
+
puts "=== #{@app}"
|
30
|
+
puts
|
31
|
+
puts "Git URL:\t#{h['git_repo']}"
|
32
|
+
puts "Owner Email:\t#{h['owner_email']}"
|
33
|
+
puts "Region:\t\tEU"
|
34
|
+
# puts "Slug Size:\t#{h['slug_size']}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.list
|
38
|
+
apps = api.get "/apps?type=service"
|
39
|
+
puts '=== My Services'
|
40
|
+
apps.each { |app| puts app }
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class String
|
2
|
+
def constantize
|
3
|
+
names = self.split('::')
|
4
|
+
names.shift if names.empty? || names.first.empty?
|
5
|
+
|
6
|
+
constant = Object
|
7
|
+
names.each do |name|
|
8
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
9
|
+
end
|
10
|
+
constant
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elasticdot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ElasticDot
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: netrc
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.7.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rest-client
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.6.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.6.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: launchy
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.4.2
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.4.2
|
62
|
+
description: ElasticDot Command Line Interface
|
63
|
+
email: info@elasticdot.com
|
64
|
+
executables:
|
65
|
+
- elasticdot
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- bin/elasticdot
|
70
|
+
- lib/elasticdot.rb
|
71
|
+
- lib/elasticdot/api.rb
|
72
|
+
- lib/elasticdot/cli.rb
|
73
|
+
- lib/elasticdot/command.rb
|
74
|
+
- lib/elasticdot/command/addons.rb
|
75
|
+
- lib/elasticdot/command/alias.rb
|
76
|
+
- lib/elasticdot/command/apps.rb
|
77
|
+
- lib/elasticdot/command/auth.rb
|
78
|
+
- lib/elasticdot/command/base.rb
|
79
|
+
- lib/elasticdot/command/config.rb
|
80
|
+
- lib/elasticdot/command/db.rb
|
81
|
+
- lib/elasticdot/command/domains.rb
|
82
|
+
- lib/elasticdot/command/help.rb
|
83
|
+
- lib/elasticdot/command/keys.rb
|
84
|
+
- lib/elasticdot/command/logs.rb
|
85
|
+
- lib/elasticdot/command/ps.rb
|
86
|
+
- lib/elasticdot/command/services.rb
|
87
|
+
- lib/elasticdot/initializers/string.rb
|
88
|
+
homepage: http://elasticdot.com
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.23
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: ElasticDot CLI
|
113
|
+
test_files: []
|