conan_deploy 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,95 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
class DefaultArtifactRepository
|
5
|
+
@@artifact_repository_url = 'http://nexus.mtnsatcloud.com/nexus'
|
6
|
+
@@artifact_repository_local_api_url = "#{@@artifact_repository_url}/service/local/artifact/maven"
|
7
|
+
|
8
|
+
def resolveArtifact(group_id, artifact_id, extension, version='LATEST')
|
9
|
+
# we only care about the releases repo, no snapshots please
|
10
|
+
RestClient.get("#{@@artifact_repository_local_api_url}/resolve?r=releases&g=#{group_id}&a=#{artifact_id}&v=#{version}&e=#{extension}")
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def downloadArtifact(output_dir, group_id, artifact_id, extension, version, sha1_hash)
|
15
|
+
if (extension == 'jar')
|
16
|
+
# put jars in a parent directory
|
17
|
+
output_dir = File.join(output_dir, "#{artifact_id}")
|
18
|
+
end
|
19
|
+
url="#{@@artifact_repository_local_api_url}/content?r=releases&g=#{group_id}&a=#{artifact_id}&v=#{version}&e=#{extension}"
|
20
|
+
target_file = File.join(output_dir, "#{artifact_id}.#{extension}")
|
21
|
+
HttpUtil.download(url, target_file, "#{artifact_id}-#{version}.#{extension}", sha1_hash)
|
22
|
+
|
23
|
+
# extract tar balls
|
24
|
+
if (extension == 'tar.gz')
|
25
|
+
dir = "#{output_dir}/#{artifact_id}"
|
26
|
+
FileUtils.rm_rf dir
|
27
|
+
FileUtils.mkdir_p dir
|
28
|
+
`tar -xf #{target_file} -C #{dir}`
|
29
|
+
# TODO fail if tar doesn't work
|
30
|
+
|
31
|
+
# extract deploy artifacts
|
32
|
+
elsif (extension == 'jar')
|
33
|
+
# templates
|
34
|
+
dir = "#{output_dir}/deploy-templates"
|
35
|
+
FileUtils.rm_rf dir
|
36
|
+
FileUtils.mkdir_p dir
|
37
|
+
c = "unzip -j #{target_file} 'META-INF/deploy/templates/*.erb' -d '#{dir}'"
|
38
|
+
system(c) or raise "unzip failed: #{c}"
|
39
|
+
|
40
|
+
# new relic extensions
|
41
|
+
dir = "#{output_dir}/extensions"
|
42
|
+
FileUtils.rm_rf dir
|
43
|
+
FileUtils.mkdir_p dir
|
44
|
+
c = "unzip -j #{target_file} 'META-INF/deploy/extensions/*.yml' -d '#{dir}'"
|
45
|
+
system(c) # extensions are optional, it's ok if this one fails
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def downloadNewRelicAgent(output_dir, nr_version, sha1_hash)
|
50
|
+
url = "#{@@artifact_repository_url}/content/groups/public/com/newrelic/agent/java/newrelic-agent/#{nr_version}/newrelic-agent-#{nr_version}.jar"
|
51
|
+
target_file = File.join(output_dir, "newrelic.jar")
|
52
|
+
HttpUtil.download(url, target_file, "newrelic-agent-#{nr_version}.jar", sha1_hash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module HttpUtil
|
57
|
+
def self.download(url, target_file, download_file_name, sha1_hash)
|
58
|
+
not_shasum = ->(file) {
|
59
|
+
ret = true
|
60
|
+
if (File.exists?(file))
|
61
|
+
# verify the file
|
62
|
+
found_hash = Digest::SHA1.file(file).hexdigest
|
63
|
+
puts "Found: #{found_hash}, Want: #{sha1_hash}"
|
64
|
+
if (found_hash != sha1_hash)
|
65
|
+
puts "#{file} is not the file we're looking for. It has a SHA1 hash of #{found_hash}, but #{sha1_hash} is expected"
|
66
|
+
ret = true
|
67
|
+
else
|
68
|
+
puts "#{file} matches checksum"
|
69
|
+
ret = false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
ret
|
73
|
+
}
|
74
|
+
|
75
|
+
if (not_shasum.call(target_file))
|
76
|
+
tmp = File.join(Dir.tmpdir(), "nexus-app-manifest")
|
77
|
+
FileUtils.mkdir_p tmp
|
78
|
+
download_file = File.join(tmp, download_file_name)
|
79
|
+
if (not_shasum.call(download_file))
|
80
|
+
# download it
|
81
|
+
puts "Fetching artifact #{download_file_name}"
|
82
|
+
puts " #{url}"
|
83
|
+
puts "..."
|
84
|
+
wget_cmd = "wget -nv \"#{url}\" --content-disposition --no-use-server-timestamps --output-document \"#{download_file}\""
|
85
|
+
puts wget_cmd
|
86
|
+
system( wget_cmd )
|
87
|
+
raise "Failed to download #{download_file_name}" if not_shasum.call(download_file)
|
88
|
+
end
|
89
|
+
# move download to target file
|
90
|
+
puts "cp #{download_file} to #{target_file}"
|
91
|
+
FileUtils.mkdir_p File.dirname(target_file)
|
92
|
+
FileUtils.copy(download_file, target_file)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Stackato
|
5
|
+
|
6
|
+
def initialize(paas_user, paas_password, trace=false, dry_run=false)
|
7
|
+
@paas_user = paas_user
|
8
|
+
@paas_password = paas_password
|
9
|
+
@doit = dry_run ? "echo " : ""
|
10
|
+
@trace = trace ? "--trace" : ""
|
11
|
+
random_str = (0...8).map { ('a'..'z').to_a[rand(26)] }.join
|
12
|
+
@paas_token_file = File.join(Dir.tmpdir(), "token_#{random_str}")
|
13
|
+
@paas_manifest = "stackato.yml"
|
14
|
+
end
|
15
|
+
|
16
|
+
def login(paas_target)
|
17
|
+
# login only one time per target, and only one target at a time
|
18
|
+
if (@session != paas_target)
|
19
|
+
@session = paas_target
|
20
|
+
@paas_cmd = "stackato #{@trace} --target #{paas_target} --token-file #{@paas_token_file}"
|
21
|
+
unless @paas_user.nil?
|
22
|
+
system( "#{@paas_cmd} login --user #{@paas_user} --password #{@paas_password}" ) or raise "Stackato login failed on #{paas_target}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def deploy(work_dir, app, deployment, force=false)
|
28
|
+
puts ''
|
29
|
+
Dir.chdir(work_dir){
|
30
|
+
login(deployment.paas_target)
|
31
|
+
|
32
|
+
paas_app_name = deployment.name(app.id)
|
33
|
+
appCreate = ->(cmd) {
|
34
|
+
c = "#{@doit} #{@paas_cmd} --manifest #{@paas_manifest} --no-prompt #{cmd} --no-start"
|
35
|
+
system(c) or raise "Stackato #{cmd} failed: #{c}"
|
36
|
+
}
|
37
|
+
appOperation = ->(cmd) {
|
38
|
+
c = "#{@doit} #{@paas_cmd} #{cmd} #{paas_app_name} --no-prompt"
|
39
|
+
system(c) or raise "Stackato #{cmd} failed: #{c}"
|
40
|
+
}
|
41
|
+
|
42
|
+
yield paas_app_name if block_given?
|
43
|
+
|
44
|
+
stats_status = `#{@paas_cmd} stats #{paas_app_name} 2>&1 >/dev/null`
|
45
|
+
puts stats_status
|
46
|
+
raise ScriptError, "Stackato authorization failure. Provide user and password" if (stats_status =~ /Not Authorized/)
|
47
|
+
|
48
|
+
# print out the stackato.yml for posterity
|
49
|
+
puts '------------------------------------------------------------------------------'
|
50
|
+
|
51
|
+
File.open(@paas_manifest, "r") do |f|
|
52
|
+
while (line = f.gets)
|
53
|
+
puts "#{line}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
puts '------------------------------------------------------------------------------'
|
57
|
+
puts ''
|
58
|
+
|
59
|
+
# A Error 301 will be returned from stackato if the app does not exist, this determins if we should push or update
|
60
|
+
if (stats_status =~ /Error 301/)
|
61
|
+
appCreate.call "push"
|
62
|
+
else
|
63
|
+
if (force)
|
64
|
+
puts "Replacing #{paas_app_name}. May be force be with you!"
|
65
|
+
appOperation.call "delete"
|
66
|
+
appCreate.call "push"
|
67
|
+
else
|
68
|
+
puts "Update #{paas_app_name}"
|
69
|
+
appOperation.call "stop"
|
70
|
+
appCreate.call "update"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "Starting #{paas_app_name}"
|
75
|
+
appOperation.call "start"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def bg_deploy(work_dir, app, deployment)
|
80
|
+
puts ''
|
81
|
+
Dir.chdir(work_dir){
|
82
|
+
login(deployment.paas_target)
|
83
|
+
|
84
|
+
app_info = application_info(app, deployment)
|
85
|
+
raise "Please remove inactive app '#{app_info.inactive_app_name}' before proceeding." unless app_info.inactive_app_name.nil?
|
86
|
+
|
87
|
+
puts "Deploying inactive app #{deployment.name(app.id)}"
|
88
|
+
c = "#{@doit} #{@paas_cmd} --manifest #{@paas_manifest} --no-prompt push"
|
89
|
+
system(c) or raise "Stackato push failed: #{c}"
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def bg_switch(app, deployment)
|
94
|
+
login(deployment.paas_target)
|
95
|
+
|
96
|
+
app_info = application_info(app, deployment)
|
97
|
+
|
98
|
+
if app_info.inactive_app_name.nil?
|
99
|
+
puts "No inactive app to switch to."
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
active_urls = deployment.active_urls(app.id)
|
104
|
+
inactive_urls = deployment.inactive_urls(app.id)
|
105
|
+
|
106
|
+
appUrlOp = ->(cmd, paas_app_name, url) {
|
107
|
+
c = "#{@doit} #{@paas_cmd} #{cmd} #{paas_app_name} #{url}"
|
108
|
+
system(c) or raise "Stackato #{cmd} failed: #{c}"
|
109
|
+
}
|
110
|
+
|
111
|
+
# For inactive app, map active urls to it and unmap inactive urls
|
112
|
+
puts "Mapping active urls"
|
113
|
+
active_urls.each {|active_url| appUrlOp.call("map", app_info.inactive_app_name, active_url)}
|
114
|
+
inactive_urls.each {|inactive_url| appUrlOp.call("unmap", app_info.inactive_app_name, inactive_url)}
|
115
|
+
|
116
|
+
# If active app exists, unmap active urls and map inactive urls to it
|
117
|
+
if app_info.active_app_name
|
118
|
+
puts "Mapping inactive urls"
|
119
|
+
inactive_urls.each {|inactive_url| appUrlOp.call("map", app_info.active_app_name, inactive_url)}
|
120
|
+
active_urls.each {|active_url| appUrlOp.call("unmap", app_info.active_app_name, active_url)}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def bg_clean(app, deployment)
|
125
|
+
login(deployment.paas_target)
|
126
|
+
|
127
|
+
app_info = application_info(app, deployment)
|
128
|
+
|
129
|
+
# Delete inactive app if it exists
|
130
|
+
if app_info.inactive_app_name
|
131
|
+
puts "Deleting inactive app."
|
132
|
+
c = "#{@doit} #{@paas_cmd} delete #{app_info.inactive_app_name} --no-prompt"
|
133
|
+
system(c) or raise "Stackato delete failed: #{c}"
|
134
|
+
else
|
135
|
+
puts "No inactive app to clean."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def application_info(app, deployment)
|
140
|
+
list_json = `#{@paas_cmd} list --json`
|
141
|
+
raise "Stackato list failed" if !$?.success?
|
142
|
+
|
143
|
+
base_name = deployment.name(app.id)
|
144
|
+
active_urls = deployment.active_urls(app.id)
|
145
|
+
inactive_urls = deployment.inactive_urls(app.id)
|
146
|
+
|
147
|
+
StackatoApplicationInfo.new(list_json, base_name, active_urls, inactive_urls)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class StackatoApplicationInfo
|
152
|
+
def initialize(list_json, base_name, active_urls, inactive_urls)
|
153
|
+
app_list = JSON.parse(list_json)
|
154
|
+
|
155
|
+
active_app_names = []
|
156
|
+
inactive_app_names = []
|
157
|
+
|
158
|
+
app_list.each { |app_hash|
|
159
|
+
if !(app_hash.has_key? 'name' and app_hash.has_key? 'uris')
|
160
|
+
raise "Stackato list json is malformed: " + list_json
|
161
|
+
end
|
162
|
+
|
163
|
+
app_name = app_hash['name']
|
164
|
+
app_uris = app_hash['uris']
|
165
|
+
|
166
|
+
if app_name.start_with? base_name
|
167
|
+
if app_uris.sort == active_urls.sort
|
168
|
+
active_app_names << app_name
|
169
|
+
elsif app_uris.sort == inactive_urls.sort
|
170
|
+
inactive_app_names << app_name
|
171
|
+
else
|
172
|
+
raise 'Stackato app "' + app_name +
|
173
|
+
'" needs to be mapped exclusively to either the active or inactive set of URLs. Please reconcile before continuing.'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
}
|
177
|
+
|
178
|
+
if active_app_names.length > 1
|
179
|
+
raise "More than one app is mapped to the active URL(s). Please reconcile before continuing."
|
180
|
+
end
|
181
|
+
if inactive_app_names.length > 1
|
182
|
+
raise "More than one app is mapped to the inactive URL(s). Please reconcile before continuing."
|
183
|
+
end
|
184
|
+
|
185
|
+
# These will be set to nil if no app is found, which is OK
|
186
|
+
@active_app_name = active_app_names.first
|
187
|
+
@inactive_app_name = inactive_app_names.first
|
188
|
+
end
|
189
|
+
|
190
|
+
def active_app_name
|
191
|
+
@active_app_name
|
192
|
+
end
|
193
|
+
|
194
|
+
def inactive_app_name
|
195
|
+
@inactive_app_name
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Templates
|
5
|
+
|
6
|
+
# Returns the number of files changed/created
|
7
|
+
def self.evaluate(templates_dir, output_dir, context)
|
8
|
+
no_changes = 0
|
9
|
+
FileUtils.mkdir_p File.dirname(output_dir)
|
10
|
+
Dir.glob(File.join(templates_dir, '*.erb')) do |template|
|
11
|
+
target_file = File.join(output_dir, File.basename(template)[0..-5])
|
12
|
+
eruby = Erubis::Eruby.new(File.read(template))
|
13
|
+
if (File.exist? target_file)
|
14
|
+
Tempfile.open(File.basename template) { |t|
|
15
|
+
t.write(eruby.evaluate(context))
|
16
|
+
t.rewind
|
17
|
+
if (FileUtils.identical? t.path, target_file)
|
18
|
+
puts "-- No change from: #{File.basename template} => #{target_file}"
|
19
|
+
else
|
20
|
+
puts ">> Replace file from template: #{File.basename template} => #{target_file}"
|
21
|
+
puts "------------------------------"
|
22
|
+
puts `diff -y -W 120 #{t.path} #{target_file}`
|
23
|
+
puts "------------------------------"
|
24
|
+
FileUtils.cp t.path, target_file
|
25
|
+
no_changes += 1
|
26
|
+
end
|
27
|
+
}
|
28
|
+
else
|
29
|
+
File.open(target_file, 'w') { |f|
|
30
|
+
puts "++ New file from template: #{File.basename template} => #{target_file}"
|
31
|
+
f.write(eruby.evaluate(context))
|
32
|
+
no_changes += 1
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
no_changes
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: conan_deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mike Reardon
|
9
|
+
- Michael Crane
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-04-09 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: Provision, configure, deploy and never hear da lamantation of da users
|
16
|
+
email: michael.reardon@mtnsat.com
|
17
|
+
executables:
|
18
|
+
- conan
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/conan
|
23
|
+
- lib/conan/deploy.rb
|
24
|
+
- lib/conan/manifest_builder.rb
|
25
|
+
- lib/conan/newrelic.rb
|
26
|
+
- lib/conan/options.rb
|
27
|
+
- lib/conan/output.rb
|
28
|
+
- lib/conan/repository.rb
|
29
|
+
- lib/conan/stackato.rb
|
30
|
+
- lib/conan/templates.rb
|
31
|
+
- lib/conan/version.rb
|
32
|
+
- README.md
|
33
|
+
homepage: http://github.com/MTNSatelliteComm/conan/README.md
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.23
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Conan da Deployer
|
57
|
+
test_files: []
|