conan_deploy 0.0.2
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 +216 -0
- data/bin/conan +105 -0
- data/lib/conan/deploy.rb +46 -0
- data/lib/conan/manifest_builder.rb +494 -0
- data/lib/conan/newrelic.rb +16 -0
- data/lib/conan/options.rb +31 -0
- data/lib/conan/output.rb +169 -0
- data/lib/conan/repository.rb +95 -0
- data/lib/conan/stackato.rb +197 -0
- data/lib/conan/templates.rb +38 -0
- data/lib/conan/version.rb +3 -0
- metadata +57 -0
@@ -0,0 +1,494 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'properties-ruby'
|
4
|
+
require 'json'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
require 'conan/output'
|
8
|
+
require 'conan/repository'
|
9
|
+
require 'conan/stackato'
|
10
|
+
require 'conan/newrelic'
|
11
|
+
|
12
|
+
module ManifestBuilder
|
13
|
+
def self.build(options, pipeline_id, env_id, artifact_repo=nil, &block)
|
14
|
+
manifest_dir = options[:directory]
|
15
|
+
output_type = options[:format]
|
16
|
+
|
17
|
+
|
18
|
+
artifact_repo ||= DefaultArtifactRepository.new
|
19
|
+
|
20
|
+
# this is a feature toggle to allow the current property file based impl to be migrated to the stackato output impl
|
21
|
+
if (output_type == :stackato)
|
22
|
+
outthingy = StackatoOutThingy.new(manifest_dir)
|
23
|
+
else
|
24
|
+
outthingy = PropertiesFileOutThingy.new(manifest_dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
manifest = Manifest.new(options, artifact_repo, outthingy)
|
28
|
+
manifest.instance_eval(&block)
|
29
|
+
manifest.select(pipeline_id, env_id)
|
30
|
+
manifest
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Manifest
|
35
|
+
|
36
|
+
def initialize(options, artifact_repo, output)
|
37
|
+
@pipelines = {}
|
38
|
+
@environments = {}
|
39
|
+
@options = options
|
40
|
+
@artifact_repo = artifact_repo
|
41
|
+
@output = output
|
42
|
+
end
|
43
|
+
|
44
|
+
# select a pipeline and environment to do operations on
|
45
|
+
def select(pipeline_id, env_id)
|
46
|
+
env = @environments[env_id]
|
47
|
+
raise ArgumentError.new "Invalid environment id: '#{env_id}'. Valid options are #{@environments.keys.join(", ")}" if env.nil?
|
48
|
+
@current_environment = env
|
49
|
+
pl = @pipelines[pipeline_id]
|
50
|
+
raise ArgumentError.new "Invalid pipeline id: '#{pipeline_id}'. Valid options are #{@pipelines.keys.join(", ")}" if pl.nil?
|
51
|
+
@current_pipeline = pl
|
52
|
+
pl.load!(env, @options[:'deploy-shipcloud'])
|
53
|
+
end
|
54
|
+
|
55
|
+
def pipeline(pipeline_id, &block)
|
56
|
+
pl = Pipeline.new(pipeline_id, @artifact_repo, @output)
|
57
|
+
pl.instance_eval(&block)
|
58
|
+
@pipelines[pipeline_id] = pl
|
59
|
+
pl
|
60
|
+
end
|
61
|
+
|
62
|
+
def environment(env_id, &block)
|
63
|
+
e = Environment.new(env_id)
|
64
|
+
e.instance_eval(&block)
|
65
|
+
@environments[env_id] = e
|
66
|
+
e
|
67
|
+
end
|
68
|
+
|
69
|
+
def appVersion(pipeline_id, app_id)
|
70
|
+
@pipelines[pipeline_id].appVersion(app_id)
|
71
|
+
end
|
72
|
+
|
73
|
+
def provision
|
74
|
+
validateThatEnvironmentIsSelected
|
75
|
+
# TODO banners
|
76
|
+
puts "Update #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
|
77
|
+
@current_pipeline.update(@current_environment, @environments[@current_environment.upstream_env])
|
78
|
+
end
|
79
|
+
|
80
|
+
def configure
|
81
|
+
validateThatEnvironmentIsSelected
|
82
|
+
puts "Configure #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
|
83
|
+
@current_pipeline.configure(@current_environment)
|
84
|
+
end
|
85
|
+
|
86
|
+
def bg_configure
|
87
|
+
validateThatEnvironmentIsSelected
|
88
|
+
puts "Blue/green configure #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
|
89
|
+
@current_pipeline.configure(@current_environment, true)
|
90
|
+
end
|
91
|
+
|
92
|
+
def paas
|
93
|
+
paas_user = @options[:'paas-user'] || ENV['PAAS_USER']
|
94
|
+
paas_pwd = @options[:'paas-password'] || ENV['PAAS_PASSWORD']
|
95
|
+
dry_run = @options[:'dry-run'] || false
|
96
|
+
trace = @options[:'verbose'] || false
|
97
|
+
|
98
|
+
Stackato.new(paas_user, paas_pwd, trace, dry_run)
|
99
|
+
end
|
100
|
+
|
101
|
+
def deploy
|
102
|
+
validateThatEnvironmentIsSelected
|
103
|
+
puts "Deploy #{@current_environment.id} for #{@current_pipeline.id} pipeline"
|
104
|
+
|
105
|
+
nr_api_key = @options[:'new-relic-api-key'] || ENV['NEWRELIC_API_KEY']
|
106
|
+
force = @options[:'force-deploy'] || false
|
107
|
+
|
108
|
+
@current_pipeline.deploy(@current_environment, paas, force) { |app_name|
|
109
|
+
if (nr_api_key)
|
110
|
+
puts 'Reporting Deployment to New Relic'
|
111
|
+
job = @options[:'job-url'] || 'nexus-app-manifest'
|
112
|
+
NewRelic.alertDeployment(nr_api_key, app_name, "Deployed by #{job}")
|
113
|
+
else
|
114
|
+
puts "WARNING: Skipping New Relic alert. No API defined"
|
115
|
+
end
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def bg_deploy
|
120
|
+
validateThatEnvironmentIsSelected
|
121
|
+
puts "Blue/green deploy #{@current_environment.id} for #{@current_pipeline.id} pipeline"
|
122
|
+
|
123
|
+
force = @options[:'force-deploy'] || false
|
124
|
+
|
125
|
+
@current_pipeline.bg_deploy(@current_environment, paas, force)
|
126
|
+
end
|
127
|
+
|
128
|
+
def bg_switch
|
129
|
+
validateThatEnvironmentIsSelected
|
130
|
+
puts "Blue/green switch #{@current_environment.id} for #{@current_pipeline.id} pipeline"
|
131
|
+
|
132
|
+
@current_pipeline.bg_switch(@current_environment, paas)
|
133
|
+
end
|
134
|
+
|
135
|
+
def bg_clean
|
136
|
+
validateThatEnvironmentIsSelected
|
137
|
+
puts "Blue/green clean #{@current_environment.id} for #{@current_pipeline.id} pipeline"
|
138
|
+
|
139
|
+
@current_pipeline.bg_clean(@current_environment, paas)
|
140
|
+
end
|
141
|
+
|
142
|
+
def validateThatEnvironmentIsSelected
|
143
|
+
unless @current_environment && @current_pipeline
|
144
|
+
raise ScriptError, 'Environment and Pipeline must be selected'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Pipeline
|
150
|
+
attr_accessor :id, :apps
|
151
|
+
|
152
|
+
def initialize(id, artifact_repo, output)
|
153
|
+
@id = id
|
154
|
+
@artifact_repo = artifact_repo
|
155
|
+
@output = output
|
156
|
+
@apps = {}
|
157
|
+
end
|
158
|
+
|
159
|
+
def app(app_id, project_name, platform_type)
|
160
|
+
@apps[app_id] = case platform_type
|
161
|
+
when :jvm
|
162
|
+
JvmApp.new(app_id, project_name, @artifact_repo)
|
163
|
+
when :rails_zip
|
164
|
+
RailsZipApp.new(app_id, project_name, @artifact_repo)
|
165
|
+
else
|
166
|
+
raise "unknown platform type: #{platform_type}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def appVersion(app_id)
|
171
|
+
@apps[app_id].version
|
172
|
+
end
|
173
|
+
|
174
|
+
def load!(environment, deploy_shipcloud)
|
175
|
+
eachAppDeployment(environment) { |app, deploy|
|
176
|
+
# load the persisted meta-data
|
177
|
+
@output.loadAppMetadataFromDeploymentIfExists(app, deploy)
|
178
|
+
}
|
179
|
+
@deploy_shipcloud = deploy_shipcloud
|
180
|
+
end
|
181
|
+
|
182
|
+
def update(environment, from_upstream_env)
|
183
|
+
@apps.values.each { |app|
|
184
|
+
puts ''
|
185
|
+
# promote versions from an upstream deploy
|
186
|
+
if (from_upstream_env)
|
187
|
+
# TODO: this needs improving
|
188
|
+
puts "Promote #{app.id} from #{from_upstream_env.id} to #{environment.id}"
|
189
|
+
upstream_deploy = from_upstream_env.deployments.select { |d| d.app_ids.select {|x| x == app.id }.size > 0 }.first
|
190
|
+
@output.loadAppMetadataFromDeployment(app, upstream_deploy)
|
191
|
+
else
|
192
|
+
puts "Find latest #{app.artifact_id}"
|
193
|
+
# lookup most recent from repo
|
194
|
+
app.findLatestVersion()
|
195
|
+
end
|
196
|
+
@output.clean(app, environment)
|
197
|
+
# download artifacts
|
198
|
+
@output.provision(app)
|
199
|
+
}
|
200
|
+
|
201
|
+
# write out deployment artifact meta-data
|
202
|
+
eachAppDeployment(environment) { |app, deploy| @output.writeArtifactMetadata(app, deploy) }
|
203
|
+
end
|
204
|
+
|
205
|
+
def configure(environment, bg=false)
|
206
|
+
eachAppDeployment(environment) { |app, deploy| @output.writeConfiguration(app, deploy, bg) }
|
207
|
+
end
|
208
|
+
|
209
|
+
def deploy(environment, paas, force=false)
|
210
|
+
eachAppDeployment(environment) { |app, deploy|
|
211
|
+
work_dir = @output.workingDir(app)
|
212
|
+
if (force)
|
213
|
+
paas.deploy(work_dir, app, deploy, true)
|
214
|
+
else
|
215
|
+
paas.deploy(work_dir, app, deploy) unless app.isDeployedAt(deploy.manifest_url(app.id))
|
216
|
+
end
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
def bg_deploy(environment, paas, force=false)
|
221
|
+
eachAppDeployment(environment) { |app, deploy|
|
222
|
+
if (force or !app.isDeployedAt(deploy.manifest_url(app.id)))
|
223
|
+
paas.bg_deploy(@output.workingDir(app), app, deploy)
|
224
|
+
end
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
def bg_switch(environment, paas)
|
229
|
+
eachAppDeployment(environment) { |app, deploy| paas.bg_switch(app, deploy) }
|
230
|
+
end
|
231
|
+
|
232
|
+
def bg_clean(environment, paas)
|
233
|
+
eachAppDeployment(environment) { |app, deploy| paas.bg_clean(app, deploy) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def eachAppDeployment(environment)
|
237
|
+
selected_deployments = environment.deployments.select{ |d| @deploy_shipcloud.nil? || d.shipcloud == @deploy_shipcloud }
|
238
|
+
puts "WARNING no deploys selected: #{@deploy_shipcloud}" if selected_deployments.empty?
|
239
|
+
selected_deployments.each { |d|
|
240
|
+
d.app_ids.each { |a|
|
241
|
+
app = @apps[a]
|
242
|
+
yield app, d if app
|
243
|
+
}
|
244
|
+
}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class Application
|
249
|
+
|
250
|
+
attr_accessor :id, :platform_type, :group_id, :artifact_id, :version, :extension, :sha1, :artifact_meta_data
|
251
|
+
|
252
|
+
def initialize(id, project_name, platform_type)
|
253
|
+
@id = id
|
254
|
+
@platform_type = platform_type
|
255
|
+
mvn_id = project_name.split(':')
|
256
|
+
@group_id = mvn_id[0]
|
257
|
+
@artifact_id = mvn_id[1]
|
258
|
+
end
|
259
|
+
|
260
|
+
def findLatestVersion
|
261
|
+
extension = case platform_type
|
262
|
+
when :jvm
|
263
|
+
"jar"
|
264
|
+
when :rails_zip
|
265
|
+
"tar.gz"
|
266
|
+
else
|
267
|
+
raise "Unsupported platform type: #{platform_type}"
|
268
|
+
end
|
269
|
+
readArtifactMetadata(@artifact_repo.resolveArtifact(group_id, artifact_id, extension))
|
270
|
+
puts "- Found latest #{@id} artifact\n"+
|
271
|
+
" Artifact: #{self}\n" +
|
272
|
+
" Checksum: #{@sha1}"
|
273
|
+
end
|
274
|
+
|
275
|
+
def readArtifactMetadata(xml)
|
276
|
+
@artifact_meta_data = xml
|
277
|
+
begin
|
278
|
+
doc = REXML::Document.new(xml)
|
279
|
+
rescue Exception => e
|
280
|
+
puts "Failed to parse meta-data: #{e}"
|
281
|
+
puts "--------------------------------"
|
282
|
+
puts xml
|
283
|
+
puts "--------------------------------"
|
284
|
+
raise "Unreadable artifact meta-data for #{self}"
|
285
|
+
end
|
286
|
+
|
287
|
+
meta_data = doc.elements['/artifact-resolution/data']
|
288
|
+
@version = meta_data.elements['version'].text
|
289
|
+
@sha1 = meta_data.elements['sha1'].text
|
290
|
+
@extension = meta_data.elements['extension'].text
|
291
|
+
end
|
292
|
+
|
293
|
+
def download(location)
|
294
|
+
puts "- Provision #{group_id}:#{artifact_id}:#{extension}:#{version}"
|
295
|
+
@artifact_repo.downloadArtifact(location, group_id, artifact_id, extension, version, sha1)
|
296
|
+
end
|
297
|
+
|
298
|
+
def isDeployedAt(url)
|
299
|
+
# only deploy if an older version is found, or no app found
|
300
|
+
res = false
|
301
|
+
begin
|
302
|
+
response = RestClient::Request.new(
|
303
|
+
:method => :get,
|
304
|
+
:url => url,
|
305
|
+
:headers => {
|
306
|
+
'accept' => :json,
|
307
|
+
'content_type' => :json
|
308
|
+
},
|
309
|
+
:timeout => 5,
|
310
|
+
:open_timeout => 5
|
311
|
+
).execute
|
312
|
+
if (response.code == 200)
|
313
|
+
build_metadata = JSON.parse(response.to_str)
|
314
|
+
puts ''
|
315
|
+
puts "+- Currently at #{url}:"
|
316
|
+
printM = ->(s) {
|
317
|
+
puts " | #{s}: #{build_metadata[s]}"
|
318
|
+
}
|
319
|
+
printM.call 'Implementation-Title'
|
320
|
+
printM.call 'Build-Version'
|
321
|
+
printM.call 'Build-Url'
|
322
|
+
printM.call 'Git-Commit'
|
323
|
+
current_ver = build_metadata["Build-Version"]
|
324
|
+
end
|
325
|
+
#TODO make sure the artifact id matches too, currently finding "Implementation-Title": "apollo" in build metadata
|
326
|
+
if (compareVersion(@version, current_ver))
|
327
|
+
puts "#{@artifact_id} #{@version} is newer than version found at #{url}: #{current_ver}"
|
328
|
+
else
|
329
|
+
puts "#{@artifact_id} #{@version} found at #{url}. (skipping deployment)"
|
330
|
+
res = true
|
331
|
+
end
|
332
|
+
rescue => e
|
333
|
+
puts "#{@artifact_id} not found at #{url}: #{e}"
|
334
|
+
end
|
335
|
+
res
|
336
|
+
end
|
337
|
+
|
338
|
+
def compareVersion(target_version, deployed_version)
|
339
|
+
target = Gem::Version.new(target_version)
|
340
|
+
begin
|
341
|
+
found = Gem::Version.new(deployed_version)
|
342
|
+
target > found
|
343
|
+
rescue => e
|
344
|
+
puts "Error parsing version number: #{e}"
|
345
|
+
false
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def to_s
|
350
|
+
"#{artifact_id}-#{version}.#{extension}"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class JvmApp < Application
|
355
|
+
attr_accessor :sha1, :artifact_repo
|
356
|
+
|
357
|
+
def initialize(id, project_name, artifact_repo)
|
358
|
+
super(id, project_name, :jvm)
|
359
|
+
@sha1 = nil
|
360
|
+
@artifact_repo = artifact_repo
|
361
|
+
end
|
362
|
+
|
363
|
+
def download(location)
|
364
|
+
super
|
365
|
+
puts "- Provision NewRelic Agent jar"
|
366
|
+
@artifact_repo.downloadNewRelicAgent(File.join(location, @artifact_id), '3.2.3', 'c07cd82e033af9628cd7f90df874daee7000e471')
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class RailsZipApp < Application
|
371
|
+
attr_accessor :sha1, :artifact_repo
|
372
|
+
|
373
|
+
def initialize(id, project_name, artifact_repo)
|
374
|
+
super(id, project_name, :rails_zip)
|
375
|
+
@sha1 = nil
|
376
|
+
@artifact_repo = artifact_repo
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
class HasOptions
|
381
|
+
attr_accessor :options
|
382
|
+
|
383
|
+
def initialize(options)
|
384
|
+
@options = options
|
385
|
+
end
|
386
|
+
|
387
|
+
def option(key, value)
|
388
|
+
if @options[key].nil?
|
389
|
+
@options[key] = [value]
|
390
|
+
else
|
391
|
+
@options[key] << value
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class Environment #< HasOptions
|
397
|
+
attr_accessor :id, :upstream_env, :deployments
|
398
|
+
|
399
|
+
def initialize(env_id)
|
400
|
+
#super({})
|
401
|
+
@id = env_id
|
402
|
+
@deployments = []
|
403
|
+
@org_holder = nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def promotes_from(env_id)
|
407
|
+
@upstream_env = env_id
|
408
|
+
end
|
409
|
+
|
410
|
+
def org(org_name, &block)
|
411
|
+
@org_holder = org_name
|
412
|
+
self.instance_eval(&block)
|
413
|
+
end
|
414
|
+
|
415
|
+
def deploy(ship, &block)
|
416
|
+
d = Deployment.new(self, @org_holder, ship)
|
417
|
+
d.instance_eval(&block)
|
418
|
+
@deployments << d
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
class Deployment < HasOptions
|
423
|
+
|
424
|
+
@@paas_domain = 'mtnsatcloud.com'
|
425
|
+
|
426
|
+
attr_accessor :environment, :org, :ship, :shipcloud, :app_ids, :facility_id
|
427
|
+
|
428
|
+
def initialize(environment, org, ship)
|
429
|
+
# inherit options from the environment
|
430
|
+
#super(environment.options) # TODO: copy?!
|
431
|
+
super({})
|
432
|
+
@environment = environment
|
433
|
+
@org = org
|
434
|
+
@ship = ship
|
435
|
+
@shipcloud = "#{org}-#{ship}"
|
436
|
+
@app_ids = []
|
437
|
+
@facility_id = nil
|
438
|
+
@enabled = true
|
439
|
+
@randomid = SecureRandom.hex(3)
|
440
|
+
end
|
441
|
+
|
442
|
+
def apps(*app_ids)
|
443
|
+
@app_ids = app_ids
|
444
|
+
end
|
445
|
+
|
446
|
+
def facility(facility_id)
|
447
|
+
@facility_id = facility_id
|
448
|
+
end
|
449
|
+
|
450
|
+
def enabled(b)
|
451
|
+
@enabled = b
|
452
|
+
end
|
453
|
+
|
454
|
+
def enabled?
|
455
|
+
@enabled
|
456
|
+
end
|
457
|
+
|
458
|
+
def name(app_id)
|
459
|
+
"#{app_id}-#{@environment.id}-#{@ship}"
|
460
|
+
end
|
461
|
+
|
462
|
+
def dns_name(app_id)
|
463
|
+
"#{@environment.id}.#{app_id}.#{@ship}.#{@org}.#{@@paas_domain}"
|
464
|
+
end
|
465
|
+
|
466
|
+
def agnostic_dns_name(app_id)
|
467
|
+
"#{@environment.id}.#{app_id}.#{@@paas_domain}"
|
468
|
+
end
|
469
|
+
|
470
|
+
def unique_name(app_id)
|
471
|
+
"#{name(app_id)}-#{@randomid}"
|
472
|
+
end
|
473
|
+
|
474
|
+
def active_urls(app_id)
|
475
|
+
[ dns_name(app_id), agnostic_dns_name(app_id) ]
|
476
|
+
end
|
477
|
+
|
478
|
+
def inactive_urls(app_id)
|
479
|
+
["inactive.#{dns_name(app_id)}"]
|
480
|
+
end
|
481
|
+
|
482
|
+
def manifest_url(app_id)
|
483
|
+
"http://#{dns_name(app_id)}/status/manifest"
|
484
|
+
end
|
485
|
+
|
486
|
+
def paas_target
|
487
|
+
"https://api.paas.#{@ship}.#{@org}.#{@@paas_domain}"
|
488
|
+
end
|
489
|
+
|
490
|
+
def to_s
|
491
|
+
"#{org}-#{ship}"
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
module NewRelic
|
4
|
+
def self.alertDeployment(api_key, app_name, description)
|
5
|
+
response = RestClient.post(
|
6
|
+
'https://api.newrelic.com/deployments.xml',
|
7
|
+
:params => {
|
8
|
+
:'deployment[app_name]' => app_name,
|
9
|
+
:'deployment[description]' => description
|
10
|
+
},
|
11
|
+
:headers => { 'x-api-key' => api_key }
|
12
|
+
)
|
13
|
+
puts response
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'conan/version'
|
2
|
+
|
3
|
+
module Conan
|
4
|
+
class Options
|
5
|
+
def initialize(options = {})
|
6
|
+
@options = defaults.merge(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def []=(k, v)
|
10
|
+
@options[k] = v
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](k)
|
14
|
+
@options[k]
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@options.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def defaults
|
23
|
+
{
|
24
|
+
:version => Conan::VERSION,
|
25
|
+
:directory => Dir.pwd,
|
26
|
+
:format => :stackato,
|
27
|
+
:action => :provision
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/conan/output.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'conan/templates'
|
2
|
+
|
3
|
+
class OutThingy
|
4
|
+
attr_accessor :output_dir, :time
|
5
|
+
|
6
|
+
def initialize(output_dir)
|
7
|
+
@output_dir = output_dir
|
8
|
+
@time = Time.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def outputToFile(file, &block)
|
12
|
+
o = File.join(@output_dir, file)
|
13
|
+
puts "Write: #{o}"
|
14
|
+
FileUtils.mkdir_p File.dirname(o)
|
15
|
+
File.open(o, 'w') { |f| yield f }
|
16
|
+
end
|
17
|
+
|
18
|
+
def loadAppMetadataFromDeploymentIfExists(app, deploy)
|
19
|
+
loadAppMetadataFromDeployment(app, deploy, true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class StackatoOutThingy < OutThingy
|
24
|
+
|
25
|
+
attr_accessor :environments_dir, :apps_dir
|
26
|
+
|
27
|
+
@@stackato_file_name = "stackato.yml"
|
28
|
+
@@metadata_file_name = "metadata.xml"
|
29
|
+
@environments_dir = nil
|
30
|
+
@apps_dir = nil
|
31
|
+
|
32
|
+
def initialize(output_dir)
|
33
|
+
super(output_dir)
|
34
|
+
workdir = File.join(output_dir,'tmp')
|
35
|
+
@environments_dir = File.join(output_dir,'environments')
|
36
|
+
@apps_dir = File.join(workdir,'apps')
|
37
|
+
FileUtils.mkdir_p environments_dir
|
38
|
+
FileUtils.mkdir_p apps_dir
|
39
|
+
end
|
40
|
+
|
41
|
+
def clean(app, environment)
|
42
|
+
# TODO: cleanup
|
43
|
+
end
|
44
|
+
|
45
|
+
def provision(app)
|
46
|
+
app.download(@apps_dir)
|
47
|
+
end
|
48
|
+
|
49
|
+
def filePath(app, deploy)
|
50
|
+
"#{deploy.environment.id}/#{deploy.shipcloud}/#{app.id}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def workingDir(app)
|
54
|
+
File.join(apps_dir, app.artifact_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO this needs a refactor, it's knoted up with App
|
58
|
+
def loadAppMetadataFromDeployment(app, deploy, fail_silent=false)
|
59
|
+
p = filePath(app, deploy)
|
60
|
+
path = File.join(@environments_dir, p)
|
61
|
+
metadata_file = File.join(path, @@metadata_file_name)
|
62
|
+
|
63
|
+
if (File.exists? path)
|
64
|
+
File.open(metadata_file, 'r') { |f| app.readArtifactMetadata(f.read) }
|
65
|
+
else
|
66
|
+
raise "Build meta-data was not found: #{path}" unless fail_silent
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def writeArtifactMetadata(app, deploy)
|
71
|
+
if (deploy.enabled?)
|
72
|
+
path = filePath(app, deploy)
|
73
|
+
outputToFile("environments/#{path}/#{@@metadata_file_name}") { |f|
|
74
|
+
f.write(app.artifact_meta_data)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def writeConfiguration(app, deploy, bg=false)
|
80
|
+
if (deploy.enabled?)
|
81
|
+
target_dir = workingDir(app)
|
82
|
+
templates_dir = File.join(target_dir, "deploy-templates")
|
83
|
+
n_changes = Templates.evaluate templates_dir, target_dir, {
|
84
|
+
:app => app,
|
85
|
+
:deploy => deploy,
|
86
|
+
|
87
|
+
# short-cuts
|
88
|
+
:app_id => app.id,
|
89
|
+
:deploy_base_name => deploy.name(app.id),
|
90
|
+
:deploy_name => bg ? deploy.unique_name(app.id) : deploy.name(app.id),
|
91
|
+
:deploy_urls => bg ? deploy.inactive_urls(app.id) : deploy.active_urls(app.id),
|
92
|
+
:env => deploy.environment.id,
|
93
|
+
:org => deploy.org,
|
94
|
+
:ship => deploy.ship,
|
95
|
+
:infra => deploy.shipcloud, # TODO: remove infra key, this is just to avoid breaking existing templates
|
96
|
+
:shipcloud => deploy.shipcloud,
|
97
|
+
:facility => deploy.facility_id,
|
98
|
+
:options => deploy.options
|
99
|
+
}
|
100
|
+
end
|
101
|
+
#TODO: can this be used to provide idempotency for config-only deploymnent?
|
102
|
+
puts "#{n_changes} files changed"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class PropertiesFileOutThingy < OutThingy
|
107
|
+
|
108
|
+
def clean(app, environment)
|
109
|
+
# cleanup current
|
110
|
+
FileUtils.rm Dir.glob("#{@output_dir}/#{app.id}-deploy-#{environment.id}-*.properties")
|
111
|
+
end
|
112
|
+
|
113
|
+
def fileName(app, deploy)
|
114
|
+
"#{app.id}-deploy-#{deploy.environment.id}-#{deploy.shipcloud}.properties"
|
115
|
+
end
|
116
|
+
|
117
|
+
def loadAppMetadataFromDeployment(app, deploy, fail_silent=false)
|
118
|
+
path = File.join(@output_dir, fileName(app, deploy))
|
119
|
+
if (File.exists? path)
|
120
|
+
props = Utils::Properties.load_from_file(path, true)
|
121
|
+
app.version = props.get(:DEPLOY_VERSION) # TODO: preserve SHA1
|
122
|
+
else
|
123
|
+
raise "File not found: #{path}" unless fail_silent
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def writeArtifactMetadata(app, deploy)
|
128
|
+
file_name = fileName(app, deploy)
|
129
|
+
out_file = File.join(@output_dir, file_name)
|
130
|
+
if (!deploy.enabled?)
|
131
|
+
if (File.exists? out_file)
|
132
|
+
File.delete(out_file)
|
133
|
+
end
|
134
|
+
out_file = File.join(@output_dir, "DISABLED-#{file_name}")
|
135
|
+
end
|
136
|
+
puts "Write: #{out_file}"
|
137
|
+
File.open(out_file, 'w') { |f|
|
138
|
+
# TODO: would like @time in the manifest as audit field, but not sure it's such a good idea yet
|
139
|
+
# f << "\# #{file_name}\n\# #{@time}\n"
|
140
|
+
f << "\# #{file_name}\n"
|
141
|
+
f << "DEPLOY_ENV=#{deploy.environment.id}\n"
|
142
|
+
f << "DEPLOY_INFRA=#{deploy.shipcloud}\n"
|
143
|
+
if (deploy.facility_id)
|
144
|
+
f << "FACILITY=facility-#{deploy.facility_id}\n"
|
145
|
+
end
|
146
|
+
unless (deploy.options.empty?)
|
147
|
+
options = []
|
148
|
+
deploy.options.each do |k, v|
|
149
|
+
options << k + '|' + v.join(',')
|
150
|
+
end
|
151
|
+
p options.join(';')
|
152
|
+
f << "OPTIONS=#{options.join(';')}\n"
|
153
|
+
end
|
154
|
+
f << "APP=#{app.id}\n"
|
155
|
+
f << "DEPLOY_SHA1=#{app.sha1}\n"
|
156
|
+
f << "DEPLOY_VERSION=#{app.version}\n"
|
157
|
+
f << "TYPE=#{app.platform_type}\n"
|
158
|
+
f << "\n"
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def provision(app)
|
163
|
+
#noop
|
164
|
+
end
|
165
|
+
|
166
|
+
def writeConfiguration(app, deploy)
|
167
|
+
# noop
|
168
|
+
end
|
169
|
+
end
|