crapapult 0.0.8 → 0.0.9
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/README.md +31 -39
- data/Rakefile +5 -2
- data/VERSION +1 -1
- data/lib/crapapult.rb +123 -98
- metadata +50 -8
data/README.md
CHANGED
@@ -5,25 +5,22 @@ services at [Yammer](http://www.yammer.com).
|
|
5
5
|
|
6
6
|
## What the hell is going on here
|
7
7
|
|
8
|
-
At Yammer, we use
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
Once the artifact is staged (by default in `/opt/APPNAME`), it then uploads the
|
25
|
-
specified set of asset files to each server. After that, it renders and uploads
|
26
|
-
the specified set of template files to each server.
|
8
|
+
At Yammer, we use store all our deployable artifacts in a [Maven](http://maven.apache.org)
|
9
|
+
repository. Each time we commit to a service, Jenkins runs all the tests and (if they pass,
|
10
|
+
naturally) builds a fat, deployable JAR containing all the various dependencies which then gets
|
11
|
+
deployed to our [Nexus](http://nexus.sonatype.org) repository.
|
12
|
+
|
13
|
+
Crapapult allows us to deploy new versions of our services *and* synchronize those deploys with
|
14
|
+
changes to application-specific configuration. (We use Puppet to provision and maintain machines
|
15
|
+
with broad roles but use Crapapult to generate all the application-specific configuration.)
|
16
|
+
|
17
|
+
It does so by using Capistrano to SSH into each of the application servers and using `curl` to
|
18
|
+
download the specified artifact from the Maven repo. This tends to be super-fast, especially if your
|
19
|
+
Maven repo is located in the same data center as your app servers.
|
20
|
+
|
21
|
+
Once the artifact is staged (by default in `/opt/APPNAME`), it then uploads the specified set of
|
22
|
+
asset files to each server. After that, it renders and uploads the specified set of template files
|
23
|
+
to each server.
|
27
24
|
|
28
25
|
Then it restarts your service and gives you a high-five.
|
29
26
|
|
@@ -43,6 +40,11 @@ And start slingin' some crap in your `Capfile`:
|
|
43
40
|
```ruby
|
44
41
|
require "crapapult"
|
45
42
|
|
43
|
+
# Tell us where and what your application is.
|
44
|
+
maven "http://maven.example.com/repo/"
|
45
|
+
group_id "com.example.myapp"
|
46
|
+
artifact_id "myapp-service"
|
47
|
+
|
46
48
|
# Give your application a name.
|
47
49
|
application "myapp" do
|
48
50
|
# Upload the file in assets/myapp.jvm.conf to each host.
|
@@ -56,15 +58,8 @@ application "myapp" do
|
|
56
58
|
upstart "myapp.upstart"
|
57
59
|
end
|
58
60
|
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
# Specify the various branches and their respective build jobs.
|
63
|
-
branch :master, "myapp-release"
|
64
|
-
branch :development, "myapp-development"
|
65
|
-
|
66
|
-
# Define environments with allowed branches.
|
67
|
-
environment :staging, [:master, :development] do
|
61
|
+
# Define environments, optionally allowing snapshots.
|
62
|
+
environment :staging, :allow_snapshots => true do
|
68
63
|
# Set environment-specific data for your templates to work with.
|
69
64
|
data :jdbc_url, "jdbc:postgresql://db.example.com/happy_fun_times"
|
70
65
|
|
@@ -74,8 +69,8 @@ environment :staging, [:master, :development] do
|
|
74
69
|
end
|
75
70
|
end
|
76
71
|
|
77
|
-
# This will disallow deploying
|
78
|
-
environment :production
|
72
|
+
# This will disallow deploying snapshots to production.
|
73
|
+
environment :production do
|
79
74
|
# Set environment-specific data for your templates to work with.
|
80
75
|
data :jdbc_url, "jdbc:postgresql://db.example.com/happy_fun_times"
|
81
76
|
|
@@ -92,18 +87,15 @@ end
|
|
92
87
|
|
93
88
|
## Deploying a thing
|
94
89
|
|
95
|
-
cap
|
96
|
-
cap
|
97
|
-
cap from:master # Deploy the master branch
|
98
|
-
cap to:production # Deploy to the production environment
|
99
|
-
cap to:staging # Deploy to the staging environment
|
90
|
+
cap production # Deploy to the production environment
|
91
|
+
cap staging # Deploy to the staging environment
|
100
92
|
|
101
|
-
So to deploy
|
102
|
-
simple:
|
93
|
+
So to deploy to your staging environment, it's just a simple:
|
103
94
|
|
104
|
-
cap
|
95
|
+
cap staging
|
105
96
|
|
106
|
-
|
97
|
+
You'll be prompted for the version you want to deploy (and, if you're deploying a snapshot, the
|
98
|
+
build number), and you're off to the races.
|
107
99
|
|
108
100
|
--------------------------------------------------------------------------------
|
109
101
|
Copyright (c) 2011 Yammer, Inc. See LICENSE.txt for further details.
|
data/Rakefile
CHANGED
@@ -11,9 +11,12 @@ Jeweler::Tasks.new do |gem|
|
|
11
11
|
gem.email = "coda.hale@gmail.com"
|
12
12
|
gem.authors = ["Coda Hale"]
|
13
13
|
|
14
|
-
gem.add_runtime_dependency "json"
|
15
14
|
gem.add_runtime_dependency "capistrano"
|
16
15
|
gem.add_runtime_dependency "erubis"
|
16
|
+
gem.add_runtime_dependency "nokogiri"
|
17
|
+
gem.add_runtime_dependency "highline"
|
18
|
+
gem.add_runtime_dependency "patron"
|
19
|
+
gem.add_runtime_dependency "yajl-ruby"
|
17
20
|
gem.add_runtime_dependency "always_verify_ssl_certificates"
|
18
21
|
end
|
19
|
-
Jeweler::RubygemsDotOrgTasks.new
|
22
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.9
|
data/lib/crapapult.rb
CHANGED
@@ -1,10 +1,64 @@
|
|
1
1
|
require "rubygems"
|
2
|
+
require "patron"
|
3
|
+
require "nokogiri"
|
2
4
|
require "always_verify_ssl_certificates"
|
3
5
|
require "capistrano"
|
4
6
|
require "net/http"
|
5
7
|
require "net/https"
|
6
|
-
require "json"
|
7
8
|
require "erubis"
|
9
|
+
require "yajl/json_gem"
|
10
|
+
|
11
|
+
class Maven
|
12
|
+
def initialize(url)
|
13
|
+
@url = url.gsub(/\/$/, '')
|
14
|
+
@session = Patron::Session.new
|
15
|
+
@session.timeout = 10
|
16
|
+
@session.base_url = @url
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_versions(group_id, artifact_id)
|
20
|
+
resp = @session.get("/#{urlify(group_id)}/#{artifact_id}/maven-metadata.xml")
|
21
|
+
if resp.status == 200
|
22
|
+
Nokogiri::XML(resp.body).css("metadata versioning versions version").map { |e| e.text.strip }
|
23
|
+
else
|
24
|
+
raise "unable to find metadata for #{group_id}:#{artifact_id}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_release_versions(group_id, artifact_id)
|
29
|
+
find_versions(group_id, artifact_id).select { |v| v !~ /SNAPSHOT$/ }
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_snapshot_builds(group_id, artifact_id, version)
|
33
|
+
resp = @session.get("/#{urlify(group_id)}/#{artifact_id}/#{version}/maven-metadata.xml")
|
34
|
+
if resp.status == 200
|
35
|
+
Nokogiri::XML(resp.body).css('metadata > versioning > snapshotVersions').map { |e|
|
36
|
+
ext = e.at("extension")
|
37
|
+
if ext && ext.text.strip == "jar"
|
38
|
+
e.at("value").text.strip
|
39
|
+
else
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
}.flatten
|
43
|
+
else
|
44
|
+
raise "unable to find metadata for #{group_id}:#{artifact_id}:#{version}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def release_artifact_url(group_id, artifact_id, version)
|
49
|
+
"#{@url}/#{urlify(group_id)}/#{artifact_id}/#{version}/#{artifact_id}-#{version}.jar"
|
50
|
+
end
|
51
|
+
|
52
|
+
def snapshot_artifact_url(group_id, artifact_id, version, build)
|
53
|
+
"#{@url}/#{urlify(group_id)}/#{artifact_id}/#{version}/#{artifact_id}-#{build}.jar"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def urlify(group_id)
|
59
|
+
group_id.gsub('.', '/')
|
60
|
+
end
|
61
|
+
end
|
8
62
|
|
9
63
|
Capistrano::Configuration.instance(:must_exist).load do
|
10
64
|
|
@@ -16,8 +70,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
16
70
|
default_run_options[:pty] = true
|
17
71
|
@invoke = tasks.delete(:invoke)
|
18
72
|
@shell = tasks.delete(:shell)
|
19
|
-
|
20
|
-
@branches = []
|
73
|
+
|
21
74
|
@hosts = {}
|
22
75
|
@data = {}
|
23
76
|
|
@@ -25,51 +78,43 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
25
78
|
###################################### API ###################################
|
26
79
|
##############################################################################
|
27
80
|
|
81
|
+
# Defines the artifact's groupId.
|
82
|
+
def group_id(name)
|
83
|
+
set :current_group_id, name
|
84
|
+
end
|
85
|
+
|
86
|
+
# Defines the artifact's artifactId.
|
87
|
+
def artifact_id(name)
|
88
|
+
set :current_artifact_id, name
|
89
|
+
end
|
90
|
+
|
28
91
|
# Defines a host with an optional host-specific configuration block.
|
29
92
|
def host(name, &block)
|
30
93
|
@hosts[name] = block || lambda {}
|
31
94
|
server name, :app
|
32
95
|
end
|
33
96
|
|
34
|
-
# Defines an environment
|
97
|
+
# Defines an environment which might allow snapshots and an optional
|
35
98
|
# environment-specific configuration block.
|
36
|
-
def environment(name,
|
99
|
+
def environment(name, opts={}, &block)
|
37
100
|
env_block = block || lambda {}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
namespace :to do
|
46
|
-
desc "Deploy to the #{name} environment"
|
47
|
-
task name.to_sym do
|
48
|
-
set :current_environment, name
|
49
|
-
set :allowed_branches, branches
|
50
|
-
env_block.call
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Defines a branch with a Hudson job name.
|
56
|
-
def branch(name, job)
|
57
|
-
@branches << name.to_sym
|
58
|
-
namespace :from do
|
59
|
-
desc "Deploy the #{name} branch"
|
60
|
-
task name.to_sym do
|
61
|
-
set :current_branch, name
|
62
|
-
set :current_job, job
|
63
|
-
end
|
101
|
+
|
102
|
+
desc "Deploy to the #{name} environment"
|
103
|
+
task name.to_sym do
|
104
|
+
set :current_environment, name
|
105
|
+
set :allow_snapshots, opts[:allow_snapshots]
|
106
|
+
env_block.call
|
107
|
+
execute_task(find_task("deploy"))
|
64
108
|
end
|
65
109
|
end
|
66
|
-
|
67
|
-
# Defines a
|
68
|
-
def
|
69
|
-
if exists?(:
|
70
|
-
abort "You're calling #
|
110
|
+
|
111
|
+
# Defines a Maven repository.
|
112
|
+
def maven(url)
|
113
|
+
if exists?(:current_repo)
|
114
|
+
abort "You're calling #maven twice. Stop it."
|
115
|
+
else
|
116
|
+
set :current_repo, url
|
71
117
|
end
|
72
|
-
set :current_hudson_url, url
|
73
118
|
end
|
74
119
|
|
75
120
|
# Defines an application with an optional owner, group, and configuration
|
@@ -99,7 +144,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
99
144
|
puts(">> #{server.host} is OK")
|
100
145
|
else
|
101
146
|
puts(">> #{server.host} is DOWN!!!")
|
102
|
-
puts(resp.body.split("\n").map { |s| ">>> #{s}" }.join)
|
147
|
+
puts(resp.body.split("\n").map { |s| ">>> #{s}" }.join("\n"))
|
103
148
|
end
|
104
149
|
end
|
105
150
|
end
|
@@ -153,12 +198,12 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
153
198
|
end
|
154
199
|
|
155
200
|
# Post a message to your Yammer network when you deploy.
|
156
|
-
def yammer(token, opts={})
|
157
|
-
opts = { :host => "www.yammer.com",
|
158
|
-
:body => %Q{The <%= current_branch %> branch of <%= application_name %> (<%= last_ref[0..5] %>) was just deployed to the <%= current_environment %> environment by @<%= `whoami`.strip %>.}
|
159
|
-
}.merge(opts)
|
160
|
-
|
201
|
+
def yammer(token, opts={})
|
161
202
|
after :deploy do
|
203
|
+
set :deployed_version, exists?(:build) ? "#{version} (#{build})" : version
|
204
|
+
opts = {:host => "www.yammer.com",
|
205
|
+
:body => %Q{<%= application_name %> v<%= deployed_version %> was just deployed to the <%= current_environment %> environment by @<%= `whoami`.strip %>.}
|
206
|
+
}.merge(opts)
|
162
207
|
puts "> Sending notification to Yammer"
|
163
208
|
opts[:body] = Erubis::Eruby.new(opts[:body]).result(binding)
|
164
209
|
http = Net::HTTP.new(opts.delete(:host), 443)
|
@@ -180,11 +225,12 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
180
225
|
##############################################################################
|
181
226
|
|
182
227
|
namespace :deploy do
|
183
|
-
desc "Deploy the specified branch to the specified environment"
|
184
228
|
task :default do
|
185
229
|
check_required_parameters!
|
186
230
|
|
187
|
-
puts "> Deploying
|
231
|
+
puts "> Deploying #{application_name} to #{current_environment}"
|
232
|
+
|
233
|
+
artifact_url # queue this up before hand
|
188
234
|
|
189
235
|
setup
|
190
236
|
stage
|
@@ -194,7 +240,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
194
240
|
verify
|
195
241
|
cleanup
|
196
242
|
|
197
|
-
puts "> Successfully
|
243
|
+
puts "> Successfully #{application_name} to #{current_environment}! Congrats!"
|
198
244
|
end
|
199
245
|
|
200
246
|
task :setup, :role => :app do
|
@@ -257,77 +303,56 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
257
303
|
"/tmp/#{application_name}-#{rand(10_000_000)}"
|
258
304
|
end
|
259
305
|
|
260
|
-
set(:last_build) do
|
261
|
-
build = hudson_api(current_hudson_url + "/job/#{current_job}/lastBuild")
|
262
|
-
unless build["result"] == "SUCCESS"
|
263
|
-
abort("The last build of #{current_job} failed; see #{build["url"]} for details")
|
264
|
-
end
|
265
|
-
|
266
|
-
build
|
267
|
-
end
|
268
|
-
|
269
|
-
set(:last_ref) do
|
270
|
-
last_build["actions"].find { |f| f["lastBuiltRevision"] }["lastBuiltRevision"]["SHA1"]
|
271
|
-
end
|
272
|
-
|
273
306
|
set(:artifact_url) do
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
307
|
+
hl = HighLine.new
|
308
|
+
m = Maven.new(current_repo)
|
309
|
+
|
310
|
+
hl.say("Select a version to deploy:")
|
311
|
+
versions = if allow_snapshots
|
312
|
+
m.find_versions(current_group_id, current_artifact_id).reverse[0..2]
|
313
|
+
else
|
314
|
+
m.find_release_versions(current_group_id, current_artifact_id).reverse[0..2]
|
315
|
+
end
|
316
|
+
|
317
|
+
set :version, hl.choose(*versions)
|
318
|
+
puts
|
319
|
+
|
320
|
+
if version =~ /SNAPSHOT$/
|
321
|
+
builds = m.find_snapshot_builds(current_group_id, current_artifact_id, version)
|
322
|
+
hl.say("Select a snapshot build to deploy:")
|
323
|
+
set :build, hl.choose(*builds)
|
324
|
+
m.snapshot_artifact_url(current_group_id, current_artifact_id, version, build)
|
286
325
|
else
|
287
|
-
|
326
|
+
m.release_artifact_url(current_group_id, current_artifact_id, version)
|
288
327
|
end
|
289
|
-
last_build["url"] + "artifact/" + artifact["relativePath"]
|
290
328
|
end
|
291
329
|
|
292
330
|
set(:artifact_filename) do
|
293
331
|
filename = artifact_url.split("/").last
|
294
|
-
"#{application_name}-#{Time.now.strftime("%Y%m%d%H%M%S")}-#{
|
332
|
+
"#{application_name}-#{Time.now.strftime("%Y%m%d%H%M%S")}-#{version}#{File.extname(filename)}"
|
295
333
|
end
|
296
334
|
|
297
335
|
set(:directory) { "/opt/#{application_name}" }
|
298
336
|
|
299
|
-
def hudson_api(url)
|
300
|
-
if url !~ /\/$/
|
301
|
-
url = url + "/api/json"
|
302
|
-
else
|
303
|
-
url = url + "api/json"
|
304
|
-
end
|
305
|
-
|
306
|
-
logger.debug("getting #{url}...")
|
307
|
-
json = Net::HTTP.get(URI.parse(url))
|
308
|
-
# logger.debug("Received: #{json}")
|
309
|
-
JSON.parse(json)
|
310
|
-
end
|
311
|
-
|
312
337
|
def check_required_parameters!
|
313
338
|
unless exists?(:application_name)
|
314
339
|
abort("No application defined.")
|
315
340
|
end
|
316
341
|
|
317
|
-
unless exists?(:
|
318
|
-
abort("No
|
342
|
+
unless exists?(:current_repo)
|
343
|
+
abort("No Maven repository defined.")
|
319
344
|
end
|
320
|
-
|
321
|
-
unless exists?(:
|
322
|
-
abort("No
|
345
|
+
|
346
|
+
unless exists?(:current_group_id)
|
347
|
+
abort("No group ID defined.")
|
323
348
|
end
|
324
|
-
|
325
|
-
unless exists?(:
|
326
|
-
abort("No
|
349
|
+
|
350
|
+
unless exists?(:current_artifact_id)
|
351
|
+
abort("No artifact ID defined.")
|
327
352
|
end
|
328
353
|
|
329
|
-
unless
|
330
|
-
abort
|
354
|
+
unless exists?(:current_environment)
|
355
|
+
abort("No target environment specified. Please use one of the to:* tasks.")
|
331
356
|
end
|
332
357
|
end
|
333
358
|
|
@@ -336,4 +361,4 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
336
361
|
tasks[:invoke] = @invoke
|
337
362
|
tasks[:shell] = @shell
|
338
363
|
end
|
339
|
-
end
|
364
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crapapult
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 9
|
10
|
+
version: 0.0.9
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Coda Hale
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-12-21 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: capistrano
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: erubis
|
37
37
|
prerelease: false
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
@@ -47,7 +47,7 @@ dependencies:
|
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id002
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: nokogiri
|
51
51
|
prerelease: false
|
52
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
@@ -61,7 +61,7 @@ dependencies:
|
|
61
61
|
type: :runtime
|
62
62
|
version_requirements: *id003
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
|
-
name:
|
64
|
+
name: highline
|
65
65
|
prerelease: false
|
66
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
67
67
|
none: false
|
@@ -74,6 +74,48 @@ dependencies:
|
|
74
74
|
version: "0"
|
75
75
|
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: patron
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: yajl-ruby
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
type: :runtime
|
104
|
+
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: always_verify_ssl_certificates
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
type: :runtime
|
118
|
+
version_requirements: *id007
|
77
119
|
description: Yammer's Capistrano-based deploy crap.
|
78
120
|
email: coda.hale@gmail.com
|
79
121
|
executables: []
|