heroku_release 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ doc/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in heroku_deploy.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ heroku_release (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.2)
10
+ mocha (0.9.9)
11
+ rake
12
+ rake (0.8.7)
13
+ rspec (2.0.0)
14
+ rspec-core (= 2.0.0)
15
+ rspec-expectations (= 2.0.0)
16
+ rspec-mocks (= 2.0.0)
17
+ rspec-core (2.0.0)
18
+ rspec-expectations (2.0.0)
19
+ diff-lcs (>= 1.1.2)
20
+ rspec-mocks (2.0.0)
21
+ rspec-core (= 2.0.0)
22
+ rspec-expectations (= 2.0.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ heroku_release!
29
+ mocha (~> 0.9.9)
30
+ rspec (~> 2.0.0)
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = Heroku Deploy
2
+
3
+ Simple RubyGem for tagging and deploying versioned releases of an application to Heroku with the ability to do rollbacks.
4
+
5
+ == Usage
6
+
7
+ If you are using Bundler, add a gem dependency like this:
8
+
9
+ group :development, :test do
10
+ gem 'heroku_deploy', '~> version known to work'
11
+ end
12
+
13
+ Require the Rake tasks in your Rakefile:
14
+
15
+ require 'heroku_deploy/tasks'
16
+
17
+ You have a few configuration options. If you are using a Git remote other than "heroku" then you must configure that. You can choose to have the release version written to a file on deploy so you can check the version on the live server. Here is an example:
18
+
19
+ HerokuDeploy.config.heroku_remote = "production" # git remote for heroku, defaults to "heroku"
20
+ HerokuDeploy.config.version_file_path = "public/version"
21
+
22
+ To deploy the master branch to production, use the heroku_deploy rake task:
23
+
24
+ rake heroku_deploy COMMENT="This is a comment describing what changed since the last release"
25
+
26
+ If the deploy went horribly wrong and you need to do a rollback you can do so:
27
+
28
+ rake heroku_deploy:rollback
29
+
30
+ In order to see the changelog:
31
+
32
+ rake heroku_deploy:log
33
+
34
+ To examine git commits since the last release you can do:
35
+
36
+ rake heroku_deploy:pending
37
+
38
+ == TODO
39
+
40
+ It seems Heroku {is working on}[http://github.com/adamwiggins/heroku-releases] incorporating rollback functionality into the heroku command. Something to look into...
41
+
42
+ == Credit
43
+
44
+ This gem was inspired by
45
+ {this gist}[http://gist.github.com/raw/307543/c762d1cb136588fb76edff907d65bbf80a1e254e/deploy.rake].
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+ Bundler.require
4
+
5
+ require 'rspec/core'
6
+ require 'rspec/core/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new(:default)
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "heroku_release/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "heroku_release"
7
+ s.version = HerokuRelease::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Peter Marklund"]
10
+ s.email = ["peter@marklunds.com"]
11
+ s.homepage = "http://rubygems.org/gems/heroku_release"
12
+ s.summary = %q{Simple RubyGem for tagging and deploying versioned releases of an application to Heroku with the ability to do rollbacks.}
13
+
14
+ s.rubyforge_project = "heroku_release"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec", "~> 2.0.0"
22
+ s.add_development_dependency "mocha", "~> 0.9.9"
23
+ end
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__), "..", "tasks", "*.rake")].each { |rake_file| load rake_file }
@@ -0,0 +1,3 @@
1
+ module HerokuRelease
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,135 @@
1
+ require 'ostruct'
2
+
3
+ module HerokuRelease
4
+ @@config = OpenStruct.new(
5
+ :heroku_remote => "heroku"
6
+ )
7
+
8
+ def self.config
9
+ @@config
10
+ end
11
+
12
+ def self.config=(config)
13
+ @@config = config
14
+ end
15
+
16
+ class Task
17
+ def self.tasks
18
+ {
19
+ :push => nil,
20
+ :tag => nil,
21
+ :log => "Produces a changelog from release tags and their comments. Assumes tag comments have no newlines in them.",
22
+ :current_release => "Show version of current release",
23
+ :previous_release => "Show version of previous release",
24
+ :pending => "Show git commits since last released version",
25
+ :rollback => "Rollback to previous release and remove current release tag"
26
+ }
27
+ end
28
+
29
+ def push
30
+ output 'Deploying site to Heroku ...'
31
+ execute "git push #{config.heroku_remote} master"
32
+ end
33
+
34
+ def tag
35
+ release_name = get_release_name
36
+ commit_version_file(release_name) if config.version_file_path
37
+ comment = ENV['COMMENT'] || 'Tagged release'
38
+ output "Tagging release as '#{release_name}' with comment '#{comment}'"
39
+ execute "git tag -a #{release_name} -m '#{comment}'"
40
+ execute "git push --tags origin"
41
+ execute "git push --tags #{config.heroku_remote}"
42
+ end
43
+
44
+ def log
45
+ change_log = git_tags_with_comments.scan(/^\s*(release-\d+-\d+)\s*(.+)$/).reverse.map do |release, comment|
46
+ "- #{release}\n\n#{comment}\n\n"
47
+ end.join
48
+ output "\n" + change_log
49
+ end
50
+
51
+ def current_release
52
+ output current_release_version
53
+ end
54
+
55
+ def previous_release
56
+ if previous_release_version
57
+ output previous_release_version
58
+ else
59
+ output "no previous release found"
60
+ end
61
+ end
62
+
63
+ def pending
64
+ execute "git log #{current_release_version}..HEAD"
65
+ end
66
+
67
+ def rollback
68
+ # Store releases in local variables so they don't change during tag deletion
69
+ current = current_release_version
70
+ previous = previous_release_version
71
+ if previous
72
+ output "Rolling back to '#{previous}' ..."
73
+ execute "git push -f #{config.heroku_remote} #{previous}:master"
74
+ output "Deleting rollbacked release '#{current}' ..."
75
+ execute "git tag -d #{current}"
76
+ execute "git push #{config.heroku_remote} :refs/tags/#{current}"
77
+ execute "git push origin :refs/tags/#{current}"
78
+ output 'Rollback completed'
79
+ else
80
+ output "No release tags found - cannot do rollback"
81
+ output releases
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def output(message)
88
+ puts message
89
+ end
90
+
91
+ def execute(command)
92
+ output `#{command}`.strip
93
+ end
94
+
95
+ def releases
96
+ @releases ||= git_tags.split("\n").select { |t| t[0..7] == 'release-' }.sort
97
+ end
98
+
99
+ def current_release_version
100
+ releases.last
101
+ end
102
+
103
+ def previous_release_version
104
+ if releases.length >= 2 && previous = releases[-2]
105
+ previous
106
+ else
107
+ nil
108
+ end
109
+ end
110
+
111
+ def commit_version_file(release_name)
112
+ output "Committing version file for release #{release_name}"
113
+ File.open(config.version_file_path, "w") { |f| f.print release_name }
114
+ execute "git add #{config.version_file_path}"
115
+ execute "git commit -m 'Updated version file to #{release_name}'"
116
+ execute "git push origin master"
117
+ end
118
+
119
+ def get_release_name
120
+ "release-#{Time.now.utc.strftime("%Y%m%d-%H%M%S")}"
121
+ end
122
+
123
+ def git_tags
124
+ `git tag`
125
+ end
126
+
127
+ def git_tags_with_comments
128
+ `git tag -n`
129
+ end
130
+
131
+ def config
132
+ HerokuRelease.config
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,11 @@
1
+ desc "Tag and push master branch to heroku"
2
+ task :heroku_release => ['heroku_release:tag', 'heroku_release:push']
3
+
4
+ namespace :heroku_release do
5
+ HerokuRelease::Task.tasks.each do |task_name, description|
6
+ desc(description) if description
7
+ task task_name do
8
+ HerokuRelease::Task.new.send(task_name)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe HerokuRelease do
4
+ before(:each) do
5
+ @config_before = HerokuRelease.config.dup
6
+ end
7
+
8
+ after(:each) do
9
+ HerokuRelease.config = @config_before
10
+ end
11
+
12
+ describe "config" do
13
+ it "defaults heroku_remote to heroku" do
14
+ config.heroku_remote.should == "heroku"
15
+ end
16
+
17
+ it "can set heroku_remote" do
18
+ config.heroku_remote = "production"
19
+ config.heroku_remote.should == "production"
20
+ end
21
+
22
+ it "defaults version_file_path to nil" do
23
+ config.version_file_path.should be_nil
24
+ end
25
+
26
+ it "can set version_file_path" do
27
+ config.version_file_path = "public/system/version"
28
+ config.version_file_path.should == "public/system/version"
29
+ end
30
+ end
31
+
32
+ def config
33
+ HerokuRelease.config
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler.require(:development)
3
+
4
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
5
+ require 'heroku_release'
6
+
7
+ RSpec.configure do |config|
8
+ config.mock_with :mocha
9
+ end
data/spec/task_spec.rb ADDED
@@ -0,0 +1,208 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe HerokuRelease::Task do
5
+ before(:each) do
6
+ @task = HerokuRelease::Task.new
7
+ @config = HerokuRelease.config
8
+ @config_before = HerokuRelease.config.dup
9
+ end
10
+
11
+ after(:each) do
12
+ HerokuRelease.config = @config_before
13
+ end
14
+
15
+ describe "tasks" do
16
+ it "is a Hash" do
17
+ HerokuRelease::Task.tasks.is_a?(Hash).should be_true
18
+ end
19
+
20
+ it "corresponds to public instance methods" do
21
+ HerokuRelease::Task.tasks.keys.map(&:to_s).sort.should == task_instance_methods
22
+ end
23
+
24
+ def task_instance_methods
25
+ HerokuRelease::Task.public_instance_methods(false).map(&:to_s).sort
26
+ end
27
+ end
28
+
29
+ describe "push" do
30
+ it "should execute a git push to heroku remote" do
31
+ @task.expects(:execute).with("git push #{@config.heroku_remote} master")
32
+ @task.push
33
+ end
34
+ end
35
+
36
+ describe "tag" do
37
+ it "create git tag with release name and comment and pushes it to origin and to heroku" do
38
+ ENV['COMMENT'] = "the comment"
39
+ @task.expects(:get_release_name).returns("release-123")
40
+ @task.expects(:commit_version_file).never
41
+ git = sequence('git')
42
+ @task.expects(:execute).with("git tag -a release-123 -m 'the comment'").in_sequence(git)
43
+ @task.expects(:execute).with("git push --tags origin").in_sequence(git)
44
+ @task.expects(:execute).with("git push --tags #{@config.heroku_remote}").in_sequence(git)
45
+
46
+ @task.tag
47
+ end
48
+
49
+ it "commits version file if version_file_path is set" do
50
+ ENV['COMMENT'] = nil
51
+ @config.version_file_path = "public/version"
52
+ @task.expects(:get_release_name).returns("release-123")
53
+ @task.expects(:commit_version_file).with("release-123")
54
+ git = sequence('git')
55
+ @task.expects(:execute).with("git tag -a release-123 -m 'Tagged release'").in_sequence(git)
56
+ @task.expects(:execute).with("git push --tags origin").in_sequence(git)
57
+ @task.expects(:execute).with("git push --tags #{@config.heroku_remote}").in_sequence(git)
58
+
59
+ @task.tag
60
+ end
61
+ end
62
+
63
+ describe "log" do
64
+ it "outputs a changelog from the git tags and their comments" do
65
+ git_tags = <<-END
66
+ release-20101026-143729 Initial release
67
+ release-20101027-225442 Some improvements
68
+ release-20101029-085937 Major new feature
69
+ END
70
+ @task.expects(:git_tags_with_comments).returns(git_tags)
71
+ @task.expects(:output).with(<<-END
72
+
73
+ - release-20101029-085937
74
+
75
+ Major new feature
76
+
77
+ - release-20101027-225442
78
+
79
+ Some improvements
80
+
81
+ - release-20101026-143729
82
+
83
+ Initial release
84
+
85
+ END
86
+ )
87
+
88
+ @task.log
89
+ end
90
+ end
91
+
92
+ describe "current_release" do
93
+ it "should output latest release" do
94
+ @task.expects(:releases).returns(%w(release-20100922-115151 release-20100922-122313))
95
+ @task.expects(:output).with("release-20100922-122313")
96
+ @task.current_release
97
+ end
98
+ end
99
+
100
+ describe "previous_release" do
101
+ it "outputs previous release if there is one" do
102
+ @task.stubs(:releases).returns(%w(release-20100922-115151 release-20100922-122313))
103
+ @task.expects(:output).with("release-20100922-115151")
104
+ @task.previous_release
105
+ end
106
+
107
+ it "says there is no previous release if there is none" do
108
+ @task.stubs(:releases).returns(%w(release-20100922-122313))
109
+ @task.expects(:output).with("no previous release found")
110
+ @task.previous_release
111
+ end
112
+ end
113
+
114
+ describe "pending" do
115
+ it "executes a git log between current release and HEAD" do
116
+ @task.expects(:current_release_version).returns("release-123")
117
+ @task.expects(:execute).with("git log release-123..HEAD")
118
+ @task.pending
119
+ end
120
+ end
121
+
122
+ describe "rollback" do
123
+ it "pushes previous release tag to heroku and deletes current release tag" do
124
+ git = sequence('git')
125
+ @task.expects(:previous_release_version).returns("release-previous")
126
+ @task.expects(:current_release_version).returns("release-current")
127
+ @task.expects(:execute).with("git push -f #{@config.heroku_remote} release-previous:master").in_sequence(git)
128
+ @task.expects(:execute).with("git tag -d release-current").in_sequence(git)
129
+ @task.expects(:execute).with("git push #{@config.heroku_remote} :refs/tags/release-current").in_sequence(git)
130
+ @task.expects(:execute).with("git push origin :refs/tags/release-current").in_sequence(git)
131
+
132
+ @task.rollback
133
+ end
134
+ end
135
+
136
+ describe "output" do
137
+ it "invokes puts with message" do
138
+ @task.expects(:puts).with("a message")
139
+ @task.send(:output, "a message")
140
+ end
141
+ end
142
+
143
+ describe "execute" do
144
+ it "executes a command and outputs the result" do
145
+ @task.expects(:output).with("foobar")
146
+ @task.send(:execute, "echo foobar")
147
+ end
148
+
149
+ it "outputs empty line if there is no output" do
150
+ @task.expects(:output).with("")
151
+ @task.send(:execute, "echo")
152
+ end
153
+ end
154
+
155
+ describe "get_release_name" do
156
+ it "generates a timestamped tag name" do
157
+ @task.send(:get_release_name).should =~ /release-\d\d\d\d\d\d\d\d-\d\d\d\d\d\d/
158
+ end
159
+ end
160
+
161
+ describe "commit_version_file(release_name)" do
162
+ before(:each) do
163
+ @path = "spec/version"
164
+ end
165
+
166
+ after(:each) do
167
+ FileUtils.rm_f(@path)
168
+ end
169
+
170
+ it "writes release_name to version_file_path and commits that file" do
171
+ git = sequence('git')
172
+ @task.expects(:execute).with("git add #{@path}").in_sequence(git)
173
+ @task.expects(:execute).with("git commit -m 'Updated version file to release-123'").in_sequence(git)
174
+ @task.expects(:execute).with("git push origin master").in_sequence(git)
175
+
176
+ HerokuRelease.config.version_file_path = @path
177
+ @task.send(:commit_version_file, "release-123")
178
+
179
+ File.read(@path).should == "release-123"
180
+ end
181
+ end
182
+
183
+ describe "git_tags_with_comments" do
184
+ it "works" do
185
+ @task.send(:git_tags_with_comments)
186
+ end
187
+ end
188
+
189
+ describe "releases" do
190
+ it "parses out list of releases from git tags" do
191
+ @task.expects(:git_tags).returns(<<-END
192
+ release-20100922-115151
193
+ release-20100922-122313
194
+ foobar
195
+ release-20100926-173016
196
+ END
197
+ )
198
+
199
+ @task.send(:releases).should == %w(release-20100922-115151 release-20100922-122313 release-20100926-173016)
200
+ end
201
+ end
202
+
203
+ describe "git_tags" do
204
+ it "works" do
205
+ @task.send(:git_tags)
206
+ end
207
+ end
208
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heroku_release
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Peter Marklund
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-03 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 0
31
+ version: 2.0.0
32
+ type: :development
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ - 9
45
+ - 9
46
+ version: 0.9.9
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ description:
51
+ email:
52
+ - peter@marklunds.com
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - README.rdoc
64
+ - Rakefile
65
+ - heroku_release.gemspec
66
+ - lib/heroku_release.rb
67
+ - lib/heroku_release/tasks.rb
68
+ - lib/heroku_release/version.rb
69
+ - lib/tasks/heroku_release.rake
70
+ - spec/heroku_release_spec.rb
71
+ - spec/spec_helper.rb
72
+ - spec/task_spec.rb
73
+ has_rdoc: true
74
+ homepage: http://rubygems.org/gems/heroku_release
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options: []
79
+
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 746099489
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 746099489
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project: heroku_release
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Simple RubyGem for tagging and deploying versioned releases of an application to Heroku with the ability to do rollbacks.
107
+ test_files:
108
+ - spec/heroku_release_spec.rb
109
+ - spec/spec_helper.rb
110
+ - spec/task_spec.rb