kstrano 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/kumafy +251 -0
- data/lib/helpers/airbrake_helper.rb +33 -0
- data/lib/helpers/campfire_helper.rb +25 -0
- data/lib/helpers/git_helper.rb +36 -0
- data/lib/helpers/jenkins_helper.rb +132 -0
- data/lib/helpers/kuma_helper.rb +27 -0
- data/lib/kstrano.rb +298 -0
- metadata +164 -0
data/bin/kumafy
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'highline'
|
6
|
+
require 'net/http'
|
7
|
+
require 'net/https'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
DEPLOY_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/deploy.rb"
|
11
|
+
PRODUCTION_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/production.rb"
|
12
|
+
STAGING_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/staging.rb"
|
13
|
+
|
14
|
+
BUILD_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/build.xml"
|
15
|
+
PHPCS_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/phpcs.xml"
|
16
|
+
PHPMD_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/phpmd.xml"
|
17
|
+
PHPUNIT_GIST = "https://raw.github.com/Kunstmaan/kStrano/master/config/phpunit.xml.dist"
|
18
|
+
|
19
|
+
def update_capfile(base, context, force)
|
20
|
+
file = File.join(base, "Capfile")
|
21
|
+
|
22
|
+
if !File.exists?("Capfile")
|
23
|
+
abort "Make sure the project has been capified or capifonied."
|
24
|
+
else
|
25
|
+
includestr = "load Gem.find_files('kstrano.rb').last.to_s"
|
26
|
+
fcontent = ""
|
27
|
+
File.open(file, "r") do |f|
|
28
|
+
f.each do |line|
|
29
|
+
fcontent << line
|
30
|
+
if line.include? includestr
|
31
|
+
abort "This project is already kumafied!" unless force
|
32
|
+
return
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
File.open(file, "w") do |f|
|
38
|
+
fcontent.each_line do |line|
|
39
|
+
if line.include? "load 'app/config/deploy'"
|
40
|
+
f.print includestr + "\r\n"
|
41
|
+
end
|
42
|
+
f.print line
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_deploy_config(ui, base, context, force)
|
49
|
+
|
50
|
+
deploy = File.join(base, "app", "config", "deploy.rb")
|
51
|
+
write_deploy = true
|
52
|
+
#if File.exists?(deploy)
|
53
|
+
#overwrite = ui.ask("The file #{deploy} already exists, do you want to override it? ") { |q| q.default = 'n' }
|
54
|
+
#if !overwrite.match(/^y/)
|
55
|
+
# write_deploy = false
|
56
|
+
#end
|
57
|
+
#end
|
58
|
+
|
59
|
+
if write_deploy
|
60
|
+
deploy_gist = get_plain_secure(DEPLOY_GIST).body
|
61
|
+
context["app_name"] ||= ui.ask("What's the name of the application?")
|
62
|
+
app_name = context["app_name"]
|
63
|
+
deploy_gist.gsub!(/(:application,\s)(\"\")/, '\1' + "\"#{app_name}\"")
|
64
|
+
deploy_gist.gsub!(/(:admin_runner,\s)(\"\")/, '\1' + "\"#{app_name}\"")
|
65
|
+
|
66
|
+
campfire_room = ui.ask("What's the name of the campfire room for this project?")
|
67
|
+
if !campfire_room.nil? && !campfire_room.empty?
|
68
|
+
deploy_gist.gsub!(/(:campfire_room,\s)(nil)/, '\1' + "\"#{campfire_room}\"")
|
69
|
+
context["campfire_room"] = campfire_room
|
70
|
+
end
|
71
|
+
|
72
|
+
airbrake_api_key = ui.ask("What's the api key of this project for airbrake?")
|
73
|
+
if !airbrake_api_key.nil? && !airbrake_api_key.empty?
|
74
|
+
deploy_gist.gsub!(/(:airbrake_api_key,\s)(nil)/, '\1' + "\"#{airbrake_api_key}\"")
|
75
|
+
context["airbrake_api_key"] = airbrake_api_key
|
76
|
+
end
|
77
|
+
|
78
|
+
File.open(deploy, "w") do |f|
|
79
|
+
deploy_gist.each_line do |line|
|
80
|
+
f.print line
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
deploy_dir = File.join(base, "app", "config", "deploy")
|
86
|
+
Dir.mkdir(deploy_dir) unless File.directory?(deploy_dir)
|
87
|
+
|
88
|
+
{
|
89
|
+
"production" => PRODUCTION_GIST,
|
90
|
+
"staging" => STAGING_GIST
|
91
|
+
}.each do |env, gist|
|
92
|
+
file = File.join(deploy_dir, "#{env}.rb")
|
93
|
+
write = true
|
94
|
+
|
95
|
+
if File.exists?(file)
|
96
|
+
overwrite = ui.ask("The file #{file} already exists, do you want to override it? ") { |q| q.default = 'n' }
|
97
|
+
if !overwrite.match(/^y/)
|
98
|
+
write = false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if write
|
103
|
+
gist_body = get_plain_secure(gist).body
|
104
|
+
server = ui.ask("On which server is the #{env} environment deployed?")
|
105
|
+
if !server.match(/^.*\.kunstmaan\.be$/)
|
106
|
+
server = "#{server}.kunstmaan.be"
|
107
|
+
end
|
108
|
+
|
109
|
+
gist_body.gsub!(/(:domain,\s)(\"\")/, '\1' + "\"#{server}\"")
|
110
|
+
|
111
|
+
File.open(file, "w") do |f|
|
112
|
+
gist_body.each_line do |line|
|
113
|
+
f.print line
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
def update_jenkins_config(ui, base, context, force)
|
122
|
+
file = File.join(base, "build.xml")
|
123
|
+
write = true
|
124
|
+
|
125
|
+
if File.exists?(file)
|
126
|
+
overwrite = ui.ask("The file #{file} already exists, do you want to override it? ") { |q| q.default = 'n' }
|
127
|
+
if !overwrite.match(/^y/)
|
128
|
+
write = false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if write
|
133
|
+
gist_body = get_plain_secure(BUILD_GIST).body
|
134
|
+
|
135
|
+
context["app_name"] ||= ui.ask("What's the name of the application?")
|
136
|
+
app_name = context["app_name"]
|
137
|
+
gist_body.gsub!(/(\<project\sname=)(\"\")/, '\1' + "\"#{app_name}\"")
|
138
|
+
|
139
|
+
File.open(file, "w") do |f|
|
140
|
+
gist_body.each_line do |line|
|
141
|
+
f.print line
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
build_dir = File.join(base, "build")
|
147
|
+
Dir.mkdir(build_dir) unless File.directory?(build_dir)
|
148
|
+
|
149
|
+
{
|
150
|
+
File.join(build_dir, "phpcs.xml") => PHPCS_GIST,
|
151
|
+
File.join(build_dir, "phpmd.xml") => PHPMD_GIST,
|
152
|
+
File.join(base, "app", "phpunit.xml.dist") => PHPUNIT_GIST
|
153
|
+
}.each do |file, gist|
|
154
|
+
write = true
|
155
|
+
|
156
|
+
if File.exists?(file)
|
157
|
+
overwrite = ui.ask("The file #{file} already exists, do you want to override it? ") { |q| q.default = 'n' }
|
158
|
+
if !overwrite.match(/^y/)
|
159
|
+
write = false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if write
|
164
|
+
gist_body = get_plain_secure(gist).body
|
165
|
+
|
166
|
+
File.open(file, "w") do |f|
|
167
|
+
gist_body.each_line do |line|
|
168
|
+
f.print line
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_plain_secure(url)
|
177
|
+
uri = URI.parse url
|
178
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
179
|
+
http.use_ssl = uri.is_a?(URI::HTTPS)
|
180
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
|
181
|
+
http.start { |http| http.get(uri.path, {}) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def validate_path
|
185
|
+
if ARGV.empty?
|
186
|
+
abort "Please specify the directory to kumafy, e.g. `#{File.basename($0)} .'"
|
187
|
+
elsif !File.exists?(ARGV.first)
|
188
|
+
abort "`#{ARGV.first}' does not exist."
|
189
|
+
elsif !File.directory?(ARGV.first)
|
190
|
+
abort "`#{ARGV.first}' is not a directory."
|
191
|
+
elsif ARGV.length > 1
|
192
|
+
abort "Too many arguments; please specify only the directory to kumafy."
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
force = false
|
197
|
+
|
198
|
+
OptionParser.new do |opts|
|
199
|
+
opts.banner = "Usage: #{File.basename($0)} [path]"
|
200
|
+
|
201
|
+
opts.on("-h", "--help", "Displays this help info") do
|
202
|
+
puts opts
|
203
|
+
exit 0
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on("-c", "--config", "Creates the configuration files needed for deployment") do
|
207
|
+
validate_path
|
208
|
+
ui = HighLine.new
|
209
|
+
base = ARGV.shift
|
210
|
+
context = Hash.new
|
211
|
+
update_deploy_config ui, base, context, force
|
212
|
+
exit 0
|
213
|
+
end
|
214
|
+
|
215
|
+
opts.on("-j", "--jenkins", "Creates the jenkins configuration files for this project") do
|
216
|
+
validate_path
|
217
|
+
ui = HighLine.new
|
218
|
+
base = ARGV.shift
|
219
|
+
context = Hash.new
|
220
|
+
update_jenkins_config ui, base, context, force
|
221
|
+
exit 0
|
222
|
+
end
|
223
|
+
|
224
|
+
opts.on("-f", "--force", "This will force the kumafying of the project") do
|
225
|
+
force = true
|
226
|
+
end
|
227
|
+
|
228
|
+
begin
|
229
|
+
opts.parse!(ARGV)
|
230
|
+
rescue OptionParser::ParseError => e
|
231
|
+
warn e.message
|
232
|
+
puts opts
|
233
|
+
exit 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
validate_path
|
238
|
+
|
239
|
+
ui = HighLine.new
|
240
|
+
base = ARGV.shift
|
241
|
+
context = Hash.new
|
242
|
+
|
243
|
+
puts "[start] kcapifony"
|
244
|
+
%x(kcapifony .)
|
245
|
+
puts "[end] kcapifony"
|
246
|
+
|
247
|
+
update_capfile base, context, force
|
248
|
+
update_deploy_config ui, base, context, force
|
249
|
+
update_jenkins_config ui, base, context, force
|
250
|
+
|
251
|
+
puts "[done] project kumafied!"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Kumastrano
|
2
|
+
# Using the gem https://github.com/airbrake/airbrake doesn't work because it's made for rails apps, it needs rake etc. + you need to have the i18n gem
|
3
|
+
# This will integrate a very easy command to tell airbrake a deploy has been done
|
4
|
+
class AirbrakeHelper
|
5
|
+
|
6
|
+
require 'net/http'
|
7
|
+
require 'uri'
|
8
|
+
require 'etc'
|
9
|
+
|
10
|
+
def self.notify(api_key, revision, repository, environment = 'production', username = Etc.getlogin.capitalize)
|
11
|
+
uri = URI.parse("http://airbrake.io")
|
12
|
+
|
13
|
+
params = {
|
14
|
+
'api_key' => api_key,
|
15
|
+
'deploy[rails_env]' => environment, # Environment of the deploy (production, staging), this needs to be the current environment
|
16
|
+
'deploy[scm_revision]' => revision, # The given revision/sha that is being deployed, this needs to be the current_revision variable
|
17
|
+
'deploy[scm_repository]' => repository, # Address of your repository to help with code lookups
|
18
|
+
'deploy[local_username]' => username # Who is deploying
|
19
|
+
}
|
20
|
+
|
21
|
+
post = Net::HTTP::Post.new("/deploys")
|
22
|
+
post.set_form_data(params)
|
23
|
+
|
24
|
+
res = Net::HTTP.start(uri.host, uri.port) {|http| http.request(post)}
|
25
|
+
|
26
|
+
if res.code.to_i == 200
|
27
|
+
return true
|
28
|
+
else
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Kumastrano
|
2
|
+
class CampfireHelper
|
3
|
+
|
4
|
+
require 'broach'
|
5
|
+
|
6
|
+
def self.speak(campfire_account, campfire_token, campfire_room, message="")
|
7
|
+
## extracted this to here, because i don't know how to call capistrano tasks with arguments
|
8
|
+
## else i just had to make one capistrano task which i could call
|
9
|
+
if !campfire_account.nil? && !campfire_token.nil? && !campfire_room.nil?
|
10
|
+
|
11
|
+
Broach.settings = {
|
12
|
+
'account' => campfire_account,
|
13
|
+
'token' => campfire_token,
|
14
|
+
'use_ssl' => true
|
15
|
+
}
|
16
|
+
|
17
|
+
room = Broach::Room.find_by_name campfire_room
|
18
|
+
|
19
|
+
if !room.nil?
|
20
|
+
room.speak message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Kumastrano
|
2
|
+
class GitHelper
|
3
|
+
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
def self.fetch
|
7
|
+
%x(git fetch)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.merge_base(commit1, commit2 = "HEAD")
|
11
|
+
base = %x(git merge-base #{commit1} #{commit2})
|
12
|
+
base.strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.commit_hash(commit = "HEAD")
|
16
|
+
hash = %x(git rev-parse #{commit})
|
17
|
+
hash.strip
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.branch_name(commit = "HEAD")
|
21
|
+
name = %x(git name-rev --name-only #{commit})
|
22
|
+
name.strip
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.origin_refspec
|
26
|
+
refspec = %x(git config remote.origin.fetch)
|
27
|
+
refspec.strip
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.origin_url
|
31
|
+
url = %x(git config remote.origin.url)
|
32
|
+
url.strip
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Kumastrano
|
2
|
+
class JenkinsHelper
|
3
|
+
|
4
|
+
require 'cgi'
|
5
|
+
require 'net/http'
|
6
|
+
require 'uri'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
def self.available?(base_uri)
|
10
|
+
res = get_plain("#{base_uri}")
|
11
|
+
res.code.to_i == 200
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.build_and_wait(job_uri, timeout=300, interval=2)
|
15
|
+
success = false
|
16
|
+
last_build_info = nil
|
17
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number(job_uri)
|
18
|
+
Kumastrano::JenkinsHelper.build_job(job_uri)
|
19
|
+
Kumastrano.poll("A timeout occured", timeout, interval) do
|
20
|
+
## wait for the building to be finished
|
21
|
+
last_build_info = Kumastrano::JenkinsHelper.build_info(job_uri)
|
22
|
+
result = last_build_info['result'] ## SUCCESS or FAILURE
|
23
|
+
building = last_build_info['building']
|
24
|
+
number = last_build_info['number']
|
25
|
+
if number == (prev_build + 1) && "false" == building.to_s && !result.nil?
|
26
|
+
if "SUCCESS" == result
|
27
|
+
success = true
|
28
|
+
else
|
29
|
+
success = false
|
30
|
+
end
|
31
|
+
true
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
return success, last_build_info
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.fetch_build_hash_from_build_info(build_info, branch_name)
|
40
|
+
actions = build_info['actions']
|
41
|
+
|
42
|
+
build_hash = nil
|
43
|
+
actions.each do |list|
|
44
|
+
last_revision = list['lastBuiltRevision']
|
45
|
+
if !last_revision.nil? && branch_name == last_revision['branch'][0]['name'].sub("origin/", "")
|
46
|
+
build_hash = last_revision['branch'][0]['SHA1']
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
build_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.make_safe_job_name(app_name, branch_name)
|
55
|
+
job_name = "#{app_name} (#{branch_name})"
|
56
|
+
job_name.gsub(/[#*\/\\]/, "-") # \/#* is unsafe for jenkins job name, because not uri safe
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.job_url_for_branch(jenkins_base_uri, branch_name)
|
60
|
+
current_job_url = nil
|
61
|
+
Kumastrano::JenkinsHelper.list_jobs(jenkins_base_uri).each do |job|
|
62
|
+
name = job["name"]
|
63
|
+
url = job["url"]
|
64
|
+
if /.*\(#{branch_name}\)/.match(name)
|
65
|
+
current_job_url = url
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
current_job_url
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.job_url_for_name(jenkins_base_uri, job_name)
|
73
|
+
current_job_url = nil
|
74
|
+
Kumastrano::JenkinsHelper.list_jobs(jenkins_base_uri).each do |job|
|
75
|
+
name = job["name"]
|
76
|
+
url = job["url"]
|
77
|
+
if job_name == name
|
78
|
+
current_job_url = url
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
current_job_url
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.list_jobs(base_uri)
|
86
|
+
res = get_plain("#{base_uri}/api/json?tree=jobs[name,url]")
|
87
|
+
parsed_res = JSON.parse(res.body)["jobs"]
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.create_new_job(base_uri, job_name, config)
|
91
|
+
uri = URI.parse("#{base_uri}/createItem/api/json")
|
92
|
+
request = Net::HTTP::Post.new(uri.path + "?name=#{CGI.escape(job_name)}")
|
93
|
+
request.body = config
|
94
|
+
request["Content-Type"] = "application/xml"
|
95
|
+
res = Net::HTTP.start(uri.host, uri.port) {|http| http.request(request)}
|
96
|
+
if res.code.to_i == 200
|
97
|
+
puts "job created"
|
98
|
+
else
|
99
|
+
puts "job not created"
|
100
|
+
puts res.body
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.retrieve_config_xml(job_uri)
|
105
|
+
res = get_plain("#{job_uri}/config.xml").body
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.build_job(job_uri)
|
109
|
+
res = get_plain("#{job_uri}/build")
|
110
|
+
res.code.to_i == 302
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.last_build_number(job_uri)
|
114
|
+
res = get_plain("#{job_uri}/api/json?tree=lastBuild[number]")
|
115
|
+
parsed_res = JSON.parse(res.body)
|
116
|
+
parsed_res['lastBuild']['number']
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.build_info(job_uri, build="lastBuild")
|
120
|
+
res = get_plain("#{job_uri}/#{build}/api/json")
|
121
|
+
parsed_res = JSON.parse(res.body)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def self.get_plain(uri)
|
127
|
+
uri = URI.parse uri
|
128
|
+
res = Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path, {}) }
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Kumastrano
|
2
|
+
|
3
|
+
def poll(msg=nil, seconds=10.0, interval_seconds=1.0)
|
4
|
+
(seconds / interval_seconds).to_i.times do
|
5
|
+
result = yield
|
6
|
+
return if result
|
7
|
+
sleep interval_seconds
|
8
|
+
end
|
9
|
+
msg ||= "polling failed after #{seconds} seconds"
|
10
|
+
raise msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def say(text)
|
14
|
+
Capistrano::CLI.ui.say(" * #{text}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def ask(question, default='n')
|
18
|
+
agree = Capistrano::CLI.ui.agree(" #{question} ") do |q|
|
19
|
+
q.default = default
|
20
|
+
end
|
21
|
+
|
22
|
+
agree
|
23
|
+
end
|
24
|
+
|
25
|
+
module_function :poll, :say, :ask
|
26
|
+
|
27
|
+
end
|
data/lib/kstrano.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
set :jenkins_base_uri, ""
|
2
|
+
set :jenkins_base_job_name, "Default"
|
3
|
+
set :jenkins_poll_timeout, 300
|
4
|
+
set :jenkins_poll_interval, 2
|
5
|
+
set :jenkins_enabled, false
|
6
|
+
|
7
|
+
set :campfire_room, nil
|
8
|
+
set :campfire_token, "" ## kbot
|
9
|
+
set :campfire_account, "Kunstmaan"
|
10
|
+
|
11
|
+
set :airbrake_api_key, nil
|
12
|
+
|
13
|
+
|
14
|
+
require "#{File.dirname(__FILE__)}/helpers/git_helper.rb"
|
15
|
+
require "#{File.dirname(__FILE__)}/helpers/jenkins_helper.rb"
|
16
|
+
require "#{File.dirname(__FILE__)}/helpers/airbrake_helper.rb"
|
17
|
+
require "#{File.dirname(__FILE__)}/helpers/campfire_helper.rb"
|
18
|
+
require "#{File.dirname(__FILE__)}/helpers/kuma_helper.rb"
|
19
|
+
require 'rexml/document'
|
20
|
+
require 'etc'
|
21
|
+
|
22
|
+
namespace :kuma do
|
23
|
+
|
24
|
+
desc "Run fixcron for the current project"
|
25
|
+
task :fixcron do
|
26
|
+
sudo "sh -c 'if [ -f /opt/kDeploy/tools/fixcron.py ] ; then cd /opt/kDeploy/tools/; python fixcron.py #{application}; fi'"
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Run fixperms for the current project"
|
30
|
+
task :fixperms do
|
31
|
+
sudo "sh -c 'if [ -f /opt/kDeploy/tools/fixperms.py ] ; then cd /opt/kDeploy/tools/; python fixperms.py #{application}; fi'"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
namespace :airbrake do
|
37
|
+
|
38
|
+
desc "Register a deploy with airbrake.io"
|
39
|
+
task :notify do
|
40
|
+
if !airbrake_api_key.nil?
|
41
|
+
revision = Kumastrano::GitHelper::commit_hash
|
42
|
+
repository = Kumastrano::GitHelper::origin_url
|
43
|
+
env ||= "production"
|
44
|
+
success = Kumastrano::AirbrakeHelper.notify airbrake_api_key, revision, repository, env
|
45
|
+
Kumastrano.say "Failed notifying airbrake of the new deploy" unless success
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :campfire do
|
52
|
+
|
53
|
+
desc "Say something on campfire"
|
54
|
+
task:say do
|
55
|
+
if !campfire_room.nil?
|
56
|
+
message = ARGV.join(' ').gsub('campfire:say', '')
|
57
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, message
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
namespace :jenkins do
|
65
|
+
|
66
|
+
desc "create a job for the current branch and application on Jenkins"
|
67
|
+
task:create_job do
|
68
|
+
## 1. locate the job in jenkins
|
69
|
+
current_branch = Kumastrano::GitHelper.branch_name
|
70
|
+
job_name = Kumastrano::JenkinsHelper.make_safe_job_name(application, current_branch)
|
71
|
+
current_job_url = Kumastrano::JenkinsHelper.job_url_for_name(jenkins_base_uri, job_name)
|
72
|
+
|
73
|
+
## 2. if no job was found, first create a job for this branch
|
74
|
+
if current_job_url.nil?
|
75
|
+
Kumastrano.say "no job found for branch #{current_branch} on #{application}, we'll create one now"
|
76
|
+
default_job_url = Kumastrano::JenkinsHelper.job_url_for_name(jenkins_base_uri, jenkins_base_job_name)
|
77
|
+
if !default_job_url.nil?
|
78
|
+
current_refspec = Kumastrano::GitHelper.origin_refspec
|
79
|
+
current_url = Kumastrano::GitHelper.origin_url
|
80
|
+
|
81
|
+
default_job_config = Kumastrano::JenkinsHelper.retrieve_config_xml default_job_url
|
82
|
+
document = REXML::Document.new default_job_config
|
83
|
+
root = document.root
|
84
|
+
|
85
|
+
## change the description
|
86
|
+
root.elements['description'].text = "This job will be used for testing the branch #{current_branch}"
|
87
|
+
|
88
|
+
## change the git config
|
89
|
+
git_element = root.elements["scm[@class='hudson.plugins.git.GitSCM']"]
|
90
|
+
git_element.elements["userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/refspec"].text = current_refspec
|
91
|
+
git_element.elements["userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url"].text = current_url
|
92
|
+
git_element.elements["branches/hudson.plugins.git.BranchSpec/name"].text = current_branch
|
93
|
+
|
94
|
+
## create the new job based on the modified git config
|
95
|
+
Kumastrano::JenkinsHelper.create_new_job(jenkins_base_uri, job_name, document.to_s)
|
96
|
+
current_job_url = Kumastrano::JenkinsHelper.job_url_for_name(jenkins_base_uri, job_name)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
Kumastrano.say "there was already a job available on Jenkins for branch #{current_branch} on #{application}"
|
100
|
+
end
|
101
|
+
|
102
|
+
current_job_url
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "Try to build the current branch on Jenkins"
|
106
|
+
task:build do
|
107
|
+
current_job_url = jenkins::create_job
|
108
|
+
current_branch = Kumastrano::GitHelper.branch_name
|
109
|
+
|
110
|
+
if !current_job_url.nil?
|
111
|
+
job_name = Kumastrano::JenkinsHelper.make_safe_job_name(application, current_branch)
|
112
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number current_job_url
|
113
|
+
Kumastrano.say "start building build ##{(prev_build + 1)} on job #{job_name}, this can take a while"
|
114
|
+
|
115
|
+
result, last_build_info = Kumastrano::JenkinsHelper.build_and_wait current_job_url, jenkins_poll_timeout, jenkins_poll_interval
|
116
|
+
|
117
|
+
message = ""
|
118
|
+
if result
|
119
|
+
Kumastrano.say "the build was succesful"
|
120
|
+
message = "was a success"
|
121
|
+
else
|
122
|
+
Kumastrano.say "the build failed"
|
123
|
+
message = "failed"
|
124
|
+
end
|
125
|
+
|
126
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, "#{Etc.getlogin.capitalize} just builded a new version of #{current_branch} on #{application} and it #{message}. You can view the results here #{current_job_url}/lastBuild."
|
127
|
+
else
|
128
|
+
Kumastrano.say "no job found for #{job_name}, cannot build"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
namespace :deploy do
|
135
|
+
|
136
|
+
task :symlink, :except => { :no_release => true } do
|
137
|
+
on_rollback do
|
138
|
+
if previous_release
|
139
|
+
try_sudo "ln -sf #{previous_release} #{current_path}; true"
|
140
|
+
else
|
141
|
+
logger.important "no previous release to rollback to, rollback of symlink skipped"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
try_sudo "ln -sfT #{latest_release} #{current_path}"
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
## Capistrano callbacks ##
|
151
|
+
|
152
|
+
# Before deploy:
|
153
|
+
## Check if there is a build on jenkins. If none is available, it will create one.
|
154
|
+
## Notify on Campfire what the deployer did.
|
155
|
+
before :deploy do
|
156
|
+
## only do this in production environment
|
157
|
+
if env == 'production' and jenkins_enabled
|
158
|
+
can_deploy = false
|
159
|
+
current_branch = Kumastrano::GitHelper.branch_name
|
160
|
+
current_hash = Kumastrano::GitHelper.commit_hash
|
161
|
+
|
162
|
+
if Kumastrano::JenkinsHelper.available? jenkins_base_uri
|
163
|
+
## Allways fetch the latest information from git
|
164
|
+
Kumastrano::GitHelper.fetch
|
165
|
+
|
166
|
+
job_name = Kumastrano::JenkinsHelper.make_safe_job_name(application, current_branch)
|
167
|
+
current_job_url = Kumastrano::JenkinsHelper.job_url_for_name(jenkins_base_uri, job_name)
|
168
|
+
|
169
|
+
if current_job_url.nil?
|
170
|
+
## No job exists for the current branch, we'll create a job.
|
171
|
+
if Kumastrano.ask "no job found for the current branch, do you want to create a job for #{current_branch} on #{application}?", 'y'
|
172
|
+
current_job_url = jenkins::create_job
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if !current_job_url.nil?
|
177
|
+
## we have a job url, get info of the lastBuild
|
178
|
+
last_build_info = Kumastrano::JenkinsHelper.build_info current_job_url
|
179
|
+
result = last_build_info['result'] ## SUCCESS or FAILURE
|
180
|
+
build_hash = Kumastrano::JenkinsHelper.fetch_build_hash_from_build_info(last_build_info, current_branch)
|
181
|
+
|
182
|
+
if !build_hash.nil?
|
183
|
+
if build_hash == current_hash
|
184
|
+
if "SUCCESS" == result
|
185
|
+
## The hash of the last build is the same as my hash we can deploy
|
186
|
+
can_deploy = true
|
187
|
+
else
|
188
|
+
## The hash of the last build is the same as the current hash, but the build failed.
|
189
|
+
if Kumastrano.ask "the last build for the branch #{current_branch} for commit #{current_hash} failed, do you want to build again?", 'y'
|
190
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number current_job_url
|
191
|
+
Kumastrano.say "start building build ##{(prev_build + 1)} on job #{job_name}, this can take a while"
|
192
|
+
result, last_build_info = Kumastrano::JenkinsHelper.build_and_wait current_job_url, jenkins_poll_timeout, jenkins_poll_interval
|
193
|
+
new_build_hash = Kumastrano::JenkinsHelper.fetch_build_hash_from_build_info(last_build_info, current_branch)
|
194
|
+
if !result.nil? && result && !new_build_hash.nil? && new_build_hash = current_hash
|
195
|
+
can_deploy = true
|
196
|
+
else
|
197
|
+
Kumastrano.say "there is still something wrong with #{current_branch} on #{application}, please check manually and try deploying again afterwards!"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
else
|
202
|
+
merge_base = Kumastrano::GitHelper.merge_base(build_hash, current_hash)
|
203
|
+
if merge_base == build_hash
|
204
|
+
## The build commit is an ancestor of HEAD
|
205
|
+
if Kumastrano.ask "the last build for the branch #{current_branch} is from an older commit do you want to build again? (jenkins=#{build_hash}, local=#{current_hash})", 'y'
|
206
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number current_job_url
|
207
|
+
Kumastrano.say "start building build ##{(prev_build + 1)} on job #{job_name}, this can take a while"
|
208
|
+
result, last_build_info = Kumastrano::JenkinsHelper.build_and_wait current_job_url, jenkins_poll_timeout, jenkins_poll_interval
|
209
|
+
new_build_hash = Kumastrano::JenkinsHelper.fetch_build_hash_from_build_info(last_build_info, current_branch)
|
210
|
+
if !result.nil? && result && !new_build_hash.nil? && new_build_hash = current_hash
|
211
|
+
can_deploy = true
|
212
|
+
else
|
213
|
+
Kumastrano.say "there is still something wrong with #{current_branch} on #{application}, please check manually and try deploying again afterwards!"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
elsif merge_base == current_hash
|
217
|
+
## The current HEAD is an ancestor of the build hash
|
218
|
+
Kumastrano.say "the latest build is of a newer commit, someone else is probably working on the same branch, try updating your local repository first. (jenkins=#{build_hash}, local=#{current_hash})"
|
219
|
+
else
|
220
|
+
## Something is wrong, we don't know what try building again
|
221
|
+
if Kumastrano.ask "the latest build isn't a valid build, do you want to try building again? (jenkins=#{build_hash}, local=#{current_hash})", 'y'
|
222
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number current_job_url
|
223
|
+
Kumastrano.say "start building build ##{(prev_build + 1)} on job #{job_name}, this can take a while"
|
224
|
+
result, last_build_info = Kumastrano::JenkinsHelper.build_and_wait current_job_url, jenkins_poll_timeout, jenkins_poll_interval
|
225
|
+
new_build_hash = Kumastrano::JenkinsHelper.fetch_build_hash_from_build_info(last_build_info, current_branch)
|
226
|
+
if !result.nil? && result && !new_build_hash.nil? && new_build_hash = current_hash
|
227
|
+
can_deploy = true
|
228
|
+
else
|
229
|
+
Kumastrano.say "there is still something wrong with #{current_branch} on #{application}, please check manually and try deploying again afterwards!"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
else
|
235
|
+
## No build found, try building it
|
236
|
+
if Kumastrano.ask "no build found, do you want to try building it?", 'y'
|
237
|
+
prev_build = Kumastrano::JenkinsHelper.last_build_number current_job_url
|
238
|
+
Kumastrano.say "start building build ##{(prev_build + 1)} on job #{job_name}, this can take a while"
|
239
|
+
result, last_build_info = Kumastrano::JenkinsHelper.build_and_wait current_job_url, jenkins_poll_timeout, jenkins_poll_interval
|
240
|
+
new_build_hash = Kumastrano::JenkinsHelper.fetch_build_hash_from_build_info(last_build_info, current_branch)
|
241
|
+
if !result.nil? && result && !new_build_hash.nil? && new_build_hash = current_hash
|
242
|
+
can_deploy = true
|
243
|
+
else
|
244
|
+
Kumastrano.say "there is still something wrong with #{current_branch} on #{application}, please check manually and try deploying again afterwards!"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
if !can_deploy
|
252
|
+
if Kumastrano.ask "no valid build found for #{current_hash} on branch #{current_branch}, do you still want to deploy?"
|
253
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, "#{Etc.getlogin.capitalize} ignored the fact there was something wrong with #{current_branch} on #{application} and still went on with deploying it!!"
|
254
|
+
else
|
255
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, "#{Etc.getlogin.capitalize} wanted to deploy #{current_branch} on #{application} but there is something wrong with the code, so he cancelled it!"
|
256
|
+
exit
|
257
|
+
end
|
258
|
+
else
|
259
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, "#{Etc.getlogin.capitalize} is deploying #{current_branch} for #{application}"
|
260
|
+
end
|
261
|
+
else
|
262
|
+
Kumastrano.say "jenkins on demand is disabled, skipping..."
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Before update_code:
|
267
|
+
## Make the cached_copy readable for the current user
|
268
|
+
before "deploy:update_code" do
|
269
|
+
user = Etc.getlogin
|
270
|
+
sudo "sh -c 'if [ -d #{shared_path}/cached-copy ] ; then chown -R #{user}:#{user} #{shared_path}/cached-copy; fi'" if deploy_via == :rsync_with_remote_cache || deploy_via == :remote_cache
|
271
|
+
end
|
272
|
+
|
273
|
+
# After update_code:
|
274
|
+
## Fix the permissions of the cached_copy so that it's readable for the project user
|
275
|
+
after "deploy:update_code" do
|
276
|
+
sudo "sh -c 'if [ -d #{shared_path}/cached-copy ] ; then chown -R #{application}:#{application} #{shared_path}/cached-copy; fi'" if deploy_via == :rsync_with_remote_cache || deploy_via == :remote_cache
|
277
|
+
end
|
278
|
+
|
279
|
+
# Before finalize_update:
|
280
|
+
## Create the parameters.ini if it's a symfony project
|
281
|
+
## Fix the permissions of the latest release, so that it's readable for the project user
|
282
|
+
before "deploy:finalize_update" do
|
283
|
+
sudo "sh -c 'if [ -d #{shared_path}/cached-copy ] ; then chmod -R ug+rx #{latest_release}/paramDecode; fi'"
|
284
|
+
sudo "sh -c 'if [ -f #{latest_release}/paramDecode ] ; then cd #{latest_release} && ./paramDecode; fi'" # Symfony specific: will generate the parameters.ini
|
285
|
+
sudo "chown -R #{application}:#{application} #{latest_release}"
|
286
|
+
sudo "setfacl -R -m group:admin:rwx #{latest_release}"
|
287
|
+
end
|
288
|
+
|
289
|
+
# After deploy:
|
290
|
+
## Notify the people on campfire of this deploy
|
291
|
+
## Notify airbrake to add a new deploy to the deploy history
|
292
|
+
after :deploy do
|
293
|
+
current_branch = Kumastrano::GitHelper.branch_name
|
294
|
+
Kumastrano::CampfireHelper.speak campfire_account, campfire_token, campfire_room, "#{Etc.getlogin.capitalize} successfuly deployed #{current_branch} for #{application}"
|
295
|
+
airbrake::notify
|
296
|
+
deploy::cleanup ## cleanup old releases
|
297
|
+
kuma::fixcron
|
298
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kstrano
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kunstmaan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capistrano
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.12.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.12.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: capistrano-ext
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.2.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.2.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: capistrano_colors
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.5.5
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.5.5
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: kcapifony
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.1.3
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.1.3
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: json
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.6.6
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.6.6
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: broach
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.2.1
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.2.1
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: highline
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.6.11
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.6.11
|
126
|
+
description: Deploying applications with Capistrano, Jenkins and GIT.
|
127
|
+
email: support@kunstmaan.be
|
128
|
+
executables:
|
129
|
+
- kumafy
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- lib/helpers/airbrake_helper.rb
|
134
|
+
- lib/helpers/campfire_helper.rb
|
135
|
+
- lib/helpers/git_helper.rb
|
136
|
+
- lib/helpers/jenkins_helper.rb
|
137
|
+
- lib/helpers/kuma_helper.rb
|
138
|
+
- lib/kstrano.rb
|
139
|
+
- bin/kumafy
|
140
|
+
homepage: https://github.com/Kunstmaan/kStrano
|
141
|
+
licenses: []
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ! '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 1.8.23
|
161
|
+
signing_key:
|
162
|
+
specification_version: 3
|
163
|
+
summary: Deploying applications with Capistrano, Jenkins and GIT.
|
164
|
+
test_files: []
|