conan_deploy_stackato3 0.1.1

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.
@@ -0,0 +1,91 @@
1
+ require 'conan/has_options'
2
+ class Deployment < HasOptions
3
+
4
+ attr_accessor :environment, :org, :ship, :shipcloud, :app_ids, :facility_id, :additional_mappings, :custom_smoke_test_path
5
+
6
+ def initialize(environment, org, ship)
7
+ # inherit options from the environment
8
+ #super(environment.options) # TODO: copy?!
9
+ super({})
10
+ @environment = environment
11
+ @org = org
12
+ @ship = ship
13
+ @shipcloud = "#{org}-#{ship}"
14
+ @app_ids = []
15
+ @additional_mappings = []
16
+ @facility_id = nil
17
+ @enabled = true
18
+ @randomid = SecureRandom.hex(3)
19
+ @custom_smoke_test_path = nil
20
+ end
21
+
22
+ def apps(*app_ids)
23
+ @app_ids = app_ids
24
+ end
25
+
26
+ def facility(facility_id)
27
+ @facility_id = facility_id
28
+ end
29
+
30
+ def hostnames(*hostnames)
31
+ hostnames.each do |hostname|
32
+ @additional_mappings.push(hostname) unless hostname.empty?
33
+ end
34
+ end
35
+
36
+ def enabled(b)
37
+ @enabled = b
38
+ end
39
+
40
+ def enabled?
41
+ @enabled
42
+ end
43
+
44
+ def name(app)
45
+ "#{app.id}-#{@environment.id}-#{@ship}"
46
+ end
47
+
48
+ def unique_name(app)
49
+ "#{name(app)}-#{@randomid}"
50
+ end
51
+
52
+ def dns_name(app)
53
+ "#{@environment.id}.#{app.url_segment}.#{@ship}.#{@org}.mtnsatcloud.com"
54
+ end
55
+
56
+ def agnostic_dns_name(app)
57
+ "#{app.url_segment}.mtnsat.io"
58
+ end
59
+
60
+ def active_urls(app)
61
+ [ dns_name(app), agnostic_dns_name(app) ] + @additional_mappings
62
+ end
63
+
64
+ def inactive_urls(app)
65
+ active_urls(app).map{ |url| "x.#{url}" }
66
+ end
67
+
68
+ def manifest_url(app)
69
+ "http://#{dns_name(app)}/status/manifest"
70
+ end
71
+
72
+ def active_smoke_test_url(app)
73
+ "http://#{dns_name(app)}/#{@custom_smoke_test_path || 'status/healthcheck'}"
74
+ end
75
+
76
+ def inactive_smoke_test_url(app)
77
+ "http://x.#{dns_name(app)}/#{@custom_smoke_test_path || 'status/healthcheck'}"
78
+ end
79
+
80
+ def paas_target
81
+ "https://api.paasv2.#{@ship}.#{@org}.mtnsatcloud.com"
82
+ end
83
+
84
+ def to_s
85
+ "#{org}-#{ship}"
86
+ end
87
+
88
+ def smoke_test_path(path=nil)
89
+ @custom_smoke_test_path = path
90
+ end
91
+ 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
@@ -0,0 +1,15 @@
1
+ class HasOptions
2
+ attr_accessor :options
3
+
4
+ def initialize(options)
5
+ @options = options
6
+ end
7
+
8
+ def option(key, value)
9
+ if @options[key].nil?
10
+ @options[key] = [value]
11
+ else
12
+ @options[key] << value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'conan/application'
2
+ class JvmApp < Application
3
+ attr_accessor :sha1, :artifact_repo
4
+
5
+ def initialize(id, project_name, artifact_repo, options, url_segment=nil)
6
+ super(id, project_name, :jvm, options, url_segment)
7
+ @sha1 = nil
8
+ @artifact_repo = artifact_repo
9
+ end
10
+
11
+ def download(location)
12
+ super
13
+ puts "- Provision NewRelic Agent jar"
14
+ @artifact_repo.downloadNewRelicAgent(File.join(location, @artifact_id), '3.2.3', 'c07cd82e033af9628cd7f90df874daee7000e471')
15
+ end
16
+ end
@@ -0,0 +1,100 @@
1
+ class Manifest
2
+
3
+ def initialize(options, artifact_repo, output)
4
+ @pipelines = {}
5
+ @environments = {}
6
+ @options = options
7
+ @artifact_repo = artifact_repo
8
+ @output = output
9
+ end
10
+
11
+ # select a pipeline and environment to do operations on
12
+ def select(pipeline_id, env_id)
13
+ env = @environments[env_id]
14
+ raise ArgumentError.new "Invalid environment id: '#{env_id}'. Valid options are #{@environments.keys.join(", ")}" if env.nil?
15
+ @current_environment = env
16
+ pl = @pipelines[pipeline_id]
17
+ raise ArgumentError.new "Invalid pipeline id: '#{pipeline_id}'. Valid options are #{@pipelines.keys.join(", ")}" if pl.nil?
18
+ @current_pipeline = pl
19
+ pl.load!(env, @options[:'deploy-shipcloud'])
20
+ end
21
+
22
+ def pipeline(pipeline_id, &block)
23
+ pl = Pipeline.new(pipeline_id, @artifact_repo, @output, @options)
24
+ pl.instance_eval(&block)
25
+ @pipelines[pipeline_id] = pl
26
+ pl
27
+ end
28
+
29
+ def environment(env_id, &block)
30
+ e = Environment.new(env_id)
31
+ e.instance_eval(&block)
32
+ @environments[env_id] = e
33
+ e
34
+ end
35
+
36
+ def appVersion(pipeline_id, app_id)
37
+ @pipelines[pipeline_id].appVersion(app_id)
38
+ end
39
+
40
+ def provision
41
+ validateThatEnvironmentIsSelected
42
+ # TODO banners
43
+ puts "Update #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
44
+ @current_pipeline.update(@current_environment, @environments[@current_environment.upstream_env])
45
+ end
46
+
47
+ def configure
48
+ validateThatEnvironmentIsSelected
49
+ puts "Configure #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
50
+ @current_pipeline.configure(@current_environment)
51
+ end
52
+
53
+ def bg_configure
54
+ validateThatEnvironmentIsSelected
55
+ puts "Blue/green configure #{@current_environment.id} manifest files for #{@current_pipeline.id} pipeline"
56
+ @current_pipeline.configure(@current_environment, true)
57
+ end
58
+
59
+ def paas
60
+ paas_user = @options[:'paas-user'] || ENV['PAAS_USER']
61
+ paas_pwd = @options[:'paas-password'] || ENV['PAAS_PASSWORD']
62
+ paas_space = @options[:'paas-space'] || ENV['PAAS_SPACE']
63
+ dry_run = @options[:'dry-run'] || false
64
+
65
+ Stackato.new(paas_user, paas_pwd, paas_space, dry_run)
66
+ end
67
+
68
+ def deploy
69
+ validateThatEnvironmentIsSelected
70
+ puts "Deploy #{@current_environment.id} for #{@current_pipeline.id} pipeline"
71
+
72
+ nr_api_key = @options[:'new-relic-api-key'] || ENV['NEWRELIC_API_KEY']
73
+ force = @options[:'force-deploy'] || false
74
+
75
+ @current_pipeline.deploy(@current_environment, paas, force) { |app_name|
76
+ if (nr_api_key)
77
+ puts 'Reporting Deployment to New Relic'
78
+ job = @options[:'job-url'] || 'nexus-app-manifest'
79
+ NewRelic.alertDeployment(nr_api_key, app_name, "Deployed by #{job}")
80
+ else
81
+ puts "WARNING: Skipping New Relic alert. No API defined"
82
+ end
83
+ }
84
+ end
85
+
86
+ def bg_deploy
87
+ validateThatEnvironmentIsSelected
88
+ puts "Blue/green deploy #{@current_environment.id} for #{@current_pipeline.id} pipeline"
89
+
90
+ force = @options[:'force-deploy'] || false
91
+
92
+ @current_pipeline.bg_deploy(@current_environment, paas, force)
93
+ end
94
+
95
+ def validateThatEnvironmentIsSelected
96
+ unless @current_environment && @current_pipeline
97
+ raise ScriptError, 'Environment and Pipeline must be selected'
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,14 @@
1
+ module ManifestBuilder
2
+ def self.build(options, artifact_repo=nil, outthingy=nil, &block)
3
+ manifest_dir = options[:directory]
4
+ output_type = options[:format]
5
+
6
+ artifact_repo ||= DefaultArtifactRepository.new
7
+ outthingy ||= StackatoOutThingy.new(manifest_dir)
8
+
9
+ manifest = Manifest.new(options, artifact_repo, outthingy)
10
+ manifest.instance_eval(&block)
11
+ manifest.select(options[:pipeline], options[:environment])
12
+ manifest
13
+ end
14
+ end
@@ -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
@@ -0,0 +1,180 @@
1
+ require 'daphne_util'
2
+ require 'conan/templates'
3
+
4
+ class OutThingy
5
+ attr_accessor :output_dir, :time
6
+
7
+ def initialize(output_dir)
8
+ @output_dir = output_dir
9
+ @time = Time.new
10
+ end
11
+
12
+ def outputToFile(file, &block)
13
+ o = File.join(@output_dir, file)
14
+ puts "Write: #{o}"
15
+ FileUtils.mkdir_p File.dirname(o)
16
+ File.open(o, 'w') { |f| yield f }
17
+ end
18
+
19
+ def loadAppMetadataFromDeploymentIfExists(app, deploy)
20
+ loadAppMetadataFromDeployment(app, deploy, true)
21
+ end
22
+ end
23
+
24
+ class StackatoOutThingy < OutThingy
25
+
26
+ attr_accessor :environments_dir, :apps_dir
27
+
28
+ @@stackato_file_name = "stackato.yml"
29
+ @@metadata_file_name = "metadata.xml"
30
+ @environments_dir = nil
31
+ @apps_dir = nil
32
+
33
+ def initialize(output_dir)
34
+ super(output_dir)
35
+ workdir = File.join(output_dir,'tmp')
36
+ @environments_dir = File.join(output_dir,'environments')
37
+ @apps_dir = File.join(workdir,'apps')
38
+ FileUtils.mkdir_p environments_dir
39
+ FileUtils.mkdir_p apps_dir
40
+ end
41
+
42
+ def clean(app, environment)
43
+ # TODO: cleanup
44
+ end
45
+
46
+ def provision(app)
47
+ app.download(@apps_dir)
48
+ end
49
+
50
+ def filePath(app, deploy)
51
+ "#{deploy.environment.id}/#{deploy.shipcloud}/#{app.id}"
52
+ end
53
+
54
+ def workingDir(app)
55
+ File.join(apps_dir, app.artifact_id)
56
+ end
57
+
58
+ # TODO this needs a refactor, it's knoted up with App
59
+ def loadAppMetadataFromDeployment(app, deploy, fail_silent=false)
60
+ p = filePath(app, deploy)
61
+ path = File.join(@environments_dir, p)
62
+ metadata_file = File.join(path, @@metadata_file_name)
63
+
64
+ if (File.exists? path)
65
+ File.open(metadata_file, 'r') { |f| app.readArtifactMetadata(f.read) }
66
+ else
67
+ raise "Build meta-data was not found: #{path}" unless fail_silent
68
+ end
69
+ end
70
+
71
+ def writeArtifactMetadata(app, deploy)
72
+ if (deploy.enabled?)
73
+ path = filePath(app, deploy)
74
+ outputToFile("environments/#{path}/#{@@metadata_file_name}") { |f|
75
+ f.write(app.artifact_meta_data)
76
+ }
77
+ end
78
+ end
79
+
80
+ def writeConfiguration(app, deploy, bg=false)
81
+ if (deploy.enabled?)
82
+ target_dir = workingDir(app)
83
+ templates_dir = File.join(target_dir, "deploy-templates")
84
+
85
+ daphne_tokens = [ "#{app.id}", "#{deploy.environment.id}" ]
86
+ daphne_tokens = daphne_tokens << "facility-#{deploy.facility_id}" unless deploy.facility_id.nil?
87
+
88
+ n_changes = Templates.evaluate templates_dir, target_dir, {
89
+ :app => app,
90
+ :deploy => deploy,
91
+
92
+ #daphne, generate oauth app tokens
93
+ #TODO: seems like this should be refactored
94
+ :oauth_id => DaphneUtil.generate_id(*daphne_tokens),
95
+ :oauth_secret => DaphneUtil.generate_secret(*daphne_tokens),
96
+ :api_key => DaphneUtil.generate_api_key(*daphne_tokens),
97
+
98
+ # short-cuts
99
+ :app_id => app.id,
100
+ :deploy_base_name => deploy.name(app),
101
+ :deploy_name => bg ? deploy.unique_name(app) : deploy.name(app),
102
+ :deploy_urls => bg ? deploy.inactive_urls(app) : deploy.active_urls(app),
103
+ :env => deploy.environment.id,
104
+ :org => deploy.org,
105
+ :ship => deploy.ship,
106
+ :infra => deploy.shipcloud, # TODO: remove infra key, this is just to avoid breaking existing templates
107
+ :shipcloud => deploy.shipcloud,
108
+ :facility => deploy.facility_id,
109
+ :options => deploy.options
110
+ }
111
+ end
112
+ #TODO: can this be used to provide idempotency for config-only deploymnent?
113
+ puts "#{n_changes} files changed"
114
+ end
115
+ end
116
+
117
+ class PropertiesFileOutThingy < OutThingy
118
+
119
+ def clean(app, environment)
120
+ # cleanup current
121
+ FileUtils.rm Dir.glob("#{@output_dir}/#{app.id}-deploy-#{environment.id}-*.properties")
122
+ end
123
+
124
+ def fileName(app, deploy)
125
+ "#{app.id}-deploy-#{deploy.environment.id}-#{deploy.shipcloud}.properties"
126
+ end
127
+
128
+ def loadAppMetadataFromDeployment(app, deploy, fail_silent=false)
129
+ path = File.join(@output_dir, fileName(app, deploy))
130
+ if (File.exists? path)
131
+ props = Utils::Properties.load_from_file(path, true)
132
+ app.version = props.get(:DEPLOY_VERSION) # TODO: preserve SHA1
133
+ else
134
+ raise "File not found: #{path}" unless fail_silent
135
+ end
136
+ end
137
+
138
+ def writeArtifactMetadata(app, deploy)
139
+ file_name = fileName(app, deploy)
140
+ out_file = File.join(@output_dir, file_name)
141
+ if (!deploy.enabled?)
142
+ if (File.exists? out_file)
143
+ File.delete(out_file)
144
+ end
145
+ out_file = File.join(@output_dir, "DISABLED-#{file_name}")
146
+ end
147
+ puts "Write: #{out_file}"
148
+ File.open(out_file, 'w') { |f|
149
+ # TODO: would like @time in the manifest as audit field, but not sure it's such a good idea yet
150
+ # f << "\# #{file_name}\n\# #{@time}\n"
151
+ f << "\# #{file_name}\n"
152
+ f << "DEPLOY_ENV=#{deploy.environment.id}\n"
153
+ f << "DEPLOY_INFRA=#{deploy.shipcloud}\n"
154
+ if (deploy.facility_id)
155
+ f << "FACILITY=facility-#{deploy.facility_id}\n"
156
+ end
157
+ unless (deploy.options.empty?)
158
+ options = []
159
+ deploy.options.each do |k, v|
160
+ options << k + '|' + v.join(',')
161
+ end
162
+ p options.join(';')
163
+ f << "OPTIONS=#{options.join(';')}\n"
164
+ end
165
+ f << "APP=#{app.id}\n"
166
+ f << "DEPLOY_SHA1=#{app.sha1}\n"
167
+ f << "DEPLOY_VERSION=#{app.version}\n"
168
+ f << "TYPE=#{app.platform_type}\n"
169
+ f << "\n"
170
+ }
171
+ end
172
+
173
+ def provision(app)
174
+ #noop
175
+ end
176
+
177
+ def writeConfiguration(app, deploy)
178
+ # noop
179
+ end
180
+ end
@@ -0,0 +1,102 @@
1
+ class Pipeline
2
+ include ApiHelper
3
+
4
+ attr_accessor :id, :apps
5
+
6
+ def initialize(id, artifact_repo, output, options)
7
+ @id = id
8
+ @artifact_repo = artifact_repo
9
+ @output = output
10
+ @apps = {}
11
+ @options = options
12
+ end
13
+
14
+ def app(app_id, project_name, platform_type, url_segment=nil)
15
+ if (@options[:'deploy-app-name'].nil?) || (@options[:'deploy-app-name'] == app_id.to_s)
16
+ puts "No app specified, or we matched the specified app: #{@options[:'deploy-app-name']}"
17
+ @apps[app_id] = case platform_type
18
+ when :jvm
19
+ JvmApp.new(app_id, project_name, @artifact_repo, @options, url_segment)
20
+ when :rails_zip
21
+ RailsZipApp.new(app_id, project_name, @artifact_repo, @options, url_segment)
22
+ else
23
+ raise "unknown platform type: #{platform_type}"
24
+ end
25
+ else
26
+ puts "App #{app_id} doesn't match specified app #{@options[:'deploy-app-name']}, skipping"
27
+ end
28
+ end
29
+
30
+ def appVersion(app_id)
31
+ @apps[app_id].version
32
+ end
33
+
34
+ def load!(environment, deploy_shipcloud)
35
+ eachAppDeployment(environment) { |app, deploy|
36
+ # load the persisted meta-data
37
+ @output.loadAppMetadataFromDeploymentIfExists(app, deploy)
38
+ }
39
+ @deploy_shipcloud = deploy_shipcloud
40
+ end
41
+
42
+ def update(environment, from_upstream_env)
43
+ @apps.values.each { |app|
44
+ puts ''
45
+ if !(@options[:'deploy-app-version'].nil?)
46
+ puts "Using version from options: #{@options[:'deploy-app-version']}"
47
+ app.findRequestedVersion(@options[:'deploy-app-version'])
48
+ elsif (from_upstream_env)
49
+ # promote versions from an upstream deploy
50
+ # TODO: this needs improving
51
+ puts "Promote #{app.id} from #{from_upstream_env.id} to #{environment.id}"
52
+ upstream_deploy = from_upstream_env.deployments.select { |d| d.app_ids.select {|x| x == app.id }.size > 0 }.first
53
+ @output.loadAppMetadataFromDeployment(app, upstream_deploy)
54
+ else
55
+ puts "Find latest #{app.artifact_id}"
56
+ # lookup most recent from repo
57
+ app.findRequestedVersion('LATEST')
58
+ end
59
+ @output.clean(app, environment)
60
+ # download artifacts
61
+ @output.provision(app)
62
+ }
63
+
64
+ # write out deployment artifact meta-data
65
+ eachAppDeployment(environment) { |app, deploy| @output.writeArtifactMetadata(app, deploy) }
66
+ end
67
+
68
+ def configure(environment, bg=false)
69
+ eachAppDeployment(environment) { |app, deploy| @output.writeConfiguration(app, deploy, bg) }
70
+ end
71
+
72
+ def deploy(environment, paas, force=false)
73
+ eachAppDeployment(environment) { |app, deploy|
74
+ work_dir = @output.workingDir(app)
75
+ if (force)
76
+ paas.deploy(work_dir, app, deploy, true)
77
+ else
78
+ paas.deploy(work_dir, app, deploy) unless app.isDeployedAt(deploy.manifest_url(app))
79
+ end
80
+ }
81
+ end
82
+
83
+ def bg_deploy(environment, paas, force=false)
84
+ apps = []
85
+ eachAppDeployment(environment) { |app, deploy|
86
+ if (force or !app.isDeployedAt(deploy.manifest_url(app)))
87
+ paas.bg_deploy(@output.workingDir(app), app, deploy)
88
+ end
89
+ }
90
+ end
91
+
92
+ def eachAppDeployment(environment)
93
+ selected_deployments = environment.deployments.select{ |d| @deploy_shipcloud.nil? || d.shipcloud == @deploy_shipcloud }
94
+ puts "WARNING no deploys selected: #{@deploy_shipcloud}" if selected_deployments.empty?
95
+ selected_deployments.each do |d|
96
+ d.app_ids.each do |a|
97
+ app = @apps[a]
98
+ yield app, d unless app.nil?
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,10 @@
1
+ require 'conan/application'
2
+ class RailsZipApp < Application
3
+ attr_accessor :sha1, :artifact_repo
4
+
5
+ def initialize(id, project_name, artifact_repo, options, url_segment=nil)
6
+ super(id, project_name, :rails_zip, options, url_segment)
7
+ @sha1 = nil
8
+ @artifact_repo = artifact_repo
9
+ end
10
+ end