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.
Files changed (47) hide show
  1. data/README.md +66 -0
  2. data/Rakefile +107 -0
  3. data/bin/heroku +15 -0
  4. data/lib/heroku.rb +5 -0
  5. data/lib/heroku/client.rb +487 -0
  6. data/lib/heroku/command.rb +96 -0
  7. data/lib/heroku/commands/account.rb +13 -0
  8. data/lib/heroku/commands/addons.rb +109 -0
  9. data/lib/heroku/commands/app.rb +239 -0
  10. data/lib/heroku/commands/auth.rb +137 -0
  11. data/lib/heroku/commands/base.rb +133 -0
  12. data/lib/heroku/commands/bundles.rb +51 -0
  13. data/lib/heroku/commands/config.rb +55 -0
  14. data/lib/heroku/commands/db.rb +129 -0
  15. data/lib/heroku/commands/domains.rb +31 -0
  16. data/lib/heroku/commands/help.rb +148 -0
  17. data/lib/heroku/commands/keys.rb +49 -0
  18. data/lib/heroku/commands/logs.rb +11 -0
  19. data/lib/heroku/commands/maintenance.rb +13 -0
  20. data/lib/heroku/commands/plugins.rb +25 -0
  21. data/lib/heroku/commands/ps.rb +37 -0
  22. data/lib/heroku/commands/service.rb +23 -0
  23. data/lib/heroku/commands/sharing.rb +29 -0
  24. data/lib/heroku/commands/ssl.rb +33 -0
  25. data/lib/heroku/commands/version.rb +7 -0
  26. data/lib/heroku/helpers.rb +23 -0
  27. data/lib/heroku/plugin.rb +65 -0
  28. data/spec/base.rb +23 -0
  29. data/spec/client_spec.rb +366 -0
  30. data/spec/command_spec.rb +15 -0
  31. data/spec/commands/addons_spec.rb +47 -0
  32. data/spec/commands/app_spec.rb +175 -0
  33. data/spec/commands/auth_spec.rb +104 -0
  34. data/spec/commands/base_spec.rb +114 -0
  35. data/spec/commands/bundles_spec.rb +48 -0
  36. data/spec/commands/config_spec.rb +45 -0
  37. data/spec/commands/db_spec.rb +53 -0
  38. data/spec/commands/domains_spec.rb +31 -0
  39. data/spec/commands/keys_spec.rb +60 -0
  40. data/spec/commands/logs_spec.rb +21 -0
  41. data/spec/commands/maintenance_spec.rb +21 -0
  42. data/spec/commands/plugins_spec.rb +26 -0
  43. data/spec/commands/ps_spec.rb +16 -0
  44. data/spec/commands/sharing_spec.rb +32 -0
  45. data/spec/commands/ssl_spec.rb +25 -0
  46. data/spec/plugin_spec.rb +64 -0
  47. metadata +150 -0
@@ -0,0 +1,137 @@
1
+ module Heroku::Command
2
+ class Auth < Base
3
+ attr_accessor :credentials
4
+
5
+ def client
6
+ @client ||= init_heroku
7
+ end
8
+
9
+ def init_heroku
10
+ client = Heroku::Client.new(user, password, host)
11
+ client.on_warning { |msg| self.display("\n#{msg}\n\n") }
12
+ client
13
+ end
14
+
15
+ def host
16
+ ENV['HEROKU_HOST'] || 'heroku.com'
17
+ end
18
+
19
+ def reauthorize
20
+ @credentials = ask_for_credentials
21
+ write_credentials
22
+ end
23
+
24
+ def user # :nodoc:
25
+ get_credentials
26
+ @credentials[0]
27
+ end
28
+
29
+ def password # :nodoc:
30
+ get_credentials
31
+ @credentials[1]
32
+ end
33
+
34
+ def credentials_file
35
+ "#{home_directory}/.heroku/credentials"
36
+ end
37
+
38
+ def get_credentials # :nodoc:
39
+ return if @credentials
40
+ unless @credentials = read_credentials
41
+ @credentials = ask_for_credentials
42
+ save_credentials
43
+ end
44
+ @credentials
45
+ end
46
+
47
+ def read_credentials
48
+ File.exists?(credentials_file) and File.read(credentials_file).split("\n")
49
+ end
50
+
51
+ def echo_off
52
+ system "stty -echo"
53
+ end
54
+
55
+ def echo_on
56
+ system "stty echo"
57
+ end
58
+
59
+ def ask_for_credentials
60
+ puts "Enter your Heroku credentials."
61
+
62
+ print "Email: "
63
+ user = ask
64
+
65
+ print "Password: "
66
+ password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
67
+
68
+ [ user, password ]
69
+ end
70
+
71
+ def ask_for_password_on_windows
72
+ require "Win32API"
73
+ char = nil
74
+ password = ''
75
+
76
+ while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
77
+ break if char == 10 || char == 13 # received carriage return or newline
78
+ if char == 127 || char == 8 # backspace and delete
79
+ password.slice!(-1, 1)
80
+ else
81
+ password << char.chr
82
+ end
83
+ end
84
+ puts
85
+ return password
86
+ end
87
+
88
+ def ask_for_password
89
+ echo_off
90
+ password = ask
91
+ puts
92
+ echo_on
93
+ return password
94
+ end
95
+
96
+ def save_credentials
97
+ begin
98
+ write_credentials
99
+ Heroku::Command.run_internal('keys:add', args)
100
+ rescue RestClient::Unauthorized => e
101
+ delete_credentials
102
+ raise e unless retry_login?
103
+
104
+ display "\nAuthentication failed"
105
+ @credentials = ask_for_credentials
106
+ @client = init_heroku
107
+ retry
108
+ rescue Exception => e
109
+ delete_credentials
110
+ raise e
111
+ end
112
+ end
113
+
114
+ def retry_login?
115
+ @login_attempts ||= 0
116
+ @login_attempts += 1
117
+ @login_attempts < 3
118
+ end
119
+
120
+ def write_credentials
121
+ FileUtils.mkdir_p(File.dirname(credentials_file))
122
+ File.open(credentials_file, 'w') do |f|
123
+ f.puts self.credentials
124
+ end
125
+ set_credentials_permissions
126
+ end
127
+
128
+ def set_credentials_permissions
129
+ FileUtils.chmod 0700, File.dirname(credentials_file)
130
+ FileUtils.chmod 0600, credentials_file
131
+ end
132
+
133
+ def delete_credentials
134
+ FileUtils.rm_f(credentials_file)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,133 @@
1
+ require 'fileutils'
2
+
3
+ module Heroku::Command
4
+ class Base
5
+ include Heroku::Helpers
6
+
7
+ attr_accessor :args
8
+ attr_reader :autodetected_app
9
+ def initialize(args, heroku=nil)
10
+ @args = args
11
+ @heroku = heroku
12
+ @autodetected_app = false
13
+ end
14
+
15
+ def display(msg, newline=true)
16
+ if newline
17
+ puts(msg)
18
+ else
19
+ print(msg)
20
+ STDOUT.flush
21
+ end
22
+ end
23
+
24
+ def format_date(date)
25
+ date = Time.parse(date) if date.is_a?(String)
26
+ date.strftime("%Y-%m-%d %H:%M %Z")
27
+ end
28
+
29
+ def error(msg)
30
+ Heroku::Command.error(msg)
31
+ end
32
+
33
+ def ask
34
+ gets.strip
35
+ end
36
+
37
+ def shell(cmd)
38
+ FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
39
+ end
40
+
41
+ def heroku
42
+ @heroku ||= Heroku::Command.run_internal('auth:client', args)
43
+ end
44
+
45
+ def extract_app(force=true)
46
+ app = extract_option('--app')
47
+ unless app
48
+ app = extract_app_in_dir(Dir.pwd) ||
49
+ raise(CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") if force
50
+ @autodetected_app = true
51
+ end
52
+ app
53
+ end
54
+
55
+ def extract_app_in_dir(dir)
56
+ return unless remotes = git_remotes(dir)
57
+
58
+ if remote = extract_option('--remote')
59
+ remotes[remote]
60
+ else
61
+ apps = remotes.values.uniq
62
+ case apps.size
63
+ when 0; return nil
64
+ when 1; return apps.first
65
+ else
66
+ current_dir_name = dir.split('/').last.downcase
67
+ apps.select { |a| a.downcase == current_dir_name }.first
68
+ end
69
+ end
70
+ end
71
+
72
+ def git_remotes(base_dir)
73
+ git_config = "#{base_dir}/.git/config"
74
+ unless File.exists?(git_config)
75
+ parent = base_dir.split('/')[0..-2].join('/')
76
+ return git_remotes(parent) unless parent.empty?
77
+ else
78
+ remotes = {}
79
+ current_remote = nil
80
+ File.read(git_config).split(/\n/).each do |l|
81
+ current_remote = $1 if l.match(/\[remote \"([\w\d-]+)\"\]/)
82
+ app = (l.match(/url = git@#{heroku.host}:([\w\d-]+)\.git/) || [])[1]
83
+ if current_remote && app
84
+ remotes[current_remote.downcase] = app
85
+ current_remote = nil
86
+ end
87
+ end
88
+ return remotes
89
+ end
90
+ end
91
+
92
+ def extract_option(options, default=true)
93
+ values = options.is_a?(Array) ? options : [options]
94
+ return unless opt_index = args.select { |a| values.include? a }.first
95
+ opt_position = args.index(opt_index) + 1
96
+ if args.size > opt_position && opt_value = args[opt_position]
97
+ if opt_value.include?('--')
98
+ opt_value = nil
99
+ else
100
+ args.delete_at(opt_position)
101
+ end
102
+ end
103
+ opt_value ||= default
104
+ args.delete(opt_index)
105
+ block_given? ? yield(opt_value) : opt_value
106
+ end
107
+
108
+ def web_url(name)
109
+ "http://#{name}.#{heroku.host}/"
110
+ end
111
+
112
+ def git_url(name)
113
+ "git@#{heroku.host}:#{name}.git"
114
+ end
115
+
116
+ def app_urls(name)
117
+ "#{web_url(name)} | #{git_url(name)}"
118
+ end
119
+
120
+ def escape(value)
121
+ heroku.escape(value)
122
+ end
123
+ end
124
+
125
+ class BaseWithApp < Base
126
+ attr_accessor :app
127
+
128
+ def initialize(args, heroku=nil)
129
+ super(args, heroku)
130
+ @app ||= extract_app
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,51 @@
1
+ module Heroku::Command
2
+ class Bundles < BaseWithApp
3
+ def list
4
+ list = heroku.bundles(app)
5
+ if list.size > 0
6
+ list.each do |bundle|
7
+ space = ' ' * [(18 - bundle[:name].size),1].max
8
+ display "#{bundle[:name]}" + space + "#{bundle[:state]} #{bundle[:created_at].strftime("%m/%d/%Y %H:%M")}"
9
+ end
10
+ else
11
+ display "#{app} has no bundles."
12
+ end
13
+ end
14
+ alias :index :list
15
+
16
+ def capture
17
+ bundle = args.shift.strip.downcase rescue nil
18
+
19
+ bundle = heroku.bundle_capture(app, bundle)
20
+ display "Began capturing bundle #{bundle} from #{app}"
21
+ end
22
+
23
+ def destroy
24
+ bundle = args.first.strip.downcase rescue nil
25
+ unless bundle
26
+ display "Usage: heroku bundle:destroy <bundle>"
27
+ else
28
+ heroku.bundle_destroy(app, bundle)
29
+ display "Destroyed bundle #{bundle} from #{app}"
30
+ end
31
+ end
32
+
33
+ def download
34
+ fname = "#{app}.tar.gz"
35
+ bundle = args.shift.strip.downcase rescue nil
36
+ url = heroku.bundle_url(app, bundle)
37
+ File.open(fname, "wb") { |f| f.write RestClient.get(url) }
38
+ display "Downloaded #{File.stat(fname).size} byte bundle #{fname}"
39
+ end
40
+
41
+ def animate
42
+ bundle = args.shift.strip.downcase rescue ""
43
+ if bundle.length == 0
44
+ display "Usage: heroku bundle:animate <bundle>"
45
+ else
46
+ name = heroku.create(nil, :origin_bundle_app => app, :origin_bundle => bundle)
47
+ display "Animated #{app} #{bundle} into #{app_urls(name)}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ module Heroku::Command
2
+ class Config < BaseWithApp
3
+ def index
4
+ long = args.delete('--long')
5
+ vars = heroku.config_vars(app)
6
+ display_vars(vars, :long => long)
7
+ end
8
+
9
+ def add
10
+ unless args.size > 0 and args.all? { |a| a.include?('=') }
11
+ raise CommandFailed, "Usage: heroku config:add <key>=<value> [<key2>=<value2> ...]"
12
+ end
13
+
14
+ vars = args.inject({}) do |vars, arg|
15
+ key, value = arg.split('=', 2)
16
+ vars[key] = value
17
+ vars
18
+ end
19
+
20
+ display "Adding config vars:"
21
+ display_vars(vars, :indent => 2)
22
+
23
+ display "Restarting app...", false
24
+ heroku.add_config_vars(app, vars)
25
+ display "done."
26
+ end
27
+
28
+ def remove
29
+ display "Removing #{args.first} and restarting app...", false
30
+ heroku.remove_config_var(app, args.first)
31
+ display "done."
32
+ end
33
+ alias :rm :remove
34
+
35
+ def clear
36
+ display "Clearing all config vars and restarting app...", false
37
+ heroku.clear_config_vars(app)
38
+ display "done."
39
+ end
40
+
41
+ protected
42
+ def display_vars(vars, options={})
43
+ max_length = vars.map { |v| v[0].size }.max
44
+ vars.keys.sort.each do |key|
45
+ spaces = ' ' * (max_length - key.size)
46
+ display "#{' ' * (options[:indent] || 0)}#{key}#{spaces} => #{format(vars[key], options)}"
47
+ end
48
+ end
49
+
50
+ def format(value, options)
51
+ return value if options[:long] || value.size < 36
52
+ value[0, 16] + '...' + value[-16, 16]
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,129 @@
1
+ module Heroku::Command
2
+ class Db < BaseWithApp
3
+ def pull
4
+ database_url = args.shift.strip rescue ''
5
+ if database_url == ''
6
+ database_url = parse_database_yml
7
+ display "Auto-detected local database: #{database_url}" if database_url != ''
8
+ end
9
+ raise(CommandFailed, "Invalid database url") if database_url == ''
10
+
11
+ taps_client(database_url) do |client|
12
+ client.cmd_receive
13
+ end
14
+ end
15
+
16
+ def push
17
+ display("Warning: All data in the the remote database will be overwritten and will not be recoverable.")
18
+ display("Are you sure you wish to push to the remote database? (y/n)? ", false)
19
+ if ask.downcase == 'y'
20
+ database_url = args.shift.strip rescue ''
21
+ if database_url == ''
22
+ database_url = parse_database_yml
23
+ display "Auto-detected local database: #{database_url}" if database_url != ''
24
+ end
25
+ raise(CommandFailed, "Invalid database url") if database_url == ''
26
+
27
+ taps_client(database_url) do |client|
28
+ client.cmd_send
29
+ end
30
+ end
31
+ end
32
+
33
+ def reset
34
+ if !autodetected_app
35
+ info = heroku.info(app)
36
+ url = info[:domain_name] || "http://#{info[:name]}.#{heroku.host}/"
37
+
38
+ display("Warning: All data in the '#{app}' database will be erased and will not be recoverable.")
39
+ display("Are you sure you wish to continue? (y/n)? ", false)
40
+ if ask.downcase == 'y'
41
+ heroku.database_reset(app)
42
+ display "Database reset for '#{app}' (#{url})"
43
+ end
44
+ else
45
+ display "Set the app you want to reset the database for by adding --app <app name> to this command"
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def parse_database_yml
52
+ return "" unless File.exists?(Dir.pwd + '/config/database.yml')
53
+
54
+ environment = ENV['RAILS_ENV'] || ENV['MERB_ENV'] || ENV['RACK_ENV']
55
+ environment = 'development' if environment.nil? or environment.empty?
56
+
57
+ conf = YAML.load(File.read(Dir.pwd + '/config/database.yml'))[environment]
58
+ case conf['adapter']
59
+ when 'sqlite3'
60
+ return "sqlite://#{conf['database']}"
61
+ when 'postgresql'
62
+ uri_hash = conf_to_uri_hash(conf)
63
+ uri_hash['scheme'] = 'postgres'
64
+ return uri_hash_to_url(uri_hash)
65
+ else
66
+ return uri_hash_to_url(conf_to_uri_hash(conf))
67
+ end
68
+ rescue Object => e
69
+ ""
70
+ end
71
+
72
+ def conf_to_uri_hash(conf)
73
+ uri = {}
74
+ uri['scheme'] = conf['adapter']
75
+ uri['username'] = conf['user'] || conf['username']
76
+ uri['password'] = conf['password']
77
+ uri['host'] = conf['host'] || conf['hostname']
78
+ uri['port'] = conf['port']
79
+ uri['path'] = conf['database']
80
+
81
+ conf['encoding'] = 'utf8' if conf['encoding'] == 'unicode' or conf['encoding'].nil?
82
+ uri['query'] = "encoding=#{conf['encoding']}"
83
+
84
+ uri
85
+ end
86
+
87
+ def uri_hash_to_url(uri)
88
+ url = "#{uri['scheme']}://"
89
+ if uri['username'].size
90
+ url += escape(uri['username'])
91
+ url += ':' + escape(uri['password']) if uri['password']
92
+ url += "@"
93
+ url += uri['host'] || '127.0.0.1'
94
+ else
95
+ url += uri['host'] if uri['host']
96
+ end
97
+ url += uri['port'] if uri['port']
98
+ url += "/"
99
+ url += uri['path']
100
+ url += "?#{uri['query']}" if uri['query']
101
+ url
102
+ end
103
+
104
+ def taps_client(database_url, &block)
105
+ chunk_size = 1000
106
+ Taps::Config.database_url = database_url
107
+ Taps::Config.verify_database_url
108
+
109
+ Taps::ClientSession.start(database_url, "http://heroku:osui59a24am79x@taps.#{heroku.host}", chunk_size) do |client|
110
+ uri = heroku.database_session(app)
111
+ client.set_session(uri)
112
+ client.verify_server
113
+ yield client
114
+ end
115
+ end
116
+
117
+ def initialize(*args)
118
+ super(*args)
119
+
120
+ gem 'taps', '>= 0.2.23', '< 0.3.0'
121
+ require 'taps/client_session'
122
+ rescue LoadError
123
+ message = "Taps Load Error: #{$!.message}\n"
124
+ message << "You may need to install or update the taps gem to use db commands.\n"
125
+ message << "On most systems this will be:\n\nsudo gem install taps"
126
+ error message
127
+ end
128
+ end
129
+ end