kstrano 0.0.4
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/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: []
|