hr_deploy 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/hr_deploy +191 -0
- data/lib/hr_deploy/config_handler.rb +190 -0
- data/lib/hr_deploy/deployer.rb +368 -0
- data/lib/hr_deploy/target.rb +55 -0
- data/lib/hr_deploy/version.rb +3 -0
- metadata +52 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b28b9132177e558b47d24cb7f9c82fcef1d8e868
|
4
|
+
data.tar.gz: 67d784589f95f0f7aa318be470c41eafee993fe5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6d9b7ada7ca554d8599255e6a25f4df5a970ca0eaa3d0559e89de8b3f8388ba641a67de98d72c2070a5d067dd637c31e31234ad95e1f744a24e736eb213cbd8
|
7
|
+
data.tar.gz: 2cab2dbd608ace629c1d29bb7b6d7cd4bac6010025087ff372488a3a5c013874a81842c84afb41a58cfddc7e3d891f94728879c7f7c79f9b3da4dfbc2622fa9e
|
data/bin/hr_deploy
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'hr_deploy/deployer'
|
4
|
+
require 'hr_deploy/config_handler'
|
5
|
+
require 'hr_deploy/target'
|
6
|
+
require 'hr_deploy/version'
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def main
|
11
|
+
command = parse(ARGV)
|
12
|
+
if command
|
13
|
+
react(command)
|
14
|
+
else
|
15
|
+
puts 'Unknown command'
|
16
|
+
puts
|
17
|
+
help(true)
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse(opts)
|
23
|
+
if opts == [] || opts == ['help']
|
24
|
+
{ command: :help }
|
25
|
+
|
26
|
+
elsif opts == ['version']
|
27
|
+
{ command: :version }
|
28
|
+
|
29
|
+
elsif opts == ['generate']
|
30
|
+
{ command: :generate }
|
31
|
+
|
32
|
+
elsif opts.first == 'deploy' && (opts.length == 1 || opts.length == 2)
|
33
|
+
# This will extract the name of the target to deploy to or nil if no target is specified
|
34
|
+
target = opts[1, opts.length].first
|
35
|
+
{ command: :deploy, target: target }
|
36
|
+
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def react(command)
|
43
|
+
if command[:command] == :help
|
44
|
+
help
|
45
|
+
|
46
|
+
elsif command[:command] == :version
|
47
|
+
version
|
48
|
+
|
49
|
+
elsif command[:command] == :generate || command[:command] == :deploy
|
50
|
+
if inside_app?
|
51
|
+
generate if command[:command] == :generate
|
52
|
+
attempt_deploy(command[:target]) if command[:command] == :deploy
|
53
|
+
else
|
54
|
+
puts 'You should be inside a Heroku app to run this command'
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
else
|
59
|
+
raise "Command was parsed but don't know how to deal with it"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate
|
64
|
+
generate = true
|
65
|
+
|
66
|
+
if HR_Deploy::ConfigHandler.config_exists?
|
67
|
+
print 'Config file already exists. Overwrite? [Y/n] > '
|
68
|
+
generate = ($stdin.gets.chomp == 'Y')
|
69
|
+
end
|
70
|
+
|
71
|
+
if generate
|
72
|
+
HR_Deploy::ConfigHandler.new.generate_config
|
73
|
+
puts "Example config created at #{HR_Deploy::ConfigHandler::CONFIG_FILE_NAME}. Edit it to suit your needs"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def attempt_deploy(target_name)
|
78
|
+
|
79
|
+
# These tools are needed for all deploys
|
80
|
+
exit 1 unless tools_available?('heroku', 'git')
|
81
|
+
|
82
|
+
if HR_Deploy::ConfigHandler.config_exists?
|
83
|
+
raw_targets = HR_Deploy::ConfigHandler.new.load_targets
|
84
|
+
if raw_targets
|
85
|
+
# Config is correct
|
86
|
+
targets = raw_targets.map { |raw_target| HR_Deploy::Target.new(raw_target) }
|
87
|
+
|
88
|
+
if target_name
|
89
|
+
target = targets.find { |target| target.name == target_name }
|
90
|
+
if target
|
91
|
+
deploy(target)
|
92
|
+
else
|
93
|
+
puts "No such target: #{target_name}"
|
94
|
+
exit 1
|
95
|
+
end
|
96
|
+
|
97
|
+
else
|
98
|
+
# No target is specified, look for the default one
|
99
|
+
default_targets = targets.select { |target| target.default? }
|
100
|
+
|
101
|
+
if default_targets.length == 0
|
102
|
+
puts 'No default target specified. Edit config to specify one'
|
103
|
+
exit 1
|
104
|
+
elsif default_targets.length > 1
|
105
|
+
puts 'Multiple default targets specified. Edit config to specify only one'
|
106
|
+
exit 1
|
107
|
+
else
|
108
|
+
# Deploy default target
|
109
|
+
deploy(default_targets.first)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
# 'ConfigHandler.new.load_targets' will print out the error here
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
else
|
117
|
+
puts "No deployment config is present. Run 'hr_deploy generate' to generate a config example"
|
118
|
+
exit 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def deploy(target)
|
123
|
+
# Some targets need 'curl' to disable/enable New Relic pinger
|
124
|
+
exit 1 if target.requires_curl? && !tools_available?('curl')
|
125
|
+
|
126
|
+
# Actual deployment
|
127
|
+
deployer = HR_Deploy::Deployer.new(target)
|
128
|
+
deployer.deploy
|
129
|
+
end
|
130
|
+
|
131
|
+
def inside_app?
|
132
|
+
File.directory?('.git')
|
133
|
+
end
|
134
|
+
|
135
|
+
def tools_available?(*tools)
|
136
|
+
|
137
|
+
def command_available?(cmd)
|
138
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
139
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
140
|
+
exts.each do |ext|
|
141
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
142
|
+
return true if File.executable? exe
|
143
|
+
end
|
144
|
+
end
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
all_tools_available = true
|
149
|
+
|
150
|
+
tools.each do |tool|
|
151
|
+
unless command_available?(tool)
|
152
|
+
puts "You don't have access to '#{tool}' command"
|
153
|
+
all_tools_available = false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
all_tools_available
|
158
|
+
end
|
159
|
+
|
160
|
+
def help(after_error = false)
|
161
|
+
help_string = ''
|
162
|
+
|
163
|
+
unless after_error
|
164
|
+
help_string += "hr_deploy - Deploy Rails apps to Heroku\n\n"
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
help_string += <<-eos
|
169
|
+
Usage: hr_deploy [command]
|
170
|
+
|
171
|
+
The following commands are supported:
|
172
|
+
|
173
|
+
generate Generate a config file which specifies targets to deploy to.
|
174
|
+
Won't overwrite existing config unless confirmed.
|
175
|
+
|
176
|
+
deploy [target] Deploy to [target]. If no [target] is specified, deploy
|
177
|
+
to default target specified in config.
|
178
|
+
|
179
|
+
version Show gem version.
|
180
|
+
|
181
|
+
help Show help.
|
182
|
+
eos
|
183
|
+
|
184
|
+
puts help_string
|
185
|
+
end
|
186
|
+
|
187
|
+
def version
|
188
|
+
puts HR_Deploy::VERSION
|
189
|
+
end
|
190
|
+
|
191
|
+
main
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module HR_Deploy
|
4
|
+
|
5
|
+
class ConfigHandler
|
6
|
+
|
7
|
+
CONFIG_FILE_NAME = '.heroku_deploy_config.yaml'
|
8
|
+
|
9
|
+
EXAMPLE_CONFIG =
|
10
|
+
<<-eos
|
11
|
+
---
|
12
|
+
# Target name, required.
|
13
|
+
# This is the target that hr_deploy will deploy to when you execute
|
14
|
+
# 'hr_deploy deploy (target)'.
|
15
|
+
# This is also what is searched for when you want to deploy to a
|
16
|
+
# particular target.
|
17
|
+
- :staging:
|
18
|
+
|
19
|
+
# Human readable name of the target, optional, defaults to target name.
|
20
|
+
:description: Staging
|
21
|
+
|
22
|
+
# URL to temporarily disable New Relic availability monitoring, optional.
|
23
|
+
# Should be wrapped in single quotes.
|
24
|
+
# If you use New Relic to monitor availability, replace this with the url
|
25
|
+
# that you use to temporarily disable monitoring. You can find it New Relic
|
26
|
+
# availability settings. Delete this and the following line if you
|
27
|
+
# don't use that.
|
28
|
+
:new_relic_disable_pinger_url: 'http://disable'
|
29
|
+
|
30
|
+
# URL to enable New Relic availability monitoring, optional.
|
31
|
+
# Should be wrapped in single quotes.
|
32
|
+
# If you use New Relic to monitor availability, replace this with the url
|
33
|
+
# that you use to enable monitoring. You can find it New Relic
|
34
|
+
# availability settings. Delete this and the previous line if you
|
35
|
+
# don't use that.
|
36
|
+
:new_relic_enable_pinger_url: 'http://enable'
|
37
|
+
|
38
|
+
# Whether to use Heroku PG Backups add-on, optional, defaults to false.
|
39
|
+
# If you use Heroku PG Backups add-on and want to take database backups
|
40
|
+
# automatically, set this to true. Delete this line if you don't use that.
|
41
|
+
:backup_db: true
|
42
|
+
|
43
|
+
# Whether this target is default, optional, defaults to false.
|
44
|
+
# If default is set to true, this target will be picked automatically
|
45
|
+
# when you execute 'hr_deploy deploy' without arguments.
|
46
|
+
:default: true
|
47
|
+
|
48
|
+
# Example of another target, feel free to delete or modify.
|
49
|
+
- :production:
|
50
|
+
:description: Production
|
51
|
+
:new_relic_disable_pinger_url: 'http://disable'
|
52
|
+
:new_relic_enable_pinger_url: 'http://enable'
|
53
|
+
:backup_db: true
|
54
|
+
:default: false
|
55
|
+
eos
|
56
|
+
|
57
|
+
def self.config_exists?
|
58
|
+
File.exists?(CONFIG_FILE_NAME)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Check if the YAML file is in the right format
|
62
|
+
def load_targets
|
63
|
+
begin
|
64
|
+
targets = YAML.load_file(CONFIG_FILE_NAME)
|
65
|
+
|
66
|
+
rescue
|
67
|
+
puts 'Could not parse config'
|
68
|
+
puts "Fix it or generate a new one with 'hr_deploy generate'"
|
69
|
+
return nil
|
70
|
+
|
71
|
+
else
|
72
|
+
|
73
|
+
correct_format = true
|
74
|
+
|
75
|
+
if targets.instance_of?(Array)
|
76
|
+
targets.each do |target|
|
77
|
+
if target.instance_of?(Hash) && target.length == 1
|
78
|
+
options = target.values.first
|
79
|
+
unless options.instance_of?(Hash) || options == nil
|
80
|
+
correct_format = false
|
81
|
+
break
|
82
|
+
end
|
83
|
+
else
|
84
|
+
correct_format = false
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
else
|
89
|
+
correct_format = false
|
90
|
+
end
|
91
|
+
|
92
|
+
if correct_format
|
93
|
+
correct_config?(targets) ? targets : nil
|
94
|
+
else
|
95
|
+
puts 'Incorrect config format'
|
96
|
+
puts "Fix it or generate a new one with 'hr_deploy generate'"
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def correct_config?(targets)
|
103
|
+
# At this point we know we've got an array of hashes
|
104
|
+
# Each hash has one key, and another hash as the value of that key
|
105
|
+
|
106
|
+
targets.each do |target|
|
107
|
+
|
108
|
+
enable_pinger_url_given = false
|
109
|
+
disable_pinger_url_given = false
|
110
|
+
|
111
|
+
target_name = target.keys.first
|
112
|
+
# Each target name is a symbol
|
113
|
+
unless target_name.instance_of?(Symbol)
|
114
|
+
puts "Target name is specified incorrectly: #{target_name}"
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
|
118
|
+
# Hash of options
|
119
|
+
options = target[target_name]
|
120
|
+
|
121
|
+
# Target might have no options
|
122
|
+
if options
|
123
|
+
options.each do |option, value|
|
124
|
+
|
125
|
+
# Each option name is a symbol
|
126
|
+
unless option.instance_of?(Symbol)
|
127
|
+
puts "Option is specified incorrectly: #{option}"
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
|
131
|
+
case option
|
132
|
+
when :description
|
133
|
+
unless value.instance_of?(String)
|
134
|
+
puts 'Target description should be a string'
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
|
138
|
+
when :new_relic_disable_pinger_url
|
139
|
+
disable_pinger_url_given = true
|
140
|
+
unless value.instance_of?(String)
|
141
|
+
puts 'New Relic disable pinger URL should be a string'
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
when :new_relic_enable_pinger_url
|
146
|
+
enable_pinger_url_given = true
|
147
|
+
unless value.instance_of?(String)
|
148
|
+
puts 'New Relic enable pinger URL should be a string'
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
|
152
|
+
when :backup_db
|
153
|
+
unless value.instance_of?(TrueClass) || value.instance_of?(FalseClass)
|
154
|
+
puts 'Backup database option should be a boolean'
|
155
|
+
return false
|
156
|
+
end
|
157
|
+
|
158
|
+
when :default
|
159
|
+
unless value.instance_of?(TrueClass) || value.instance_of?(FalseClass)
|
160
|
+
puts 'Whether this target is the default one option should be a boolean'
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
|
164
|
+
else
|
165
|
+
puts "Option unknown: #{option}"
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if enable_pinger_url_given && (!disable_pinger_url_given)
|
171
|
+
puts 'URL to enable New Relic pinger is given, but URL to disable it is not'
|
172
|
+
return false
|
173
|
+
end
|
174
|
+
|
175
|
+
if disable_pinger_url_given && (!enable_pinger_url_given)
|
176
|
+
puts 'URL to disable New Relic pinger is given, but URL to enable it is not'
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Config is correct
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
186
|
+
def generate_config
|
187
|
+
File.open(CONFIG_FILE_NAME, 'w') { |file| file.write(EXAMPLE_CONFIG) }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
module HR_Deploy
|
2
|
+
|
3
|
+
class Deployer
|
4
|
+
|
5
|
+
attr_reader :target
|
6
|
+
attr_reader :pinging_interaction
|
7
|
+
attr_reader :perform_db_backup
|
8
|
+
|
9
|
+
def initialize(target)
|
10
|
+
@target = target
|
11
|
+
@pinging_interaction = target.requires_pinging_interaction?
|
12
|
+
@perform_db_backup = target.backup_db?
|
13
|
+
end
|
14
|
+
|
15
|
+
def deploy
|
16
|
+
confirm_deploy
|
17
|
+
|
18
|
+
start_time = Time.now
|
19
|
+
puts "#{target.description} deploy started at #{Time.now.strftime('%H:%M:%S')}"
|
20
|
+
puts
|
21
|
+
|
22
|
+
check_connectivity
|
23
|
+
check_heroku_status
|
24
|
+
|
25
|
+
if pinging_interaction
|
26
|
+
disable_pinging
|
27
|
+
continue?(:disabling_pinger) { enable_maintenance }
|
28
|
+
else
|
29
|
+
enable_maintenance
|
30
|
+
end
|
31
|
+
|
32
|
+
if perform_db_backup
|
33
|
+
continue?(:enabling_maintenance) { backup_db }
|
34
|
+
continue?(:backing_up_db) { push_code }
|
35
|
+
else
|
36
|
+
continue?(:enabling_maintenance) { push_code }
|
37
|
+
end
|
38
|
+
|
39
|
+
continue?(:pushing_code) { migrate_db }
|
40
|
+
continue?(:migrating_db) { restart }
|
41
|
+
|
42
|
+
if pinging_interaction
|
43
|
+
continue?(:restarting) { enable_pinging }
|
44
|
+
continue?(:enabling_pinger) { disable_maintenance }
|
45
|
+
else
|
46
|
+
continue?(:restarting) { disable_maintenance }
|
47
|
+
end
|
48
|
+
|
49
|
+
continue?(:disabling_maintenance) do
|
50
|
+
puts 'Waiting 10 seconds for application to go live...'
|
51
|
+
sleep 10
|
52
|
+
puts 'Opening application...'
|
53
|
+
system("heroku open --remote #{target.name}")
|
54
|
+
puts
|
55
|
+
|
56
|
+
puts "Deploy completed in #{(Time.now - start_time).round} seconds"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def confirm_deploy
|
63
|
+
answered = false
|
64
|
+
|
65
|
+
until answered
|
66
|
+
print "Deploy to #{target.description}? (Yes / No) > "
|
67
|
+
answer = $stdin.gets.chomp
|
68
|
+
|
69
|
+
if answer == 'Yes' || answer == 'yes'
|
70
|
+
answered = true
|
71
|
+
puts
|
72
|
+
elsif answer == 'No' || answer == 'no'
|
73
|
+
abort_deploy(nil)
|
74
|
+
else
|
75
|
+
puts 'Unknown answer'
|
76
|
+
puts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def abort_deploy(message, stage = nil)
|
82
|
+
|
83
|
+
if stage
|
84
|
+
puts 'Notes:'
|
85
|
+
puts
|
86
|
+
|
87
|
+
case stage
|
88
|
+
when :disabling_pinger
|
89
|
+
puts '1) Application state did not change'
|
90
|
+
puts '2) Availability pinging could not be disabled'
|
91
|
+
|
92
|
+
when :enabling_maintenance
|
93
|
+
puts '1) Application state did not change'
|
94
|
+
puts
|
95
|
+
puts '2) Maintenance mode could not be enabled'
|
96
|
+
if pinging_interaction
|
97
|
+
puts
|
98
|
+
puts '3) Availability pinging is disabled'
|
99
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
100
|
+
end
|
101
|
+
|
102
|
+
when :backing_up_db
|
103
|
+
puts '1) Application state did not change'
|
104
|
+
puts
|
105
|
+
puts '2) Maintenance mode is enabled'
|
106
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
107
|
+
puts
|
108
|
+
puts '3) Database backup could not be created'
|
109
|
+
puts
|
110
|
+
if pinging_interaction
|
111
|
+
puts '4) Availability pinging is disabled'
|
112
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
113
|
+
end
|
114
|
+
|
115
|
+
when :pushing_code
|
116
|
+
puts '1) Application state is unknown'
|
117
|
+
puts
|
118
|
+
puts '2) Maintenance mode is enabled'
|
119
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
120
|
+
puts
|
121
|
+
puts '3) Application code was not pushed properly'
|
122
|
+
puts "* Check releases with 'heroku releases --remote #{target.name}' and rollback with " +
|
123
|
+
"'heroku rollback (version) --remote #{target.name}' if needed"
|
124
|
+
if pinging_interaction
|
125
|
+
puts
|
126
|
+
puts '4) Availability pinging is disabled'
|
127
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
128
|
+
end
|
129
|
+
|
130
|
+
when :migrating_db
|
131
|
+
puts '1) Application state is unknown'
|
132
|
+
puts
|
133
|
+
puts '2) Maintenance mode is enabled'
|
134
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
135
|
+
puts
|
136
|
+
puts '3) Code was pushed but database was not migrated properly'
|
137
|
+
puts "* Check releases with 'heroku releases --remote #{target.name}' and rollback " +
|
138
|
+
"with 'heroku rollback (version) --remote #{target.name}' if needed"
|
139
|
+
if perform_db_backup
|
140
|
+
puts "* List database backups with 'heroku pgbackups --remote #{target.name}' and restore " +
|
141
|
+
"with 'heroku pgbackups:restore (database name) (version) --remote #{target.name}' if needed"
|
142
|
+
end
|
143
|
+
if pinging_interaction
|
144
|
+
puts
|
145
|
+
puts '4) Availability pinging is disabled'
|
146
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
147
|
+
end
|
148
|
+
|
149
|
+
when :restarting
|
150
|
+
puts '1) Application state is unknown'
|
151
|
+
puts
|
152
|
+
puts '2) Maintenance mode is enabled'
|
153
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
154
|
+
puts
|
155
|
+
puts '3) Database was migrated but could not restart'
|
156
|
+
puts "* Try to restart manually with 'heroku restart --remote #{target.name}'"
|
157
|
+
puts "* Check releases with 'heroku releases --remote #{target.name}' and rollback " +
|
158
|
+
"with 'heroku rollback (version) --remote #{target.name}' if needed"
|
159
|
+
if perform_db_backup
|
160
|
+
puts "* List database backups with 'heroku pgbackups --remote #{target.name}' and restore " +
|
161
|
+
"with 'heroku pgbackups:restore (database name) (version) --remote #{target.name}' if needed"
|
162
|
+
end
|
163
|
+
if pinging_interaction
|
164
|
+
puts
|
165
|
+
puts '4) Availability pinging is disabled'
|
166
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
167
|
+
end
|
168
|
+
|
169
|
+
when :enabling_pinger
|
170
|
+
puts '1) Application has been deployed'
|
171
|
+
puts
|
172
|
+
puts '2) Maintenance mode is enabled'
|
173
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
174
|
+
if pinging_interaction
|
175
|
+
puts
|
176
|
+
puts '3) Availability pinging could not be enabled'
|
177
|
+
puts "* To enable: curl #{target.enable_pinger_url}"
|
178
|
+
end
|
179
|
+
|
180
|
+
when :disabling_maintenance
|
181
|
+
puts '1) Application has been deployed'
|
182
|
+
puts
|
183
|
+
puts '2) Maintenance mode could not be disabled'
|
184
|
+
puts "* To disable: heroku maintenance:off --remote #{target.name}"
|
185
|
+
|
186
|
+
else
|
187
|
+
raise 'Unknown stage'
|
188
|
+
end
|
189
|
+
|
190
|
+
puts
|
191
|
+
end
|
192
|
+
|
193
|
+
if message
|
194
|
+
puts "Deploy aborted with message: #{message}"
|
195
|
+
else
|
196
|
+
puts 'Deploy aborted'
|
197
|
+
end
|
198
|
+
|
199
|
+
exit
|
200
|
+
end
|
201
|
+
|
202
|
+
def continue?(stage)
|
203
|
+
answered = false
|
204
|
+
|
205
|
+
until answered
|
206
|
+
|
207
|
+
case stage
|
208
|
+
when :disabling_pinger
|
209
|
+
print 'Pinger disabled? (Yes / No) > '
|
210
|
+
when :enabling_maintenance
|
211
|
+
print 'Maintenance mode enabled? (Yes / No) > '
|
212
|
+
when :backing_up_db
|
213
|
+
print 'Database backed up? (Yes / No) > '
|
214
|
+
when :pushing_code
|
215
|
+
print 'Code pushed successfully? (Yes / No) > '
|
216
|
+
when :migrating_db
|
217
|
+
print 'Database migrated? (Yes / No) > '
|
218
|
+
when :restarting
|
219
|
+
print 'Restarted? (Yes / No) > '
|
220
|
+
when :enabling_pinger
|
221
|
+
print 'Pinger enabled? (Yes / No) > '
|
222
|
+
when :disabling_maintenance
|
223
|
+
print 'Maintenance mode disabled? (Yes / No) > '
|
224
|
+
else
|
225
|
+
raise 'Unknown stage'
|
226
|
+
end
|
227
|
+
|
228
|
+
answer = $stdin.gets.chomp
|
229
|
+
|
230
|
+
if answer == 'Yes' || answer == 'yes'
|
231
|
+
answered = true
|
232
|
+
puts
|
233
|
+
yield
|
234
|
+
elsif answer == 'No' || answer == 'no'
|
235
|
+
puts
|
236
|
+
abort_deploy(nil, stage)
|
237
|
+
else
|
238
|
+
puts 'Unknown answer'
|
239
|
+
puts
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
def check_connectivity
|
246
|
+
require 'open-uri'
|
247
|
+
|
248
|
+
puts 'Checking Internet connection...'
|
249
|
+
|
250
|
+
begin
|
251
|
+
open('http://www.google.com')
|
252
|
+
rescue
|
253
|
+
puts 'Not connected to the Internet'
|
254
|
+
puts
|
255
|
+
abort_deploy('Not connected to the Internet')
|
256
|
+
else
|
257
|
+
puts 'Internet connection is fine'
|
258
|
+
puts
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def check_heroku_status
|
263
|
+
|
264
|
+
require 'uri'
|
265
|
+
require 'net/http'
|
266
|
+
require 'net/https'
|
267
|
+
require 'json'
|
268
|
+
|
269
|
+
puts 'Checking Heroku status...'
|
270
|
+
|
271
|
+
uri = URI.parse('https://status.heroku.com/api/v3/current-status')
|
272
|
+
|
273
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
274
|
+
http.use_ssl = true
|
275
|
+
|
276
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
277
|
+
|
278
|
+
begin
|
279
|
+
response = http.request(request)
|
280
|
+
|
281
|
+
rescue
|
282
|
+
puts 'Could not fetch Heroku status'
|
283
|
+
puts
|
284
|
+
abort_deploy('Could not fetch Heroku status')
|
285
|
+
|
286
|
+
else
|
287
|
+
if response.code == '200'
|
288
|
+
|
289
|
+
status = JSON.parse(response.body)
|
290
|
+
status = status['status'] if status
|
291
|
+
|
292
|
+
if status && status['Production'] && status['Development']
|
293
|
+
|
294
|
+
production_ok = status['Production'] == 'green'
|
295
|
+
development_ok = status['Development'] == 'green'
|
296
|
+
|
297
|
+
if production_ok && development_ok
|
298
|
+
puts 'Heroku is operating correctly'
|
299
|
+
puts
|
300
|
+
else
|
301
|
+
puts 'Heroku is having problems'
|
302
|
+
puts
|
303
|
+
abort_deploy('Heroku is having problems')
|
304
|
+
end
|
305
|
+
else
|
306
|
+
|
307
|
+
puts 'Could not parse Heroku status'
|
308
|
+
puts
|
309
|
+
abort_deploy('Could not parse Heroku status')
|
310
|
+
end
|
311
|
+
|
312
|
+
else
|
313
|
+
puts 'Could not fetch Heroku status'
|
314
|
+
puts
|
315
|
+
abort_deploy('Could not fetch Heroku status')
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def disable_pinging
|
321
|
+
puts 'Disabling availability pinger...'
|
322
|
+
system("curl #{target.disable_pinger_url}")
|
323
|
+
puts
|
324
|
+
end
|
325
|
+
|
326
|
+
def enable_maintenance
|
327
|
+
puts 'Enabling maintenance mode...'
|
328
|
+
system("heroku maintenance:on --remote #{target.name}")
|
329
|
+
puts
|
330
|
+
end
|
331
|
+
|
332
|
+
def backup_db
|
333
|
+
puts 'Backing up database...'
|
334
|
+
system("heroku pgbackups:capture --expire --remote #{target.name}")
|
335
|
+
puts
|
336
|
+
end
|
337
|
+
|
338
|
+
def push_code
|
339
|
+
puts 'Pushing code...'
|
340
|
+
system("git push #{target.name} master")
|
341
|
+
puts
|
342
|
+
end
|
343
|
+
|
344
|
+
def migrate_db
|
345
|
+
puts 'Migrating database...'
|
346
|
+
system("heroku run rake db:migrate --remote #{target.name}")
|
347
|
+
puts
|
348
|
+
end
|
349
|
+
|
350
|
+
def restart
|
351
|
+
puts 'Restarting...'
|
352
|
+
system("heroku restart --remote #{target.name}")
|
353
|
+
puts
|
354
|
+
end
|
355
|
+
|
356
|
+
def enable_pinging
|
357
|
+
puts 'Enabling availability pinger...'
|
358
|
+
system("curl #{target.enable_pinger_url}")
|
359
|
+
puts
|
360
|
+
end
|
361
|
+
|
362
|
+
def disable_maintenance
|
363
|
+
puts 'Disabling maintenance mode...'
|
364
|
+
system("heroku maintenance:off --remote #{target.name}")
|
365
|
+
puts
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module HR_Deploy
|
2
|
+
|
3
|
+
class Target
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :description
|
7
|
+
attr_reader :disable_pinger_url
|
8
|
+
attr_reader :enable_pinger_url
|
9
|
+
|
10
|
+
def backup_db?
|
11
|
+
!!@backup_db
|
12
|
+
end
|
13
|
+
|
14
|
+
def default?
|
15
|
+
!!@default
|
16
|
+
end
|
17
|
+
|
18
|
+
def requires_curl?
|
19
|
+
!!disable_pinger_url
|
20
|
+
end
|
21
|
+
|
22
|
+
def requires_pinging_interaction?
|
23
|
+
# If URL to disable pinging is set, we want to disable and enable pinging when deploying
|
24
|
+
disable_pinger_url
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(raw_target)
|
28
|
+
target_name = raw_target.keys.first
|
29
|
+
@name = target_name.to_s
|
30
|
+
|
31
|
+
options = raw_target[target_name]
|
32
|
+
# Target might have no options
|
33
|
+
if options
|
34
|
+
options.each do |option, value|
|
35
|
+
case option
|
36
|
+
when :description
|
37
|
+
@description = value
|
38
|
+
when :new_relic_disable_pinger_url
|
39
|
+
@disable_pinger_url = value
|
40
|
+
when :new_relic_enable_pinger_url
|
41
|
+
@enable_pinger_url = value
|
42
|
+
when :backup_db
|
43
|
+
@backup_db = value
|
44
|
+
when :default
|
45
|
+
@default = value
|
46
|
+
else
|
47
|
+
raise "Unknown option: #{option}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@description = name unless description
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hr_deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dmitry Gubitskiy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Easily deploy your Rails apps to Heroku with one command, automatically
|
14
|
+
taking care of things such as checking current Heroku status, enabling maintenance
|
15
|
+
mode, creating database backups, etc. Multiple deployment targets are supported
|
16
|
+
email: d.gubitskiy@gmail.com
|
17
|
+
executables:
|
18
|
+
- hr_deploy
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/hr_deploy/deployer.rb
|
23
|
+
- lib/hr_deploy/config_handler.rb
|
24
|
+
- lib/hr_deploy/target.rb
|
25
|
+
- lib/hr_deploy/version.rb
|
26
|
+
- bin/hr_deploy
|
27
|
+
homepage: https://github.com/enthrops/hr_deploy
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.9.1
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.0.3
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Deploy your Rails apps to Heroku
|
51
|
+
test_files: []
|
52
|
+
has_rdoc:
|