artofmission-heroku 1.6.3

Sign up to get free protection for your applications and to get access to all the features.
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