conan_deploy 0.0.9 → 0.0.12
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/conan +39 -21
- data/lib/conan/application.rb +164 -0
- data/lib/conan/application_helper.rb +2 -1
- data/lib/conan/deploy.rb +12 -10
- data/lib/conan/deployment.rb +93 -0
- data/lib/conan/environment.rb +24 -0
- data/lib/conan/has_options.rb +15 -0
- data/lib/conan/jvm_app.rb +16 -0
- data/lib/conan/manifest.rb +120 -0
- data/lib/conan/manifest_builder.rb +3 -530
- data/lib/conan/output.rb +24 -24
- data/lib/conan/pipeline.rb +130 -0
- data/lib/conan/rails_zip_app.rb +10 -0
- data/lib/conan/stackato.rb +65 -11
- data/lib/conan/version.rb +2 -2
- metadata +18 -6
- checksums.yaml +0 -15
data/bin/conan
CHANGED
@@ -1,10 +1,28 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
2
|
+
require 'rest_client'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'json'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'daphne_util'
|
7
|
+
require 'optparse'
|
8
|
+
require 'conan/application'
|
9
|
+
require 'conan/application_helper'
|
3
10
|
require 'conan/deploy'
|
4
|
-
require 'conan/
|
11
|
+
require 'conan/deployment'
|
12
|
+
require 'conan/environment'
|
13
|
+
require 'conan/has_options'
|
14
|
+
require 'conan/jvm_app'
|
15
|
+
require 'conan/manifest'
|
16
|
+
require 'conan/manifest_builder'
|
17
|
+
require 'conan/newrelic'
|
5
18
|
require 'conan/options'
|
6
|
-
require '
|
7
|
-
|
19
|
+
require 'conan/output'
|
20
|
+
require 'conan/pipeline'
|
21
|
+
require 'conan/rails_zip_app'
|
22
|
+
require 'conan/repository'
|
23
|
+
require 'conan/stackato'
|
24
|
+
require 'conan/templates'
|
25
|
+
require 'conan/version'
|
8
26
|
options = {}
|
9
27
|
|
10
28
|
optparse = OptionParser.new do |o|
|
@@ -14,12 +32,12 @@ optparse = OptionParser.new do |o|
|
|
14
32
|
o.separator " environment - name of a the target environment: integ,stage,prod"
|
15
33
|
o.separator ""
|
16
34
|
o.separator " Options:"
|
17
|
-
|
35
|
+
|
18
36
|
o.on( '-d', '--output-dir [DIR]', 'Generate manifest data to this directory' ) do |dir|
|
19
37
|
options[:directory] = dir
|
20
38
|
end
|
21
39
|
|
22
|
-
o.on( '-f', '--output-format [FORMAT]', [:properties, :stackato],
|
40
|
+
o.on( '-f', '--output-format [FORMAT]', [:properties, :stackato],
|
23
41
|
'The format used to write output. Either "properties" for Jenkins build properties',
|
24
42
|
' or "stackato" (default) to provision binaries and write stackato yml files',
|
25
43
|
' from project templates.' ) do |format|
|
@@ -29,14 +47,14 @@ optparse = OptionParser.new do |o|
|
|
29
47
|
puts "Unsupported --format option"
|
30
48
|
puts o
|
31
49
|
exit
|
32
|
-
end
|
50
|
+
end
|
33
51
|
end
|
34
52
|
|
35
53
|
o.on('-a','--action [ACTION]', [:provision,:configure,:deploy,:bg_configure,:bg_deploy,:bg_switch,:bg_clean,:all,:bg_all],
|
36
54
|
'The action(s) to perform. Either "provision" (default), "configure","deploy" or "all".',
|
37
55
|
' Support for blue/green deployments use actions: "provision", "bg_configure", "bg_deploy",',
|
38
56
|
' "bg_switch", "bg_clean" or "bg_all".') do |action|
|
39
|
-
if (action)
|
57
|
+
if (action)
|
40
58
|
options[:action] = action
|
41
59
|
else
|
42
60
|
puts "Unsupported --action option"
|
@@ -44,19 +62,19 @@ optparse = OptionParser.new do |o|
|
|
44
62
|
exit
|
45
63
|
end
|
46
64
|
end
|
47
|
-
|
65
|
+
|
48
66
|
o.on('-u','--paas-user [USER]', 'The stackato user name for deployments' ) do |user|
|
49
67
|
options[:'paas-user'] = user
|
50
68
|
end
|
51
69
|
|
52
70
|
o.on('-p','--paas-password [PASSWORD]', 'The stackato password name for deployments' ) do |pw|
|
53
71
|
options[:'paas-password'] = pw
|
54
|
-
end
|
72
|
+
end
|
55
73
|
|
56
74
|
o.on('-F','--force-deploy', 'Force deployment to bypass version checks.' ) do
|
57
75
|
options[:'force-deploy'] = true
|
58
|
-
end
|
59
|
-
|
76
|
+
end
|
77
|
+
|
60
78
|
o.on('-N', '--new-relic-api-key', 'New Relic API key for deployment alerts') do |api_key|
|
61
79
|
options[:'new-relic-api-key'] = api_key
|
62
80
|
end
|
@@ -75,7 +93,7 @@ optparse = OptionParser.new do |o|
|
|
75
93
|
|
76
94
|
o.on('-D','--dry-run', 'Deployment dry-run. Prints Stackato commands' ) do
|
77
95
|
options[:'dry-run'] = true
|
78
|
-
end
|
96
|
+
end
|
79
97
|
|
80
98
|
o.on('-V','--verbose', 'Verbose console output' ) do
|
81
99
|
options[:verbose] = true
|
@@ -83,9 +101,9 @@ optparse = OptionParser.new do |o|
|
|
83
101
|
|
84
102
|
o.on("-v", "--version", "Show the software version") do
|
85
103
|
puts Conan::VERSION
|
86
|
-
exit
|
104
|
+
exit
|
87
105
|
end
|
88
|
-
|
106
|
+
|
89
107
|
o.on_tail( '-h', '--help', 'Display this screen' ) do
|
90
108
|
puts o
|
91
109
|
exit
|
@@ -94,20 +112,20 @@ end
|
|
94
112
|
optparse.parse!
|
95
113
|
|
96
114
|
|
97
|
-
|
115
|
+
if (ARGV.size < 1)
|
98
116
|
puts optparse
|
99
117
|
puts ''
|
100
118
|
raise ArgumentError, "No pipeline name argument was provided, e.g. nexus-apps-deploy"
|
101
119
|
else
|
102
|
-
ARGV[0].downcase.to_sym
|
120
|
+
options[:pipeline] = ARGV[0].downcase.to_sym
|
103
121
|
end
|
104
122
|
|
105
|
-
|
123
|
+
if (ARGV.size < 2)
|
106
124
|
puts optparse
|
107
|
-
puts ''
|
125
|
+
puts ''
|
108
126
|
raise ArgumentError, "No environment name argument was provided, e.g. integ"
|
109
127
|
else
|
110
|
-
ARGV[1].downcase.to_sym
|
128
|
+
options[:environment] = ARGV[1].downcase.to_sym
|
111
129
|
end
|
112
130
|
|
113
|
-
Conan::Deploy.run(Conan::Options.new(options)
|
131
|
+
Conan::Deploy.run(Conan::Options.new(options))
|
@@ -0,0 +1,164 @@
|
|
1
|
+
|
2
|
+
class Application
|
3
|
+
|
4
|
+
attr_accessor :id, :platform_type, :group_id, :artifact_id, :version, :extension, :sha1, :artifact_meta_data, :url_segment,
|
5
|
+
:unique_name, :additional_mappings, :org, :infra, :smoke_test_path
|
6
|
+
|
7
|
+
def initialize(id, project_name, platform_type, options, url_segment=nil)
|
8
|
+
@id = id
|
9
|
+
@platform_type = platform_type
|
10
|
+
@url_segment = url_segment || id
|
11
|
+
mvn_id = project_name.split(':')
|
12
|
+
@options = options
|
13
|
+
@org = options[:'deploy-shipcloud'].split('-')[0]
|
14
|
+
@infra = options[:'deploy-shipcloud'].split('-')[1]
|
15
|
+
@environment = options[:environment]
|
16
|
+
@group_id = mvn_id[0]
|
17
|
+
@artifact_id = mvn_id[1]
|
18
|
+
@smoke_test_path = 'status/healthcheck'
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_inactive_node_operational?
|
22
|
+
puts "Checking inactive app at #{inactive_healthcheck_url}"
|
23
|
+
!!(RestClient.get(inactive_healthcheck_url))
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_active_node_operational?
|
29
|
+
puts "Checking active app at #{inactive_healthcheck_url}"
|
30
|
+
!!(RestClient.get(active_healthcheck_url))
|
31
|
+
rescue
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def inactive_healthcheck_url
|
36
|
+
"http://inactive.#{full_backchannel_domain}/#{@smoke_test_path}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def active_healthcheck_url
|
40
|
+
"http://#{full_backchannel_domain}/#{@smoke_test_path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def old_versions
|
44
|
+
@old_apps ||= Stackato.find_old_versions(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def stackato_base_name
|
48
|
+
"#{@id}-#{@environment}-#{@infra}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def findRequestedVersion(requested_version='LATEST')
|
52
|
+
puts("Finding requested version: #{requested_version}")
|
53
|
+
extension = case platform_type
|
54
|
+
when :jvm
|
55
|
+
"jar"
|
56
|
+
when :rails_zip
|
57
|
+
"tar.gz"
|
58
|
+
else
|
59
|
+
raise "Unsupported platform type: #{platform_type}"
|
60
|
+
end
|
61
|
+
readArtifactMetadata(@artifact_repo.resolveArtifact(group_id, artifact_id, extension, requested_version))
|
62
|
+
puts "- Found version #{version} #{@id} artifact\n"+
|
63
|
+
" Artifact: #{self}\n" +
|
64
|
+
" Checksum: #{@sha1}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def readArtifactMetadata(xml)
|
68
|
+
@artifact_meta_data = xml
|
69
|
+
begin
|
70
|
+
doc = REXML::Document.new(xml)
|
71
|
+
rescue Exception => e
|
72
|
+
puts "Failed to parse meta-data: #{e}"
|
73
|
+
puts "--------------------------------"
|
74
|
+
puts xml
|
75
|
+
puts "--------------------------------"
|
76
|
+
raise "Unreadable artifact meta-data for #{self}"
|
77
|
+
end
|
78
|
+
|
79
|
+
meta_data = doc.elements['/artifact-resolution/data']
|
80
|
+
@version = meta_data.elements['version'].text
|
81
|
+
@sha1 = meta_data.elements['sha1'].text
|
82
|
+
@extension = meta_data.elements['extension'].text
|
83
|
+
end
|
84
|
+
|
85
|
+
def download(location)
|
86
|
+
puts "- Provision #{group_id}:#{artifact_id}:#{extension}:#{version}"
|
87
|
+
@artifact_repo.downloadArtifact(location, group_id, artifact_id, extension, version, sha1)
|
88
|
+
end
|
89
|
+
|
90
|
+
def isDeployedAt(url)
|
91
|
+
# only deploy if an older version is found, or no app found
|
92
|
+
res = false
|
93
|
+
begin
|
94
|
+
response = RestClient::Request.new(
|
95
|
+
:method => :get,
|
96
|
+
:url => url,
|
97
|
+
:headers => {
|
98
|
+
'accept' => :json,
|
99
|
+
'content_type' => :json
|
100
|
+
},
|
101
|
+
:timeout => 5,
|
102
|
+
:open_timeout => 5
|
103
|
+
).execute
|
104
|
+
if (response.code == 200)
|
105
|
+
build_metadata = JSON.parse(response.to_str)
|
106
|
+
puts ''
|
107
|
+
puts "+- Currently at #{url}:"
|
108
|
+
printM = ->(s) {
|
109
|
+
puts " | #{s}: #{build_metadata[s]}"
|
110
|
+
}
|
111
|
+
printM.call 'Implementation-Title'
|
112
|
+
printM.call 'Build-Version'
|
113
|
+
printM.call 'Build-Url'
|
114
|
+
printM.call 'Git-Commit'
|
115
|
+
current_ver = build_metadata["Build-Version"]
|
116
|
+
end
|
117
|
+
#TODO make sure the artifact id matches too, currently finding "Implementation-Title": "apollo" in build metadata
|
118
|
+
if (compareVersion(@version, current_ver))
|
119
|
+
puts "#{@artifact_id} #{@version} is newer than version found at #{url}: #{current_ver}"
|
120
|
+
else
|
121
|
+
puts "#{@artifact_id} #{@version} found at #{url}. (skipping deployment)"
|
122
|
+
res = true
|
123
|
+
end
|
124
|
+
rescue => e
|
125
|
+
puts "#{@artifact_id} not found at #{url}: #{e}"
|
126
|
+
end
|
127
|
+
res
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def compareVersion(target_version, deployed_version)
|
132
|
+
target = Gem::Version.new(target_version)
|
133
|
+
begin
|
134
|
+
found = Gem::Version.new(deployed_version)
|
135
|
+
target > found
|
136
|
+
rescue => e
|
137
|
+
puts "Error parsing version number: #{e}"
|
138
|
+
false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
"#{artifact_id}-#{version}.#{extension}"
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def active_domains
|
148
|
+
[full_backchannel_domain, infra_agnostic_backchannel_domain] + Array(@additional_mappings)
|
149
|
+
end
|
150
|
+
|
151
|
+
def inactive_domains
|
152
|
+
active_domains.map{|domain| "inactive.#{domain}"}
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def full_backchannel_domain
|
158
|
+
"#{@environment}.#{@url_segment}.#{@infra}.#{@org}.mtnsatcloud.com"
|
159
|
+
end
|
160
|
+
|
161
|
+
def infra_agnostic_backchannel_domain
|
162
|
+
"#{@environment}.#{@url_segment}.mtnsatcloud.com"
|
163
|
+
end
|
164
|
+
end
|
data/lib/conan/deploy.rb
CHANGED
@@ -3,11 +3,11 @@ require 'optparse'
|
|
3
3
|
|
4
4
|
module Conan
|
5
5
|
class Deploy
|
6
|
-
def self.run(options
|
7
|
-
puts "> Pipeline: #{
|
8
|
-
puts "> Environment: #{
|
6
|
+
def self.run(options)
|
7
|
+
puts "> Pipeline: #{options[:pipeline]}"
|
8
|
+
puts "> Environment: #{options[:environment]}"
|
9
9
|
|
10
|
-
manifest = ManifestBuilder.build(options
|
10
|
+
manifest = ManifestBuilder.build(options) {
|
11
11
|
m = File.join(options[:directory], 'environments.rb')
|
12
12
|
puts "> Manifest: #{m}"
|
13
13
|
instance_eval(File.read(m), m)
|
@@ -33,14 +33,16 @@ module Conan
|
|
33
33
|
manifest.configure
|
34
34
|
manifest.deploy
|
35
35
|
when :bg_all
|
36
|
+
Stackato.static_login(options)
|
36
37
|
manifest.provision
|
37
38
|
manifest.bg_configure
|
38
|
-
manifest.bg_deploy
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
manifest.bg_deploy.each do |app|
|
40
|
+
if app.is_inactive_node_operational?
|
41
|
+
Stackato.bg_switch(app)
|
42
|
+
Stackato.bg_clean(app)
|
43
|
+
else
|
44
|
+
raise RuntimeError.new "#{app}'s blue node is not operational. Skipping switch and clean."
|
45
|
+
end
|
44
46
|
end
|
45
47
|
|
46
48
|
else
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'conan/has_options'
|
2
|
+
class Deployment < HasOptions
|
3
|
+
|
4
|
+
@@paas_domain = 'mtnsatcloud.com'
|
5
|
+
|
6
|
+
attr_accessor :environment, :org, :ship, :shipcloud, :app_ids, :facility_id, :additional_mappings, :custom_smoke_test_path
|
7
|
+
|
8
|
+
def initialize(environment, org, ship)
|
9
|
+
# inherit options from the environment
|
10
|
+
#super(environment.options) # TODO: copy?!
|
11
|
+
super({})
|
12
|
+
@environment = environment
|
13
|
+
@org = org
|
14
|
+
@ship = ship
|
15
|
+
@shipcloud = "#{org}-#{ship}"
|
16
|
+
@app_ids = []
|
17
|
+
@additional_mappings = []
|
18
|
+
@facility_id = nil
|
19
|
+
@enabled = true
|
20
|
+
@randomid = SecureRandom.hex(3)
|
21
|
+
@custom_smoke_test_path = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def apps(*app_ids)
|
25
|
+
@app_ids = app_ids
|
26
|
+
end
|
27
|
+
|
28
|
+
def facility(facility_id)
|
29
|
+
@facility_id = facility_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def hostnames(*hostnames)
|
33
|
+
hostnames.each do |hostname|
|
34
|
+
@additional_mappings.push(hostname) unless hostname.empty?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def enabled(b)
|
39
|
+
@enabled = b
|
40
|
+
end
|
41
|
+
|
42
|
+
def enabled?
|
43
|
+
@enabled
|
44
|
+
end
|
45
|
+
|
46
|
+
def name(app)
|
47
|
+
"#{app.id}-#{@environment.id}-#{@ship}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def unique_name(app)
|
51
|
+
"#{name(app)}-#{@randomid}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def dns_name(app)
|
55
|
+
"#{@environment.id}.#{app.url_segment}.#{@ship}.#{@org}.#{@@paas_domain}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def agnostic_dns_name(app)
|
59
|
+
"#{@environment.id}.#{app.url_segment}.#{@@paas_domain}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def active_urls(app)
|
63
|
+
[ dns_name(app), agnostic_dns_name(app) ] + @additional_mappings
|
64
|
+
end
|
65
|
+
|
66
|
+
def inactive_urls(app)
|
67
|
+
active_urls(app).map{ |url| "inactive.#{url}" }
|
68
|
+
end
|
69
|
+
|
70
|
+
def manifest_url(app)
|
71
|
+
"http://#{dns_name(app)}/status/manifest"
|
72
|
+
end
|
73
|
+
|
74
|
+
def active_smoke_test_url(app)
|
75
|
+
"http://#{dns_name(app)}/#{@custom_smoke_test_path || 'status/healthcheck'}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def inactive_smoke_test_url(app)
|
79
|
+
"http://inactive.#{dns_name(app)}/#{@custom_smoke_test_path || 'status/healthcheck'}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def paas_target
|
83
|
+
"https://api.paas.#{@ship}.#{@org}.#{@@paas_domain}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
"#{org}-#{ship}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def smoke_test_path(path=nil)
|
91
|
+
@custom_smoke_test_path = path
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Environment
|
2
|
+
attr_accessor :id, :upstream_env, :deployments
|
3
|
+
|
4
|
+
def initialize(env_id)
|
5
|
+
@id = env_id
|
6
|
+
@deployments = []
|
7
|
+
@org_holder = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def promotes_from(env_id)
|
11
|
+
@upstream_env = env_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def org(org_name, &block)
|
15
|
+
@org_holder = org_name
|
16
|
+
self.instance_eval(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def deploy(ship, &block)
|
20
|
+
d = Deployment.new(self, @org_holder, ship)
|
21
|
+
d.instance_eval(&block)
|
22
|
+
@deployments << d
|
23
|
+
end
|
24
|
+
end
|