crapapult 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.
- data/.document +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +7 -0
- data/Rakefile +19 -0
- data/lib/crapapult.rb +290 -0
- metadata +119 -0
data/.document
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Yammer, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'jeweler'
|
2
|
+
require 'jeweler/tasks'
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = "crapapult"
|
7
|
+
gem.homepage = "http://github.com/yammer/crapapult"
|
8
|
+
gem.license = "MIT"
|
9
|
+
gem.summary = %Q{A damn simple way of deploying simple applications.}
|
10
|
+
gem.description = %Q{Yammer's Capistrano-based deploy crap.}
|
11
|
+
gem.email = "coda.hale@gmail.com"
|
12
|
+
gem.authors = ["Coda Hale"]
|
13
|
+
gem.version = "0.0.1"
|
14
|
+
|
15
|
+
gem.add_runtime_dependency "json", ">= 1.2.4"
|
16
|
+
gem.add_runtime_dependency "capistrano", ">= 2.5.9"
|
17
|
+
gem.add_runtime_dependency "erubis", "2.6.6"
|
18
|
+
end
|
19
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/lib/crapapult.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "capistrano"
|
3
|
+
require "net/http"
|
4
|
+
require "json"
|
5
|
+
require "erubis"
|
6
|
+
|
7
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
8
|
+
|
9
|
+
##############################################################################
|
10
|
+
################################ Initialization ##############################
|
11
|
+
##############################################################################
|
12
|
+
|
13
|
+
logger.level = Capistrano::Logger::IMPORTANT
|
14
|
+
default_run_options[:pty] = true
|
15
|
+
@invoke = tasks.delete(:invoke)
|
16
|
+
@shell = tasks.delete(:shell)
|
17
|
+
|
18
|
+
@branches = []
|
19
|
+
@hosts = {}
|
20
|
+
@data = {}
|
21
|
+
|
22
|
+
##############################################################################
|
23
|
+
###################################### API ###################################
|
24
|
+
##############################################################################
|
25
|
+
|
26
|
+
# Defines a host with an optional host-specific configuration block.
|
27
|
+
def host(name, &block)
|
28
|
+
@hosts[name] = block || lambda {}
|
29
|
+
server name, :app
|
30
|
+
end
|
31
|
+
|
32
|
+
# Defines an environment with a set of allowed branches and an optional
|
33
|
+
# environment-specific configuration block.
|
34
|
+
def environment(name, branches, &block)
|
35
|
+
env_block = block || lambda {}
|
36
|
+
|
37
|
+
for b in branches
|
38
|
+
unless @branches.include?(b)
|
39
|
+
abort "Environment #{name} allows a branch which hasn't been declared: #{b}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :to do
|
44
|
+
desc "Deploy to the #{name} environment"
|
45
|
+
task name.to_sym do
|
46
|
+
set :current_environment, name
|
47
|
+
set :allowed_branches, branches
|
48
|
+
env_block.call
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Defines a branch with a Hudson job name.
|
54
|
+
def branch(name, job)
|
55
|
+
@branches << name.to_sym
|
56
|
+
namespace :from do
|
57
|
+
desc "Deploy the #{name} branch"
|
58
|
+
task name.to_sym do
|
59
|
+
set :current_branch, name
|
60
|
+
set :current_job, job
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines a Hudson server.
|
66
|
+
def hudson(url)
|
67
|
+
if exists?(:current_hudson_url)
|
68
|
+
abort "You're calling #hudson twice. Stop it."
|
69
|
+
end
|
70
|
+
set :current_hudson_url, url
|
71
|
+
end
|
72
|
+
|
73
|
+
# Defines an application with an optional owner, group, and configuration
|
74
|
+
# block.
|
75
|
+
def application(name, owner=name, group=owner, &block)
|
76
|
+
app_block = block || lambda {}
|
77
|
+
set :application_name, name
|
78
|
+
set :owner, owner
|
79
|
+
set :owner_group, group
|
80
|
+
namespace :deploy do
|
81
|
+
task :configure do
|
82
|
+
puts "> Configuring the application"
|
83
|
+
app_block.call
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Defines a piece of configuration data.
|
89
|
+
def data(name, value)
|
90
|
+
@data[name] = value
|
91
|
+
end
|
92
|
+
|
93
|
+
# Renders a template to the given path on each server.
|
94
|
+
def template(template, path, opts={})
|
95
|
+
puts ">> Uploading #{template} to #{path}"
|
96
|
+
opts = { :owner => owner, :group => owner_group, :mode => "0644" }.merge(opts)
|
97
|
+
filename = "#{temp_dir}/#{File.basename(path)}"
|
98
|
+
parallelized(:app) do
|
99
|
+
output = erb(File.read("templates/#{template}"))
|
100
|
+
put output, filename
|
101
|
+
run "#{sudo} cp #{filename} #{path}"
|
102
|
+
run "#{sudo} chmod #{opts[:mode]} #{path}"
|
103
|
+
run "#{sudo} chown -R #{opts[:owner]}:#{opts[:group]} #{path}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Uploads an asset to the given path on each server.
|
108
|
+
def asset(asset, path, opts={})
|
109
|
+
puts ">> Uploading #{asset} to #{path}"
|
110
|
+
opts = { :owner => owner, :group => owner_group, :mode => "0644" }.merge(opts)
|
111
|
+
filename = "#{temp_dir}/#{File.basename(path)}"
|
112
|
+
upload(File.join("assets", asset), filename)
|
113
|
+
run "#{sudo} cp #{filename} #{path}"
|
114
|
+
run "#{sudo} chmod #{opts[:mode]} #{path}"
|
115
|
+
run "#{sudo} chown -R #{opts[:owner]}:#{opts[:group]} #{path}"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Uploads and adds an Upstart configuration file for the application.
|
119
|
+
def upstart(config_file)
|
120
|
+
puts ">> Configuring Upstart for #{application_name}"
|
121
|
+
base = "#{application_name}.conf"
|
122
|
+
filename = "#{temp_dir}/#{base}"
|
123
|
+
upload(File.join("assets", config_file), filename)
|
124
|
+
run "#{sudo} cp #{filename} /etc/init/#{base}"
|
125
|
+
run "#{sudo} ln -sf /lib/init/upstart-job /etc/init.d/#{application_name}"
|
126
|
+
end
|
127
|
+
|
128
|
+
##############################################################################
|
129
|
+
#################################### Tasks ###################################
|
130
|
+
##############################################################################
|
131
|
+
|
132
|
+
namespace :deploy do
|
133
|
+
desc "Deploy the specified branch to the specified environment"
|
134
|
+
task :default do
|
135
|
+
check_required_parameters!
|
136
|
+
|
137
|
+
puts "> Deploying the #{current_branch} branch to #{current_environment}"
|
138
|
+
|
139
|
+
setup
|
140
|
+
stage
|
141
|
+
configure
|
142
|
+
symlink
|
143
|
+
restart
|
144
|
+
cleanup
|
145
|
+
|
146
|
+
puts "> Successfully deployed the #{current_branch} branch to #{current_environment}! Congrats!"
|
147
|
+
end
|
148
|
+
|
149
|
+
task :setup, :role => :app do
|
150
|
+
run "mkdir #{temp_dir}"
|
151
|
+
end
|
152
|
+
|
153
|
+
task :stage, :role => :app do
|
154
|
+
puts "> Downloading the artifact remotely"
|
155
|
+
run "curl --silent #{artifact_url} --output #{temp_dir}/#{artifact_filename}"
|
156
|
+
run "#{sudo} cp #{temp_dir}/#{artifact_filename} #{directory}/#{artifact_filename}"
|
157
|
+
run "#{sudo} chown -R #{owner}:#{owner_group} #{directory}/#{artifact_filename}"
|
158
|
+
end
|
159
|
+
|
160
|
+
task :symlink, :role => :app do
|
161
|
+
puts "> Updating the symlink"
|
162
|
+
run "#{sudo} ln -sf #{directory}/#{artifact_filename} #{directory}/#{application_name}#{File.extname(artifact_filename)}"
|
163
|
+
end
|
164
|
+
|
165
|
+
task :cleanup, :role => :app do
|
166
|
+
puts "> Cleaning up"
|
167
|
+
run "rm -rf #{temp_dir}"
|
168
|
+
end
|
169
|
+
|
170
|
+
task :restart, :role => :app, :max_hosts => 1 do
|
171
|
+
puts "> Stopping #{application_name}"
|
172
|
+
run "#{sudo} stop #{application_name} || true"
|
173
|
+
run "sleep 10"
|
174
|
+
puts "> Starting #{application_name}"
|
175
|
+
run "#{sudo} start #{application_name}"
|
176
|
+
run "sleep 10"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
##############################################################################
|
181
|
+
################################# Internal ###################################
|
182
|
+
##############################################################################
|
183
|
+
|
184
|
+
def erb(text)
|
185
|
+
Erubis::Eruby.new(text).result(@data)
|
186
|
+
end
|
187
|
+
|
188
|
+
def parallelized(role, &block)
|
189
|
+
$parallel_block = block
|
190
|
+
original_data = @data.dup
|
191
|
+
for server in roles[role].servers
|
192
|
+
@data = original_data
|
193
|
+
@data[:host] = server.host
|
194
|
+
@hosts[server.host].call
|
195
|
+
$current_host = server.host
|
196
|
+
execute_task(tasks["execute:parallel"])
|
197
|
+
end
|
198
|
+
@data = original_data
|
199
|
+
end
|
200
|
+
|
201
|
+
namespace :execute do
|
202
|
+
task :parallel, :hosts => lambda { $current_host } do
|
203
|
+
$parallel_block.call
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
set :temp_dir do
|
208
|
+
"/tmp/#{application_name}-#{rand(10_000_000)}"
|
209
|
+
end
|
210
|
+
|
211
|
+
set(:last_build) do
|
212
|
+
build = hudson_api(current_hudson_url + "/job/#{current_job}/lastBuild")
|
213
|
+
unless build["result"] == "SUCCESS"
|
214
|
+
abort("The last build of #{current_job} failed; see #{build["url"]} for details")
|
215
|
+
end
|
216
|
+
|
217
|
+
build
|
218
|
+
end
|
219
|
+
|
220
|
+
set(:last_ref) do
|
221
|
+
last_build["actions"].find { |f| f["lastBuiltRevision"] }["lastBuiltRevision"]["SHA1"]
|
222
|
+
end
|
223
|
+
|
224
|
+
set(:artifact_url) do
|
225
|
+
artifacts = last_build["artifacts"]
|
226
|
+
if artifacts.empty?
|
227
|
+
abort("No artifacts exist for this project!")
|
228
|
+
end
|
229
|
+
artifact = if artifacts.length > 1
|
230
|
+
puts "More than one artifact was found. Please choose:"
|
231
|
+
artifacts.each_with_index do |artifact, index|
|
232
|
+
puts "#{index}: #{artifact["relativePath"]}"
|
233
|
+
end
|
234
|
+
print "? "
|
235
|
+
artifact_index = Capistrano::CLI.ui.ask("choice: ").to_i
|
236
|
+
artifacts[artifact_index]
|
237
|
+
else
|
238
|
+
artifacts.first
|
239
|
+
end
|
240
|
+
last_build["url"] + "artifact/" + artifact["relativePath"]
|
241
|
+
end
|
242
|
+
|
243
|
+
set(:artifact_filename) do
|
244
|
+
filename = artifact_url.split("/").last
|
245
|
+
"#{application_name}-#{Time.now.strftime("%Y%m%d%H%M%S")}-#{last_ref[0..5]}#{File.extname(filename)}"
|
246
|
+
end
|
247
|
+
|
248
|
+
set(:directory) { "/opt/#{application_name}" }
|
249
|
+
|
250
|
+
def hudson_api(url)
|
251
|
+
if url !~ /\/$/
|
252
|
+
url = url + "/api/json"
|
253
|
+
else
|
254
|
+
url = url + "api/json"
|
255
|
+
end
|
256
|
+
|
257
|
+
logger.debug("getting #{url}...")
|
258
|
+
json = Net::HTTP.get(URI.parse(url))
|
259
|
+
# logger.debug("Received: #{json}")
|
260
|
+
JSON.parse(json)
|
261
|
+
end
|
262
|
+
|
263
|
+
def check_required_parameters!
|
264
|
+
unless exists?(:application_name)
|
265
|
+
abort("No application defined.")
|
266
|
+
end
|
267
|
+
|
268
|
+
unless exists?(:current_hudson_url)
|
269
|
+
abort("No Hudson instance defined.")
|
270
|
+
end
|
271
|
+
|
272
|
+
unless exists?(:current_environment)
|
273
|
+
abort("No target environment specified. Please use one of the to:* tasks.")
|
274
|
+
end
|
275
|
+
|
276
|
+
unless exists?(:current_branch)
|
277
|
+
abort("No source branch specified. Please use one of the from:* tasks.")
|
278
|
+
end
|
279
|
+
|
280
|
+
unless allowed_branches.include?(current_branch)
|
281
|
+
abort "The #{current_environment} environment only allows deploys from the following branches: #{allowed_branches.join(", ")}"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
task :trace do
|
286
|
+
logger.level = Capistrano::Logger::TRACE
|
287
|
+
tasks[:invoke] = @invoke
|
288
|
+
tasks[:shell] = @shell
|
289
|
+
end
|
290
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crapapult
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Coda Hale
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-19 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
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
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 4
|
34
|
+
version: 1.2.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: capistrano
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 9
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 5
|
49
|
+
- 9
|
50
|
+
version: 2.5.9
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: erubis
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - "="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 27
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 6
|
65
|
+
- 6
|
66
|
+
version: 2.6.6
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
description: Yammer's Capistrano-based deploy crap.
|
70
|
+
email: coda.hale@gmail.com
|
71
|
+
executables: []
|
72
|
+
|
73
|
+
extensions: []
|
74
|
+
|
75
|
+
extra_rdoc_files:
|
76
|
+
- LICENSE.txt
|
77
|
+
- README.md
|
78
|
+
files:
|
79
|
+
- .document
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/crapapult.rb
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/yammer/crapapult
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
required_rubygems_version: !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
|
+
requirements: []
|
112
|
+
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.3.7
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: A damn simple way of deploying simple applications.
|
118
|
+
test_files: []
|
119
|
+
|