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.
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
+