release_party 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/release_party.rb +1 -0
- data/lib/release_party/environment.rb +57 -0
- data/lib/release_party/release_file.rb +22 -0
- data/lib/release_party/release_party.rb +202 -0
- metadata +163 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'release_party/release_party'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'release_file')
|
2
|
+
|
3
|
+
class Environment
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@attributes = {}
|
7
|
+
load_defaults
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method_id, *args, &block)
|
11
|
+
case method_id.to_s
|
12
|
+
when /\A(.*)=\Z/
|
13
|
+
@attributes[$1.to_sym] = args.first
|
14
|
+
|
15
|
+
when /\A(.*)\?\Z/
|
16
|
+
@attributes.key?($1.to_sym)
|
17
|
+
|
18
|
+
else
|
19
|
+
unless @attributes.key?(method_id)
|
20
|
+
raise ArgumentError, "No value for #{method_id} defined"
|
21
|
+
end
|
22
|
+
value = @attributes[method_id.to_sym]
|
23
|
+
return value.call if value.is_a?(Proc)
|
24
|
+
value
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_release_file
|
30
|
+
# Process the Releasefile and merge the attributes loaded
|
31
|
+
release_file = ReleaseFile.new
|
32
|
+
@attributes = @attributes.merge release_file.attributes
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_capistrano_defaults(cap_config)
|
36
|
+
# General cap info
|
37
|
+
self.user = cap_config.fetch(:user, `git config user.name`.chomp)
|
38
|
+
self.branch = cap_config.fetch(:branch, 'master')
|
39
|
+
self.stage = cap_config.fetch(:stage, 'staging')
|
40
|
+
self.repository = cap_config.fetch(:repository, nil)
|
41
|
+
self.domain = cap_config.fetch(:domain, nil)
|
42
|
+
self.display_name = cap_config.fetch(:display_name, 'Release Party')
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_defaults
|
46
|
+
self.from_address = 'Release Party <releaseparty@noreply.org>'
|
47
|
+
self.smtp_address = 'localhost'
|
48
|
+
self.smtp_port = 25
|
49
|
+
self.subject = Proc.new {"A release of #{display_name} was released to #{domain}"}
|
50
|
+
self.finished_stories = []
|
51
|
+
self.known_bugs = []
|
52
|
+
self.send_email = true
|
53
|
+
self.template_engine = :haml
|
54
|
+
self.deliver_stories = false
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ReleaseFile
|
2
|
+
|
3
|
+
RELEASE_FILE = 'Releasefile'
|
4
|
+
|
5
|
+
attr_reader :attributes
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@attributes = {}
|
9
|
+
|
10
|
+
capfile_path = File.join(Dir.pwd, RELEASE_FILE)
|
11
|
+
if File.exists?(capfile_path)
|
12
|
+
eval File.read(capfile_path)
|
13
|
+
else
|
14
|
+
puts "Warning: No #{RELEASE_FILE} found, you should put one in your project root."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method_id, *args, &block)
|
19
|
+
@attributes[method_id.to_sym] = args.first || block
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'capistrano'
|
2
|
+
require 'pivotal-tracker'
|
3
|
+
require 'mail'
|
4
|
+
require 'grit'
|
5
|
+
|
6
|
+
require File.join(File.dirname(__FILE__), 'environment')
|
7
|
+
|
8
|
+
module Capistrano::ReleaseParty
|
9
|
+
|
10
|
+
def self.extended(configuration)
|
11
|
+
configuration.load do
|
12
|
+
|
13
|
+
after 'deploy', 'release_party:finished'
|
14
|
+
|
15
|
+
namespace 'release_party' do
|
16
|
+
task 'started' do
|
17
|
+
@env = Environment.new
|
18
|
+
|
19
|
+
# Record when the release began
|
20
|
+
@env.release_started = Time.now
|
21
|
+
|
22
|
+
@env.load_capistrano_defaults(self)
|
23
|
+
@env.load_release_file
|
24
|
+
|
25
|
+
announce "beginning deployment, project details obtained."
|
26
|
+
|
27
|
+
load_git_progress @env
|
28
|
+
load_tracker_progress @env
|
29
|
+
end
|
30
|
+
|
31
|
+
task 'finished' do
|
32
|
+
raise ArgumentError, "Release finished without being started" if @env.nil?
|
33
|
+
|
34
|
+
begin
|
35
|
+
announce "celebrating release, deployment finished successfully!"
|
36
|
+
|
37
|
+
# Record when the release finished
|
38
|
+
@env.release_finished = Time.now
|
39
|
+
|
40
|
+
|
41
|
+
# Do pivotal story delivery
|
42
|
+
if @env.deliver_stories
|
43
|
+
commit_tracker_progress @env
|
44
|
+
end
|
45
|
+
|
46
|
+
# Send an email notification
|
47
|
+
if @env.send_email
|
48
|
+
body = load_email_content @env
|
49
|
+
deliver_notification(@env, body)
|
50
|
+
end
|
51
|
+
|
52
|
+
#rescue ArgumentError => error
|
53
|
+
# puts "ERROR: #{error.message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def commit_tracker_progress(env)
|
61
|
+
# Go through each finished story id we've seen in the git repo
|
62
|
+
# and deliver each story that is marked as finished
|
63
|
+
env.finished_store_ids.each do |story_id|
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_git_progress(env)
|
69
|
+
#repo = Grit::Repo.new(Dir.pwd)
|
70
|
+
repo = Grit::Repo.new("/Users/john/Projects/breeze")
|
71
|
+
config = Grit::Config.new(repo)
|
72
|
+
|
73
|
+
feature_branch = config.fetch('gitflow.prefix.feature', 'feature/')
|
74
|
+
release_branch = config.fetch('gitflow.prefix.release', 'release/')
|
75
|
+
puts "Feature branch: #{feature_branch}"
|
76
|
+
puts "Release branch: #{release_branch}"
|
77
|
+
|
78
|
+
# Find the last release
|
79
|
+
latest_release = \
|
80
|
+
repo.commits('master').find do |commit|
|
81
|
+
commit.message =~ /\AMerge branch '#{release_branch}(\d+)/
|
82
|
+
end
|
83
|
+
latest_release_tag = $1
|
84
|
+
|
85
|
+
puts "Commit: #{latest_release} #{latest_release.message}"
|
86
|
+
previous_release = \
|
87
|
+
repo.commits('master', 100).find do |commit|
|
88
|
+
if commit.message =~ /\AMerge branch '#{release_branch}(\d+)/
|
89
|
+
latest_release_tag != $1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
previous_release_tag = $1
|
93
|
+
puts "Commit: #{previous_release} #{previous_release.message}"
|
94
|
+
|
95
|
+
env.finished_story_ids =
|
96
|
+
repo.commits('develop').collect do |commit|
|
97
|
+
$1 if commit.message =~ /\AMerge branch '#{feature_branch}(\d+)/
|
98
|
+
end.compact
|
99
|
+
puts env.finished_story_ids
|
100
|
+
end
|
101
|
+
|
102
|
+
def load_email_content(env)
|
103
|
+
arguments_required env, :template_engine, :template
|
104
|
+
|
105
|
+
template = env.template
|
106
|
+
raise ArgumentError, "Missing template file #{template}" unless File.exists?(template)
|
107
|
+
|
108
|
+
engine = \
|
109
|
+
case env.template_engine
|
110
|
+
when :haml
|
111
|
+
load_haml
|
112
|
+
Haml::Engine.new(File.read(template))
|
113
|
+
|
114
|
+
when :erb
|
115
|
+
load_erb
|
116
|
+
ERB.new(File.read(template))
|
117
|
+
|
118
|
+
else
|
119
|
+
raise ArgumentError, "Unsupported template engine #{env.template_engine}"
|
120
|
+
end
|
121
|
+
|
122
|
+
engine.render env
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_tracker_progress(env)
|
126
|
+
arguments_required env, :project_id, :project_api_key
|
127
|
+
|
128
|
+
PivotalTracker::Client.token = env.project_api_key
|
129
|
+
|
130
|
+
begin
|
131
|
+
env.project = PivotalTracker::Project.find(env.project_id)
|
132
|
+
rescue RestClient::Request::Unauthorized => e
|
133
|
+
puts 'Unable to authenticate with pivotal, perhaps your API key is wrong.' +
|
134
|
+
"Message: #{e.message}"
|
135
|
+
end
|
136
|
+
|
137
|
+
env.instance_eval do
|
138
|
+
self.finished_stories = \
|
139
|
+
project.stories.all(:state => 'finished')
|
140
|
+
self.known_bugs = \
|
141
|
+
project.stories.all(:state => ['unstarted', 'started'], :story_type => 'bug')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def deliver_notification(env, body)
|
146
|
+
arguments_required env,
|
147
|
+
:smtp_address, :smtp_port, :smtp_domain, :from_address,
|
148
|
+
:email_notification_to, :subject
|
149
|
+
|
150
|
+
Mail.defaults do
|
151
|
+
delivery_method :smtp,
|
152
|
+
:address => env.smtp_address,
|
153
|
+
:port => env.smtp_port,
|
154
|
+
:domain => env.smtp_domain
|
155
|
+
end
|
156
|
+
announce "delivering deployment notice to #{env.email_notification_to.inspect}"
|
157
|
+
Mail.deliver do
|
158
|
+
from env.from_address
|
159
|
+
to env.email_notification_to
|
160
|
+
subject env.subject
|
161
|
+
html_part do
|
162
|
+
content_type 'text/html; charset=UTF-8'
|
163
|
+
body body
|
164
|
+
end
|
165
|
+
end
|
166
|
+
announce "deployment notice sent!"
|
167
|
+
end
|
168
|
+
|
169
|
+
def announce(message)
|
170
|
+
puts "ReleaseParty: #{message}"
|
171
|
+
end
|
172
|
+
|
173
|
+
def arguments_required(env, *arguments)
|
174
|
+
missing = arguments.reject do |argument|
|
175
|
+
env.send("#{argument}?".to_sym)
|
176
|
+
end
|
177
|
+
unless missing.empty?
|
178
|
+
raise ArgumentError, "Parameters #{missing.join(', ')} must be defined"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def load_haml
|
183
|
+
require 'haml'
|
184
|
+
rescue LoadError => error
|
185
|
+
raise ArgumentError,
|
186
|
+
'Unable to load HAML, you need to make sure the haml gem is installed '
|
187
|
+
'if it is specified as the template engine'
|
188
|
+
end
|
189
|
+
|
190
|
+
def load_erb
|
191
|
+
require 'erb'
|
192
|
+
rescue LoadError => error
|
193
|
+
raise ArgumentError,
|
194
|
+
'Unable to load ERB, you need to make sure the erb gem is installed '
|
195
|
+
'if it is specified as the template engine'
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
if Capistrano::Configuration.instance
|
201
|
+
Capistrano::Configuration.instance.extend(Capistrano::Configuration.instance)
|
202
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: release_party
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- John Barker
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-18 00:00:00 +08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: capistrano
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 6
|
33
|
+
- 0
|
34
|
+
version: 2.6.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: pivotal-tracker
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 23
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 2
|
49
|
+
- 0
|
50
|
+
version: 0.2.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: grit
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 29
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 4
|
65
|
+
- 1
|
66
|
+
version: 2.4.1
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mail
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 11
|
78
|
+
segments:
|
79
|
+
- 2
|
80
|
+
- 1
|
81
|
+
- 0
|
82
|
+
version: 2.1.0
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: capistrano_colors
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
type: :runtime
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: colored
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
type: :runtime
|
112
|
+
version_requirements: *id006
|
113
|
+
description: Perform a number of common post deployment tasks such as delivering pivotal stories, sending out a release email, notifying campfire.
|
114
|
+
email: jebarker@gmail.com
|
115
|
+
executables: []
|
116
|
+
|
117
|
+
extensions: []
|
118
|
+
|
119
|
+
extra_rdoc_files: []
|
120
|
+
|
121
|
+
files:
|
122
|
+
- lib/release_party/environment.rb
|
123
|
+
- lib/release_party/release_file.rb
|
124
|
+
- lib/release_party/release_party.rb
|
125
|
+
- lib/release_party.rb
|
126
|
+
has_rdoc: true
|
127
|
+
homepage:
|
128
|
+
licenses: []
|
129
|
+
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ">"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
hash: 25
|
150
|
+
segments:
|
151
|
+
- 1
|
152
|
+
- 3
|
153
|
+
- 1
|
154
|
+
version: 1.3.1
|
155
|
+
requirements: []
|
156
|
+
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 1.6.2
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: Celebrate releases
|
162
|
+
test_files: []
|
163
|
+
|