Skip to content

Commit addc8ef

Browse files
committed
Wizard Development’s deploy Gem
0 parents  commit addc8ef

13 files changed

+413
-0
lines changed

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/.bundle/
2+
/.yardoc
3+
/Gemfile.lock
4+
/_yardoc/
5+
/coverage/
6+
/doc/
7+
/pkg/
8+
/spec/reports/
9+
/tmp/
10+
*.bundle
11+
*.so
12+
*.o
13+
*.a
14+
mkmf.log

Gemfile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in wizarddev-heroku.gemspec
4+
gemspec

LICENSE.txt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Copyright (c) 2015 Francis Gulotta
2+
3+
MIT License
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Wizarddev::Heroku
2+
3+
Deploys rails apps to heroku
4+
5+
## Installation
6+
7+
Add this line to your application's Gemfile:
8+
9+
```ruby
10+
gem 'wizarddev-heroku'
11+
```
12+
13+
And then execute:
14+
15+
$ bundle
16+
17+
Or install it yourself as:
18+
19+
$ gem install wizarddev-heroku
20+
21+
## Usage
22+
From `rake wizarddev:deploy`
23+
24+
```
25+
Deploys the currently checked out revision to Heroku.
26+
Reads the project's app.json file to determine tasks for a target.
27+
Tasks include:
28+
Tag the release and pushes it to github
29+
Deploy the release to Heroku
30+
Execute commands remotely eg 'rake db:migrate'
31+
Restart the app
32+
33+
Uses the ~/.netrc file for authentication per the Heroku toolbelt.
34+
35+
usage: rake wizarddev:deploy TARGET=target_name
36+
usage: rake wizarddev:deploy:{staging|production}
37+
```
38+
39+
## Example app.json
40+
41+
This is very similar and compatible with Heroku's `app.json`.
42+
43+
```json
44+
{
45+
"name": "Our Cool App",
46+
"description": "Great app to use all the time.",
47+
"website": "https://www.ourcoolapp.com",
48+
"heroku-environments": {
49+
"staging": {
50+
"app-name": "ourcoolapp-staging",
51+
"tag-name": false,
52+
"force-push": true,
53+
"scripts": [
54+
{ "cmd": "rake db:migrate", "restart": true }
55+
]
56+
},
57+
"production": {
58+
"app-name": "ourcoolapp-production",
59+
"force-push": false,
60+
"tag-name": "prod",
61+
"scripts": [
62+
{ "cmd": "rake db:migrate", "restart": true, "remote": true },
63+
{ "cmd": "say 'deploy complete'"}
64+
]
65+
}
66+
},
67+
"source-repo": "[email protected]:wizarddevelopment/ourcoolapp.git"
68+
}
69+
```
70+
71+
72+
## Contributing
73+
74+
1. Fork it ( https://github.com/[my-github-username]/wizarddev-heroku/fork )
75+
2. Create your feature branch (`git checkout -b my-new-feature`)
76+
3. Commit your changes (`git commit -am 'Add some feature'`)
77+
4. Push to the branch (`git push origin my-new-feature`)
78+
5. Create a new Pull Request

Rakefile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require "bundler/gem_tasks"
2+

lib/wizarddev.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module Wizarddev
2+
3+
end
4+
require 'wizarddev/heroku'

lib/wizarddev/heroku.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require "wizarddev/heroku/version"
2+
require 'wizarddev/heroku/railtie' if defined?(Rails)
3+
4+
module Wizarddev
5+
module Heroku
6+
def self.load
7+
require 'wizarddev/heroku/platform_client'
8+
require 'wizarddev/heroku/deploy'
9+
end
10+
end
11+
end
12+

lib/wizarddev/heroku/deploy.rb

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
require 'json'
2+
3+
module Wizarddev
4+
module Heroku
5+
class Deploy
6+
attr_reader :target, :heroku
7+
8+
def self.check_auth_config
9+
unless auth = PlatformClient.load_auth
10+
puts "Please make sure you have an up to date version of the heroku toolbelt and it's logged in"
11+
end
12+
13+
unless config = File.exist?('app.json')
14+
puts "Please include an app.json in the root of this project."
15+
puts "`rails g wizarddev:app_json` will make one for you."
16+
end
17+
18+
exit(1) unless auth && config
19+
end
20+
21+
def initialize(target)
22+
@target = target.to_s
23+
@app_config = load_config
24+
@heroku = PlatformClient.local_auth(app_name)
25+
end
26+
27+
def deploy!
28+
force_push? ? force_push : push
29+
tag_deploy
30+
run_scripts
31+
end
32+
33+
def push
34+
puts "Pushing HEAD to Heroku ..."
35+
execute "git push [email protected]:#{app_name}.git HEAD:master"
36+
end
37+
38+
def force_push
39+
puts "Force pushing HEAD to Heroku ..."
40+
execute "git push -f [email protected]:#{app_name}.git HEAD:master"
41+
end
42+
43+
def run_scripts
44+
scripts.each { |script| run_script(script) }
45+
end
46+
47+
def tag_deploy
48+
return unless tag_name
49+
version = heroku.latest_release['version']
50+
release_name = "#{tag_name}/v#{version}"
51+
puts "Tagging release with #{release_name}"
52+
execute "git tag -a #{release_name} -m 'Tagged release'"
53+
execute "git push #{source_repo} #{release_name}"
54+
end
55+
56+
def execute_remote(cmd)
57+
print "Executing '#{cmd}' on #{app_name}\n"
58+
output, code = heroku.run_with_code(cmd)
59+
puts output.gsub(/^/, "#\t")
60+
return true if code == 0
61+
puts "Failed to Execute #{cmd} with code #{code}"
62+
exit(1)
63+
end
64+
65+
def execute(cmd)
66+
print "Executing '#{cmd}'\n"
67+
success = system(cmd)
68+
return true if success
69+
code = $CHILD_STATUS.to_i
70+
puts "Failed to Execute #{cmd} with code #{code}"
71+
exit(1)
72+
end
73+
74+
private
75+
76+
def run_script(script)
77+
if script["remote"]
78+
execute_remote script["cmd"]
79+
else
80+
execute script["cmd"]
81+
end
82+
restart if script["restart"]
83+
end
84+
85+
def restart
86+
puts "Restarting #{app_name}"
87+
heroku.restart_all
88+
end
89+
90+
def load_config
91+
JSON.parse(File.read('app.json'))
92+
end
93+
94+
def source_repo
95+
@app_config["source-repo"]
96+
end
97+
98+
def tag_name
99+
config["tag-name"]
100+
end
101+
102+
def force_push?
103+
!!config["force-push"]
104+
end
105+
106+
def app_name
107+
config["app-name"]
108+
end
109+
110+
def scripts
111+
config["scripts"]
112+
end
113+
114+
def config
115+
c = @app_config["heroku-environments"][target]
116+
raise "No configuration for #{target} in app.js" unless c
117+
c
118+
end
119+
120+
end
121+
end
122+
end
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'httparty'
2+
require 'rendezvous'
3+
require 'netrc'
4+
5+
module Wizarddev
6+
module Heroku
7+
class PlatformClient
8+
include HTTParty
9+
attr_reader :app
10+
base_uri 'https://api.heroku.com'
11+
12+
def self.local_auth(app)
13+
new(load_auth.password, app)
14+
end
15+
16+
def self.load_auth
17+
auth = Netrc.read['api.heroku.com']
18+
end
19+
20+
def initialize(oauth_key, app)
21+
@app = app
22+
self.class.headers(
23+
'Accept' => 'application/vnd.heroku+json; version=3',
24+
'Authorization' => "Bearer #{oauth_key}"
25+
)
26+
end
27+
28+
def latest_release
29+
resp = self.class.get(
30+
"/apps/#{app}/releases",
31+
headers: {
32+
'Range' => 'version ..; order=desc, max=1;'
33+
}
34+
)
35+
raise "Error fetching latest release: #{response.body}" unless resp.success?
36+
resp.first
37+
end
38+
39+
def restart_all
40+
self.class.delete("/apps/#{app}/dynos")
41+
end
42+
43+
def run(cmd)
44+
cmdstr = { attach: 'true', command: cmd }.to_json
45+
opt = {
46+
body: cmdstr,
47+
headers: { 'Content-Type' => 'application/json' }
48+
}
49+
response = self.class.post("/apps/#{app}/dynos", opt)
50+
session = Rendezvous.new(
51+
input: StringIO.new,
52+
output: StringIO.new,
53+
url: response['attach_url']
54+
)
55+
session.start
56+
session.output.rewind
57+
session.output.read
58+
end
59+
60+
def run_with_code(cmd)
61+
cmd = "#{cmd}; echo $?"
62+
*output, code = run(cmd).split("\n")
63+
[output.join("\n"), code.to_i]
64+
end
65+
end
66+
end
67+
end

lib/wizarddev/heroku/railtie.rb

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Wizarddev
2+
module Heroku
3+
class Railtie < Rails::Railtie
4+
generators do
5+
# require 'wizarddev/heroku/generators/app_json'
6+
end
7+
8+
rake_tasks do
9+
require 'wizarddev/heroku/tasks/deploy'
10+
end
11+
end
12+
end
13+
end

lib/wizarddev/heroku/tasks/deploy.rb

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace :wizarddev do
2+
task :deploy do
3+
if ENV['TARGET']
4+
Wizarddev::Heroku.load
5+
Wizarddev::Heroku::Deploy.new(ENV['TARGET']).deploy!
6+
else
7+
Rake::Task["wizarddev:deploy:help"].invoke
8+
end
9+
end
10+
11+
namespace :deploy do
12+
desc 'Deploy script usage information'
13+
task :help do
14+
puts 'Deploys the currently checked out revision to heroku.'
15+
puts 'Reads the project\'s app.json file to determine tasks for a target.'
16+
puts 'Tasks include:'
17+
puts "\t Tag the release and pushes it to github"
18+
puts "\t Deploy the release to Heroku"
19+
puts "\t Execute commands remotely eg 'rake db:migrate'"
20+
puts "\t Restart the app"
21+
puts "\nUses the ~/.netrc file for authentication per the heroku toolbelt."
22+
puts "\nusage: rake wizarddev:deploy TARGET=target_name"
23+
puts "usage: rake wizarddev:deploy:{staging|production}"
24+
25+
Wizarddev::Heroku.load
26+
Wizarddev::Heroku::Deploy.check_auth_config
27+
exit 0
28+
end
29+
30+
desc 'Deploy to Production'
31+
task :production do
32+
Wizarddev::Heroku.load
33+
Wizarddev::Heroku::Deploy.new(:production).deploy!
34+
end
35+
36+
desc 'Deploy to Staging'
37+
task :staging do
38+
Wizarddev::Heroku.load
39+
Wizarddev::Heroku::Deploy.new(:staging).deploy!
40+
end
41+
42+
end
43+
end
44+

lib/wizarddev/heroku/version.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Wizarddev
2+
module Heroku
3+
VERSION = "0.0.1"
4+
end
5+
end

0 commit comments

Comments
 (0)