crapapult 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/.document +5 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +7 -0
  4. data/Rakefile +19 -0
  5. data/lib/crapapult.rb +290 -0
  6. metadata +119 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -0,0 +1,7 @@
1
+ # crapapult
2
+
3
+ Oh we ain't ready for primetime yet.
4
+
5
+ --------------------------------------------------------------------------------
6
+ Copyright (c) 2011 Yammer, Inc. See LICENSE.txt for further details.
7
+
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
+