artofmission-heroku 1.6.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/README.md +66 -0
- data/Rakefile +107 -0
- data/bin/heroku +15 -0
- data/lib/heroku.rb +5 -0
- data/lib/heroku/client.rb +487 -0
- data/lib/heroku/command.rb +96 -0
- data/lib/heroku/commands/account.rb +13 -0
- data/lib/heroku/commands/addons.rb +109 -0
- data/lib/heroku/commands/app.rb +239 -0
- data/lib/heroku/commands/auth.rb +137 -0
- data/lib/heroku/commands/base.rb +133 -0
- data/lib/heroku/commands/bundles.rb +51 -0
- data/lib/heroku/commands/config.rb +55 -0
- data/lib/heroku/commands/db.rb +129 -0
- data/lib/heroku/commands/domains.rb +31 -0
- data/lib/heroku/commands/help.rb +148 -0
- data/lib/heroku/commands/keys.rb +49 -0
- data/lib/heroku/commands/logs.rb +11 -0
- data/lib/heroku/commands/maintenance.rb +13 -0
- data/lib/heroku/commands/plugins.rb +25 -0
- data/lib/heroku/commands/ps.rb +37 -0
- data/lib/heroku/commands/service.rb +23 -0
- data/lib/heroku/commands/sharing.rb +29 -0
- data/lib/heroku/commands/ssl.rb +33 -0
- data/lib/heroku/commands/version.rb +7 -0
- data/lib/heroku/helpers.rb +23 -0
- data/lib/heroku/plugin.rb +65 -0
- data/spec/base.rb +23 -0
- data/spec/client_spec.rb +366 -0
- data/spec/command_spec.rb +15 -0
- data/spec/commands/addons_spec.rb +47 -0
- data/spec/commands/app_spec.rb +175 -0
- data/spec/commands/auth_spec.rb +104 -0
- data/spec/commands/base_spec.rb +114 -0
- data/spec/commands/bundles_spec.rb +48 -0
- data/spec/commands/config_spec.rb +45 -0
- data/spec/commands/db_spec.rb +53 -0
- data/spec/commands/domains_spec.rb +31 -0
- data/spec/commands/keys_spec.rb +60 -0
- data/spec/commands/logs_spec.rb +21 -0
- data/spec/commands/maintenance_spec.rb +21 -0
- data/spec/commands/plugins_spec.rb +26 -0
- data/spec/commands/ps_spec.rb +16 -0
- data/spec/commands/sharing_spec.rb +32 -0
- data/spec/commands/ssl_spec.rb +25 -0
- data/spec/plugin_spec.rb +64 -0
- metadata +150 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Domains < BaseWithApp
|
3
|
+
def list
|
4
|
+
domains = heroku.list_domains(app)
|
5
|
+
if domains.empty?
|
6
|
+
display "No domain names for #{app}.#{heroku.host}"
|
7
|
+
else
|
8
|
+
display "Domain names for #{app}.#{heroku.host}:"
|
9
|
+
display domains.map { |d| d[:domain] }.join("\n")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias :index :list
|
13
|
+
|
14
|
+
def add
|
15
|
+
domain = args.shift.downcase rescue nil
|
16
|
+
heroku.add_domain(app, domain)
|
17
|
+
display "Added #{domain} as a custom domain name to #{app}.#{heroku.host}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove
|
21
|
+
domain = args.shift.downcase rescue nil
|
22
|
+
heroku.remove_domain(app, domain)
|
23
|
+
display "Removed #{domain} as a custom domain name to #{app}.#{heroku.host}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear
|
27
|
+
heroku.remove_domains(app)
|
28
|
+
display "Removed all domain names for #{app}.#{heroku.host}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Help < Base
|
3
|
+
class HelpGroup < Array
|
4
|
+
attr_reader :title
|
5
|
+
|
6
|
+
def initialize(title)
|
7
|
+
@title = title
|
8
|
+
end
|
9
|
+
|
10
|
+
def command(name, description)
|
11
|
+
self << [name, description]
|
12
|
+
end
|
13
|
+
|
14
|
+
def space
|
15
|
+
self << ['', '']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.groups
|
20
|
+
@groups ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.group(title, &block)
|
24
|
+
groups << begin
|
25
|
+
group = HelpGroup.new(title)
|
26
|
+
group.instance_eval(&block)
|
27
|
+
group
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_default_groups!
|
32
|
+
group('General Commands') do
|
33
|
+
command 'help', 'show this usage'
|
34
|
+
command 'version', 'show the gem version'
|
35
|
+
space
|
36
|
+
command 'list', 'list your apps'
|
37
|
+
command 'create [<name>]', 'create a new app'
|
38
|
+
space
|
39
|
+
command 'keys', 'show your user\'s public keys'
|
40
|
+
command 'keys:add [<path to keyfile>]', 'add a public key'
|
41
|
+
command 'keys:remove <keyname> ', 'remove a key by name (user@host)'
|
42
|
+
command 'keys:clear', 'remove all keys'
|
43
|
+
space
|
44
|
+
command 'info', 'show app info, like web url and git repo'
|
45
|
+
command 'open', 'open the app in a web browser'
|
46
|
+
command 'rename <newname>', 'rename the app'
|
47
|
+
space
|
48
|
+
command 'dynos <qty>', 'scale to qty web processes'
|
49
|
+
command 'workers <qty>', 'scale to qty background processes'
|
50
|
+
space
|
51
|
+
command 'sharing:add <email>', 'add a collaborator'
|
52
|
+
command 'sharing:remove <email>', 'remove a collaborator'
|
53
|
+
command 'sharing:transfer <email>', 'transfers the app ownership'
|
54
|
+
space
|
55
|
+
command 'domains:add <domain>', 'add a custom domain name'
|
56
|
+
command 'domains:remove <domain>', 'remove a custom domain name'
|
57
|
+
command 'domains:clear', 'remove all custom domains'
|
58
|
+
space
|
59
|
+
command 'ssl:add <pem> <key>', 'add SSL cert to the app'
|
60
|
+
command 'ssl:remove <domain>', 'removes SSL cert from the app domain'
|
61
|
+
space
|
62
|
+
command 'rake <command>', 'remotely execute a rake command'
|
63
|
+
command 'console <command>', 'remotely execute a single console command'
|
64
|
+
command 'console', 'start an interactive console to the remote app'
|
65
|
+
space
|
66
|
+
command 'restart', 'restart app servers'
|
67
|
+
command 'logs', 'fetch recent log output for debugging'
|
68
|
+
command 'logs:cron', 'fetch cron log output'
|
69
|
+
space
|
70
|
+
command 'maintenance:on', 'put the app into maintenance mode'
|
71
|
+
command 'maintenance:off', 'take the app out of maintenance mode'
|
72
|
+
space
|
73
|
+
command 'config', 'display the app\'s config vars (environment)'
|
74
|
+
command 'config:add key=val [...]', 'add one or more config vars'
|
75
|
+
command 'config:remove key [...]', 'remove one or more config vars'
|
76
|
+
command 'config:clear', 'clear user-set vars and reset to default'
|
77
|
+
space
|
78
|
+
command 'db:pull [<database_url>]', 'pull the app\'s database into a local database'
|
79
|
+
command 'db:push [<database_url>]', 'push a local database into the app\'s remote database'
|
80
|
+
command 'db:reset', 'reset the database for the app'
|
81
|
+
space
|
82
|
+
command 'bundles', 'list bundles for the app'
|
83
|
+
command 'bundles:capture [<bundle>]', 'capture a bundle of the app\'s code and data'
|
84
|
+
command 'bundles:download', 'download most recent app bundle as a tarball'
|
85
|
+
command 'bundles:download <bundle>', 'download the named bundle'
|
86
|
+
command 'bundles:animate <bundle>', 'animate a bundle into a new app'
|
87
|
+
command 'bundles:destroy <bundle>', 'destroy the named bundle'
|
88
|
+
space
|
89
|
+
command 'addons', 'list installed addons'
|
90
|
+
command 'addons:info', 'list all available addons'
|
91
|
+
command 'addons:add name [key=value]', 'install addon (with zero or more config vars)'
|
92
|
+
command 'addons:remove name', 'uninstall an addons'
|
93
|
+
command 'addons:clear', 'uninstall all addons'
|
94
|
+
space
|
95
|
+
command 'destroy', 'destroy the app permanently'
|
96
|
+
end
|
97
|
+
|
98
|
+
group('Plugins') do
|
99
|
+
command 'plugins', 'list installed plugins'
|
100
|
+
command 'plugins:install <url>', 'install the plugin from the specified git url'
|
101
|
+
command 'plugins:uninstall <url/name>', 'remove the specified plugin'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def index
|
106
|
+
display usage
|
107
|
+
end
|
108
|
+
|
109
|
+
def version
|
110
|
+
display Heroku::Client.version
|
111
|
+
end
|
112
|
+
|
113
|
+
def usage
|
114
|
+
longest_command_length = self.class.groups.map do |group|
|
115
|
+
group.map { |g| g.first.length }
|
116
|
+
end.flatten.max
|
117
|
+
|
118
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
119
|
+
output.puts "=== %s" % group.title
|
120
|
+
output.puts
|
121
|
+
|
122
|
+
group.each do |command, description|
|
123
|
+
if command.empty?
|
124
|
+
output.puts
|
125
|
+
else
|
126
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
output.puts
|
131
|
+
output
|
132
|
+
end.string + <<-EOTXT
|
133
|
+
=== Example:
|
134
|
+
|
135
|
+
rails myapp
|
136
|
+
cd myapp
|
137
|
+
git init
|
138
|
+
git add .
|
139
|
+
git commit -m "my new app"
|
140
|
+
heroku create
|
141
|
+
git push heroku master
|
142
|
+
|
143
|
+
EOTXT
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
Heroku::Command::Help.create_default_groups!
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Keys < Base
|
3
|
+
def list
|
4
|
+
long = args.any? { |a| a == '--long' }
|
5
|
+
keys = heroku.keys
|
6
|
+
if keys.empty?
|
7
|
+
display "No keys for #{heroku.user}"
|
8
|
+
else
|
9
|
+
display "=== #{keys.size} key#{'s' if keys.size > 1} for #{heroku.user}"
|
10
|
+
keys.each do |key|
|
11
|
+
display long ? key.strip : format_key_for_display(key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
alias :index :list
|
16
|
+
|
17
|
+
def add
|
18
|
+
keyfile = args.first || find_key
|
19
|
+
key = File.read(keyfile)
|
20
|
+
|
21
|
+
display "Uploading ssh public key #{keyfile}"
|
22
|
+
heroku.add_key(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove
|
26
|
+
heroku.remove_key(args.first)
|
27
|
+
display "Key #{args.first} removed."
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
heroku.remove_all_keys
|
32
|
+
display "All keys removed."
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
def find_key
|
37
|
+
%w(rsa dsa).each do |key_type|
|
38
|
+
keyfile = "#{home_directory}/.ssh/id_#{key_type}.pub"
|
39
|
+
return keyfile if File.exists? keyfile
|
40
|
+
end
|
41
|
+
raise CommandFailed, "No ssh public key found in #{home_directory}/.ssh/id_[rd]sa.pub. You may want to specify the full path to the keyfile."
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_key_for_display(key)
|
45
|
+
type, hex, local = key.strip.split(/\s/)
|
46
|
+
[type, hex[0,10] + '...' + hex[-10,10], local].join(' ')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Plugins < Base
|
3
|
+
def list
|
4
|
+
::Heroku::Plugin.list.each do |plugin|
|
5
|
+
display plugin
|
6
|
+
end
|
7
|
+
end
|
8
|
+
alias :index :list
|
9
|
+
|
10
|
+
def install
|
11
|
+
plugin = Heroku::Plugin.new(args.shift)
|
12
|
+
if plugin.install
|
13
|
+
display "#{plugin} installed"
|
14
|
+
else
|
15
|
+
error "Could not install #{plugin}. Please check the URL and try again"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def uninstall
|
20
|
+
plugin = Heroku::Plugin.new(args.shift)
|
21
|
+
plugin.uninstall
|
22
|
+
display "#{plugin} uninstalled"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Ps < Base
|
3
|
+
def index
|
4
|
+
ps = heroku.ps(extract_app)
|
5
|
+
|
6
|
+
output = []
|
7
|
+
output << "UPID Slug Command State Since"
|
8
|
+
output << "------- ------------ -------------------------- ---------- ---------"
|
9
|
+
|
10
|
+
ps.each do |p|
|
11
|
+
output << "%-7s %-12s %-26s %-10s %-9s" %
|
12
|
+
[p['upid'], p['slug'], truncate(p['command'], 22), p['state'], time_ago(p['elapsed'])]
|
13
|
+
end
|
14
|
+
|
15
|
+
display output.join("\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def time_ago(elapsed)
|
20
|
+
if elapsed < 60
|
21
|
+
"#{elapsed.floor}s ago"
|
22
|
+
elsif elapsed < (60 * 60)
|
23
|
+
"#{(elapsed / 60).floor}m ago"
|
24
|
+
else
|
25
|
+
"#{(elapsed / 60 / 60).floor}h ago"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def truncate(text, length)
|
30
|
+
if text.size > length
|
31
|
+
text[0, length - 2] + '..'
|
32
|
+
else
|
33
|
+
text
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Service < Base
|
3
|
+
def start
|
4
|
+
error "heroku service:start is defunct. Use heroku workers +1 instead."
|
5
|
+
end
|
6
|
+
|
7
|
+
def up
|
8
|
+
error "heroku service:up is defunct. Use heroku workers +1 instead."
|
9
|
+
end
|
10
|
+
|
11
|
+
def down
|
12
|
+
error "heroku service:down is defunct. Use heroku workers -1 instead."
|
13
|
+
end
|
14
|
+
|
15
|
+
def bounce
|
16
|
+
error "heroku service:bounce is defunct. Use heroku restart instead."
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
error "heroku service:status is defunct. Use heroku ps instead."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Sharing < BaseWithApp
|
3
|
+
def list
|
4
|
+
list = heroku.list_collaborators(app)
|
5
|
+
display list.map { |c| c[:email] }.join("\n")
|
6
|
+
end
|
7
|
+
alias :index :list
|
8
|
+
|
9
|
+
def add
|
10
|
+
email = args.shift.downcase rescue ''
|
11
|
+
raise(CommandFailed, "Specify an email address to share the app with.") if email == ''
|
12
|
+
display heroku.add_collaborator(app, email)
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove
|
16
|
+
email = args.shift.downcase rescue ''
|
17
|
+
raise(CommandFailed, "Specify an email address to remove from the app.") if email == ''
|
18
|
+
heroku.remove_collaborator(app, email)
|
19
|
+
display "Collaborator removed."
|
20
|
+
end
|
21
|
+
|
22
|
+
def transfer
|
23
|
+
email = args.shift.downcase rescue ''
|
24
|
+
raise(CommandFailed, "Specify the email address of the new owner") if email == ''
|
25
|
+
heroku.update(app, :transfer_owner => email)
|
26
|
+
display "App ownership transfered. New owner is #{email}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Heroku::Command
|
2
|
+
class Ssl < BaseWithApp
|
3
|
+
def list
|
4
|
+
heroku.list_domains(app).each do |d|
|
5
|
+
if cert = d[:cert]
|
6
|
+
display "#{d[:domain]} has a SSL certificate registered to #{cert[:subject]} which expires on #{cert[:expires_at].strftime("%b %d, %Y")}"
|
7
|
+
else
|
8
|
+
display "#{d[:domain]} has no certificate"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias :index :list
|
13
|
+
|
14
|
+
def add
|
15
|
+
usage = 'heroku ssl:add <pem> <key>'
|
16
|
+
raise CommandFailed, "Missing pem file. Usage:\n#{usage}" unless pem_file = args.shift
|
17
|
+
raise CommandFailed, "Missing key file. Usage:\n#{usage}" unless key_file = args.shift
|
18
|
+
raise CommandFailed, "Could not find pem in #{pem_file}" unless File.exists?(pem_file)
|
19
|
+
raise CommandFailed, "Could not find key in #{key_file}" unless File.exists?(key_file)
|
20
|
+
|
21
|
+
pem = File.read(pem_file)
|
22
|
+
key = File.read(key_file)
|
23
|
+
info = heroku.add_ssl(app, pem, key)
|
24
|
+
display "Added certificate to #{info['domain']}, expiring in #{info['expires_at']}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove
|
28
|
+
raise CommandFailed, "Missing domain. Usage:\nheroku ssl:remove <domain>" unless domain = args.shift
|
29
|
+
heroku.remove_ssl(app, domain)
|
30
|
+
display "Removed certificate from #{domain}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Helpers
|
3
|
+
def home_directory
|
4
|
+
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
5
|
+
end
|
6
|
+
|
7
|
+
def running_on_windows?
|
8
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
9
|
+
end
|
10
|
+
|
11
|
+
def running_on_a_mac?
|
12
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
unless String.method_defined?(:shellescape)
|
18
|
+
class String
|
19
|
+
def shellescape
|
20
|
+
empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# based on the Rails Plugin
|
2
|
+
|
3
|
+
module Heroku
|
4
|
+
class Plugin
|
5
|
+
class << self
|
6
|
+
include Heroku::Helpers
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :name, :uri
|
10
|
+
|
11
|
+
def self.directory
|
12
|
+
"#{home_directory}/.heroku/plugins"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.list
|
16
|
+
Dir["#{directory}/*"].map do |folder|
|
17
|
+
File.basename(folder)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load!
|
22
|
+
list.each do |plugin|
|
23
|
+
folder = "#{self.directory}/#{plugin}"
|
24
|
+
$: << "#{folder}/lib" if File.directory? "#{folder}/lib"
|
25
|
+
load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(uri)
|
30
|
+
@uri = uri
|
31
|
+
guess_name(uri)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
def path
|
39
|
+
"#{self.class.directory}/#{name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def install
|
43
|
+
FileUtils.mkdir_p(path)
|
44
|
+
Dir.chdir(path) do
|
45
|
+
system("git init > /dev/null 2>&1")
|
46
|
+
if !system("git pull --depth 1 #{uri} > /dev/null 2>&1")
|
47
|
+
FileUtils.rm_rf path
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def uninstall
|
55
|
+
FileUtils.rm_r path if File.directory?(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def guess_name(url)
|
60
|
+
@name = File.basename(url)
|
61
|
+
@name = File.basename(File.dirname(url)) if @name.empty?
|
62
|
+
@name.gsub!(/\.git$/, '') if @name =~ /\.git$/
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|