enhanced-elastic-beanstalk 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ module Elastic
2
+ module Beanstalk
3
+ require 'elastic/beanstalk/railtie' if defined?(Rails::Railtie)
4
+ require 'elastic/beanstalk/config'
5
+ require 'elastic/beanstalk/extensions'
6
+ require 'elastic/beanstalk/smoke_tester'
7
+ require 'elastic/beanstalk/version'
8
+ require 'elastic/beanstalk/spinner'
9
+ end
10
+ end
11
+
12
+ EbConfig = Elastic::Beanstalk::Config.instance
13
+ EbExtensions = Elastic::Beanstalk::Extensions
14
+ EbSmokeTester = Elastic::Beanstalk::SmokeTester
15
+ Spinner = Elastic::Beanstalk::Spinner
@@ -0,0 +1,139 @@
1
+ require 'singleton'
2
+ require 'dry/config'
3
+
4
+ module Elastic
5
+ module Beanstalk
6
+ #
7
+ # EbConfig allows for default settings and mounting a specific environment with overriding
8
+ # hash values and merging of array values.
9
+ #
10
+ # NOTE: Anything can be overridden and merged into top-level settings (hashes) including
11
+ # anything that is an array value. Array values are merged *not* replaced. If you think
12
+ # something is screwy, see the defaults in the #init as those add some default array values.
13
+ # If this behavior of merging arrays or the defaults are somehow un-sensible, file an issue and we'll revisit it.
14
+ #
15
+ class Config < Dry::Config::Base
16
+
17
+ include Singleton
18
+ # it's a singleton, thus implemented as a self-extended module
19
+ # extend self
20
+
21
+ def initialize(options = {})
22
+ # seed the sensible defaults here
23
+ options = {
24
+ symbolize: true,
25
+ interpolation: false,
26
+ default_configuration: {
27
+ environment: nil,
28
+ secrets_dir: '~/.aws',
29
+ disallow_environments: %w(cucumber test),
30
+ strategy: :blue_green,
31
+ package: {
32
+ dir: 'pkg',
33
+ verbose: false,
34
+ includes: %w(**/* .ebextensions/**/*),
35
+ exclude_files: [],
36
+ exclude_dirs: %w(pkg tmp log test-reports)
37
+ },
38
+ options: {},
39
+ inactive: {}
40
+ }
41
+ }.merge(options)
42
+ super(options)
43
+ end
44
+
45
+
46
+ def load!(environment = nil, filename = resolve_path('config/eb.yml'))
47
+ super(environment, filename)
48
+ end
49
+
50
+
51
+ def resolve_path(relative_path)
52
+ if defined?(Rails)
53
+ Rails.root.join(relative_path)
54
+ elsif defined?(Rake.original_dir)
55
+ File.expand_path(relative_path, Rake.original_dir)
56
+ else
57
+ File.expand_path(relative_path, Dir.pwd)
58
+ end
59
+ end
60
+
61
+ # custom methods for the specifics of eb.yml settings
62
+ def option_settings
63
+ generate_settings(options)
64
+ end
65
+
66
+ def inactive_settings
67
+ generate_settings(inactive)
68
+ end
69
+
70
+ def set_option(namespace, option_name, value)
71
+ current_options = to_option_setting(namespace, option_name, value)
72
+ namespace = current_options[:namespace].to_sym
73
+ option_name = current_options[:option_name].to_sym
74
+
75
+ options[namespace] = {} if options[namespace].nil?
76
+ options[namespace][option_name] = value
77
+ end
78
+
79
+ def find_option_setting(name)
80
+ find_setting(name, options)
81
+ end
82
+
83
+ def find_option_setting_value(name)
84
+ find_setting_value(name, options)
85
+ end
86
+
87
+ def find_inactive_setting(name)
88
+ find_setting(name, inactive)
89
+ end
90
+
91
+ def find_inactive_setting_value(name)
92
+ find_setting_value(name, inactive)
93
+ end
94
+
95
+ def to_option_setting(namespace, option_name, value)
96
+ erb_value = "#{value}".scan(/<%=.*%>/).first
97
+ unless erb_value.nil?
98
+ value = ERB.new(erb_value).result
99
+ end
100
+ {
101
+ :'namespace' => "#{namespace}",
102
+ :'option_name' => "#{option_name}",
103
+ :'value' => "#{value}"
104
+ }
105
+ end
106
+
107
+ private
108
+
109
+ def find_setting(name, settings_root)
110
+ name = name.to_sym
111
+ settings_root.each_key do |namespace|
112
+ settings_root[namespace].each do |option_name, value|
113
+ if option_name.eql? name
114
+ return to_option_setting(namespace, option_name, value)
115
+ end
116
+ end
117
+ end
118
+ return nil
119
+ end
120
+
121
+ def find_setting_value(name, settings_root)
122
+ o = find_setting(name, settings_root)
123
+ o[:value] unless o.nil?
124
+ end
125
+
126
+ def generate_settings(settings_root)
127
+ result = []
128
+ settings_root.each_key do |namespace|
129
+ settings_root[namespace].each do |option_name, value|
130
+ result << to_option_setting(namespace, option_name, value)
131
+ end
132
+ end
133
+
134
+ #{"option_settings" => result}
135
+ result
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,45 @@
1
+ module Elastic
2
+ module Beanstalk
3
+
4
+ module Extensions
5
+ # it's a singleton, thus implemented as a self-extended module
6
+ extend self
7
+
8
+ def write_extensions
9
+
10
+ ebextensions = EbConfig.ebextensions
11
+ return if ebextensions.nil?
12
+
13
+ Dir.mkdir absolute_file_name(nil) rescue nil
14
+
15
+ ebextensions.each_key do |filename|
16
+ contents = EbConfig.ebextensions[filename]
17
+
18
+ filename = absolute_file_name(filename)
19
+
20
+ # when converting to_yaml, kill the symbols as EB doesn't like it.
21
+ contents = contents.deep_symbolize(true).to_yaml.gsub(/---\n/, "")
22
+ #puts "\n#{filename}:\n----------------------------------------------------\n#{contents}----------------------------------------------------\n"
23
+ File.write(filename, contents)
24
+ end
25
+ end
26
+
27
+ def delete_extensions
28
+ ebextensions = EbConfig.ebextensions
29
+ return if ebextensions.nil?
30
+
31
+ ebextensions.each_key do |filename|
32
+ File.delete(absolute_file_name filename)
33
+ end
34
+ end
35
+
36
+ def absolute_file_name(filename)
37
+ EbConfig.resolve_path(".ebextensions/#{filename}")
38
+ end
39
+
40
+ def ebextensions_dir(filename)
41
+ EbConfig.resolve_path(".ebextensions/#{filename}")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ #require 'beanstalk'
2
+ #require 'rails'
3
+
4
+ module Elastic
5
+ module Beanstalk
6
+
7
+ # https://gist.github.com/josevalim/af7e572c2dc973add221
8
+ class Railtie < Rails::Railtie
9
+
10
+ #railtie_name :elastic
11
+
12
+ rake_tasks do
13
+ load 'elastic/beanstalk/tasks/eb.rake'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,98 @@
1
+ require 'timeout'
2
+ require 'net/http'
3
+ require 'elastic/beanstalk/spinner'
4
+ require 'nokogiri'
5
+
6
+ module Elastic
7
+ module Beanstalk
8
+
9
+ module SmokeTester
10
+ # it's a singleton, thus implemented as a self-extended module
11
+ extend self
12
+
13
+ def test_url(url, timeout, sleep_wait, expected_text)
14
+
15
+ puts '-------------------------------------------------------------------------------'
16
+ # puts "Smoke Testing: \n\turl: #{url}\n\ttimeout: #{timeout}\n\tsleep_wait: #{sleep_wait}\n\texpected_text: #{expected_text}\n"
17
+ puts "Smoke Test: \n\turl: #{url}\n\ttimeout: #{timeout}\n\texpected_text: #{expected_text}"
18
+ response = nil
19
+ begin
20
+ Timeout.timeout(timeout) do
21
+ i = 0
22
+ print "\nRunning..."
23
+ Spinner.show {
24
+ begin
25
+ sleep sleep_wait.to_i unless (i == 0)
26
+ i += 1
27
+ begin
28
+ response = Net::HTTP.get_response(URI(url))
29
+ #rescue SocketError => e
30
+ # response = ResponseStub.new({code: e.message, body: ''})
31
+ rescue => e
32
+ response = ResponseStub.new({code: e.message, body: ''})
33
+ end
34
+
35
+ puts "\t\t[#{response.code}]"
36
+ #puts "\t#{response.body}"
37
+
38
+ #
39
+ # Let's try to get out quickly when the deploy has gone wrong.
40
+ #
41
+ break if received_fatal_error?(response)
42
+
43
+ end until (!response.nil? && response.code.to_i == 200 && response.body.include?(expected_text))
44
+ }
45
+ end
46
+ ensure
47
+ puts "\n\nFinal response code: [#{response.code}] expectation met: #{response.body.include?(expected_text)}"
48
+ puts '-------------------------------------------------------------------------------'
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def received_fatal_error?(response)
55
+ fatal_error = false
56
+ #['Bundler::PathError'].each do |code|
57
+ # fail right away on known terminal cases.
58
+ #if (!response.nil? && response.code.to_i == 500 && response.body.include?(code))
59
+ if (!response.nil? && response.code.to_i == 500 && response.body.include?('rror'))
60
+
61
+ doc = Nokogiri::HTML(response.body)
62
+
63
+ # By default, let's grab the info from the Passenger error page...
64
+ #$stderr.puts "\t\t[#{response.code}] #{code}"
65
+ $stderr.puts "\t\t[#{response.code}]"
66
+ $stderr.puts "\t\t\t#{get_content(doc, '//h1')}"
67
+ $stderr.puts "\t\t\t#{get_content(doc, '//dl/dd')}"
68
+ fatal_error = true
69
+ # break
70
+ end
71
+ #end
72
+
73
+ fatal_error
74
+ end
75
+
76
+ def get_content(doc, xpath)
77
+ value = ''
78
+ begin
79
+ element = doc.xpath(xpath)
80
+ element = element.first unless element.nil?
81
+ value = element.content unless element.nil?
82
+ rescue
83
+ end
84
+ value
85
+ end
86
+
87
+ class ResponseStub
88
+
89
+ attr_reader :code, :body
90
+
91
+ def initialize(args)
92
+ @code = args[:code]
93
+ @body = args[:body]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,29 @@
1
+ module Elastic
2
+ module Beanstalk
3
+
4
+ module Spinner
5
+ # it's a singleton, thus implemented as a self-extended module
6
+ extend self
7
+
8
+ def show(fps=10)
9
+ chars = %w{ | / - \\ }
10
+ delay = 1.0/fps
11
+ iter = 0
12
+ spinner = Thread.new do
13
+ while iter do # Keep spinning until told otherwise
14
+
15
+ print chars[0]
16
+ sleep delay
17
+ print "\b"
18
+ chars.push chars.shift
19
+ end
20
+ end
21
+ yield.tap {# After yielding to the block, save the return value
22
+ iter = false # Tell the thread to exit, cleaning up after itself…
23
+ spinner.join # …and wait for it to do so.
24
+ } # Use the block's return value as the method's
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,486 @@
1
+ require 'digest'
2
+ require 'zip'
3
+ require 'ap' # gem 'awesome_print'
4
+ require 'eb_deployer'
5
+ require 'time_diff'
6
+ require 'elastic/beanstalk'
7
+ require 'yaml'
8
+ require 'table_print'
9
+ require 'timeout'
10
+ require 'hipchat'
11
+ require 'pry'
12
+
13
+ namespace :eb do
14
+
15
+ namespace :rds do
16
+ # http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html
17
+ desc 'List RDS snapshots'
18
+ task :snapshots => [:config] do |t, args|
19
+ # absolutely do not run this without specifying columns, otherwise it will call all defined methods including :delete
20
+ print_snapshots(rds.describe_db_snapshots.db_snapshots)
21
+ end
22
+
23
+ desc 'List RDS instances'
24
+ task :instances => [:config] do |t, args|
25
+ # absolutely do not run this without specifying columns, otherwise it will call all defined methods including :delete
26
+
27
+ print_instances(rds.describe_db_instances.db_instances)
28
+ end
29
+
30
+ desc 'Creates an RDS snapshot'
31
+ task :create_snapshot, [:instance_id, :snapshot_id] => [:config] do |t, args|
32
+
33
+ snapshot_id = args[:snapshot_id]
34
+
35
+ db = db(args[:instance_id])
36
+
37
+ from_time = Time.now
38
+ puts "\n\n---------------------------------------------------------------------------------------------------------------------------------------"
39
+ snapshot = db.create_snapshot(snapshot_id)
40
+ #snapshot = snapshot(snapshot_id) # for quick testing of code below
41
+
42
+
43
+ #ID | STATUS | GB | TYPE | ENGINE | ZONE | CREATED_AT | INSTANCE_CREATE_TIME
44
+ #------|----------|----|--------|--------------|------------|------------|------------------------
45
+ #pre-2 | creating | 10 | manual | mysql 5.6.12 | us-east-1d | | 2013-09-10 21:37:27
46
+ # available
47
+ print_snapshots(snapshot)
48
+ puts "\n"
49
+
50
+ timeout = 20 * 60
51
+ sleep_wait = 5
52
+ begin
53
+ Timeout.timeout(timeout) do
54
+ i = 0
55
+
56
+ print "\nCreating snapshot[#{snapshot_id}]..."
57
+ Spinner.show {
58
+ begin
59
+ sleep sleep_wait.to_i unless (i == 0)
60
+ i += 1
61
+
62
+ snapshot = snapshot(snapshot_id)
63
+
64
+ end until (snapshot.status.eql? 'available')
65
+ }
66
+ end
67
+ ensure
68
+ puts "\n\nSnapshot[#{snapshot_id}]: #{snapshot.status}. Finished in #{Time.diff(from_time, Time.now, '%N %S')[:diff]}.\n"
69
+ puts "---------------------------------------------------------------------------------------------------------------------------------------\n\n"
70
+ end
71
+ end
72
+
73
+ def snapshot(snapshot_id)
74
+ Aws::RDS::DBSnapshot.new(snapshot_id)
75
+ end
76
+
77
+ def db(instance_id)
78
+ db_instance = Aws::RDS::DBInstance.new(instance_id)
79
+ raise "DB Instance[#{instance_id}] does not exist." unless db_instance.exists?
80
+ db_instance
81
+ end
82
+
83
+ def rds
84
+ @rds ||= Aws::RDS::Client.new(Aws.config)
85
+ @rds
86
+ end
87
+
88
+ def print_snapshots(snapshots)
89
+ tp snapshots,
90
+ {snapshot_id: {display_method: :db_snapshot_identifier}},
91
+ :status,
92
+ {gb: {display_method: :allocated_storage}},
93
+ {type: {display_method: :snapshot_type}},
94
+ {engine: lambda { |i| "#{i.engine} #{i.engine_version}" }},
95
+ {zone: {display_method: :availability_zone}},
96
+ :snapshot_create_time,
97
+ :instance_create_time
98
+ end
99
+
100
+ def print_instances(instances)
101
+ tp instances,
102
+ {instance_id: {display_method: :db_instance_identifier}},
103
+ {name: {display_method: :db_name}},
104
+ {status: {display_method: :db_instance_status}},
105
+ {gb: {display_method: :allocated_storage}},
106
+ :iops,
107
+ {class: {display_method: :db_instance_class}},
108
+ {engine: lambda { |i| "#{i.engine} #{i.engine_version}" }},
109
+ {zone: {display_method: :availability_zone}},
110
+ :multi_az,
111
+ {endpoint: {display_method: lambda { |i| "#{i.endpoint.address}"}, :width => 120}},
112
+ {port: {display_method: lambda { |i| "#{i.endpoint.port}"}}},
113
+ #:latest_restorable_time,
114
+ #:auto_minor_version_upgrade,
115
+ #:read_replica_db_instance_identifiers,
116
+ #:read_replica_source_db_instance_identifier,
117
+ #:backup_retention_period,
118
+ #:master_username,
119
+ {created_at: {display_method: :instance_create_time}}
120
+ end
121
+ end
122
+
123
+
124
+ ###########################################
125
+ #
126
+ #
127
+ #
128
+ desc 'Setup AWS.config and merge/override environments into one resolved configuration'
129
+ task :config, [:environment, :version] do |t, args|
130
+
131
+ #-------------------------------------------------------------------------------
132
+ # Resolve arguments in a backwards compatibile way (see https://github.com/alienfast/elastic-beanstalk/issues/12)
133
+ # This allows both the use of RAILS_ENV or the :environment parameter
134
+ #
135
+ # Previously, we relied solely on the environment to be passed in as RAILS_ENV. It is _sometimes_ more convenient to allow it to be passed as the :environment parameter.
136
+ # Since :environment is first, and :version used to be first, check the :environment against a version number regex and adjust as necessary.
137
+ bc_arg_environment = args[:environment]
138
+ unless /^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/.match(bc_arg_environment).nil?
139
+
140
+ arg_version = args[:version]
141
+ raise "Found version[#{bc_arg_environment}] passed as :environment, but also found a value for :version[#{arg_version}]. Please adjust arguments to be [:environment, :version] or use RAILS_ENV with a single [:version] argument" unless arg_version.nil?
142
+
143
+ # version was passed as :environment, adjust it.
144
+ arg_version = bc_arg_environment
145
+ arg_environment = nil
146
+
147
+ # puts "NOTE: Manipulated :environment argument to :version[#{bc_arg_environment}]."
148
+ else
149
+ # normal resolution of argements
150
+ arg_environment = args[:environment]
151
+ arg_version = args[:version]
152
+ end
153
+
154
+
155
+ # set the default environment to be development if not otherwise resolved
156
+ environment = arg_environment || ENV['RAILS_ENV'] || 'development'
157
+
158
+ # load the configuration from same dir (for standalone CI purposes) or from the rails config dir if within the rails project
159
+ filename = EbConfig.resolve_path('eb.yml')
160
+ unless File.exists? filename
161
+ filename = EbConfig.resolve_path('config/eb.yml')
162
+ end
163
+ EbConfig.load!(environment, filename)
164
+
165
+ # Let's be explicit regardless of 'production' being the eb's default shall we? Set RACK_ENV and RAILS_ENV based on the given environment
166
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', "#{EbConfig.environment}")
167
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RAILS_ENV', "#{EbConfig.environment}")
168
+
169
+
170
+ # let's load secret, non-repo ENV vars from .secrets/env_vars.yml
171
+ secret_env_file = EbConfig.resolve_path('.secrets/env_vars.yml')
172
+ if File.exists? secret_env_file
173
+ puts "Using secret env vars from .secrets/env_vars.yml..."
174
+ vars = YAML::load_file secret_env_file
175
+ if vars[EbConfig.environment]
176
+ vars[EbConfig.environment].each { |key, val|
177
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', key, val)
178
+ }
179
+ end
180
+ end
181
+
182
+ #-------------------------------------------------------------------------------
183
+ # resolve the version and set the APP_VERSION environment variable
184
+
185
+ # try to use from task argument first
186
+ version = arg_version
187
+ file = resolve_absolute_package_file
188
+ if version.nil? && File.exists?(file)
189
+ # otherwise use the MD5 hash of the package file
190
+ version = Digest::MD5.file(file).hexdigest
191
+ end
192
+
193
+ # set the var, depending on the sequence of calls, this may be nil
194
+ # (i.e. :show_config with no :version argument) so omit it until we have something worthwhile.
195
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'APP_VERSION', "#{version}") unless version.nil?
196
+
197
+ #-------------------------------------------------------------------------------
198
+ # configure aws credentials. Depending on the called task, this may not be necessary parent task should call #credentials! for validation.
199
+ Aws.config.update({
200
+ credentials: Aws::Credentials.new(credentials['access_key_id'], credentials['secret_access_key']),
201
+ }) unless credentials.nil?
202
+
203
+ #-------------------------------------------------------------------------------
204
+ # configure aws region if specified in the eb.yml
205
+ Aws.config.update({
206
+ region: EbConfig.region,
207
+ }) unless EbConfig.region.nil?
208
+
209
+ end
210
+
211
+ ###########################################
212
+ #
213
+ #
214
+ #
215
+ desc 'Show resolved configuration without doing anything.'
216
+ task :show_config, [:environment, :version] => [:config] do |t, args|
217
+
218
+ puts "Working Directory: #{Rake.original_dir}"
219
+ print_config
220
+ end
221
+
222
+ ###########################################
223
+ #
224
+ #
225
+ #
226
+ desc 'Remove any generated package.'
227
+ task :clobber do |t, args|
228
+ # kill the old package dir
229
+ rm_r EbConfig.package[:dir] rescue nil
230
+ #puts "Clobbered #{EbConfig.package[:dir]}."
231
+ end
232
+
233
+ ###########################################
234
+ #
235
+ # Elastic Beanstalk seems to be finicky with a tar.gz. Using a zip, EB wants the files to be at the
236
+ # root of the archive, not under a top level folder. Include this package task to make
237
+ # sure we don't need to learn about this again through long deploy cycles!
238
+ #
239
+ desc 'Package zip source bundle for Elastic Beanstalk and generate external Rakefile. (optional) specify the :version arg to make it available to the elastic beanstalk app dynamically via the APP_VERSION environment varable'
240
+ task :package, [:environment, :version] => [:clobber, :config] do |t, args|
241
+
242
+ begin
243
+ # write .ebextensions
244
+ EbExtensions.write_extensions
245
+
246
+ # include all
247
+ files = FileList[EbConfig.package[:includes]]
248
+
249
+ # exclude files
250
+ EbConfig.package[:exclude_files].each do |file|
251
+ files.exclude(file)
252
+ end
253
+
254
+ EbConfig.package[:exclude_dirs].each do |dir|
255
+ files.exclude("#{dir}/**/*")
256
+ files.exclude("#{dir}")
257
+ end
258
+
259
+ # ensure dir exists
260
+ mkdir_p EbConfig.package[:dir] rescue nil
261
+
262
+ # zip it up
263
+ Zip::File.open(package_file, Zip::File::CREATE) do |archive|
264
+
265
+ puts "\nCreating archive (#{package_file}):" if package_verbose?
266
+ files.each do |f|
267
+
268
+ if File.directory?(f)
269
+ puts "\t#{f}" if package_verbose?
270
+ archive.add(f, f)
271
+ else
272
+ puts "\t\t#{f}" if package_verbose?
273
+ archive.add(f, f)
274
+ end
275
+ end
276
+ end
277
+
278
+
279
+ # write Rakefile for external CI/CD package deployment
280
+ File.open(package_rakefile, "w+") do |f|
281
+ f.write("spec = Gem::Specification.find_by_name('elastic-beanstalk', '>= #{Elastic::Beanstalk::VERSION}')\n")
282
+ f.write("load \"\#{spec.gem_dir}/lib/elastic/beanstalk/tasks/eb.rake\"")
283
+ end
284
+
285
+ puts "\nFinished creating archive (#{package_file})."
286
+ ensure
287
+ EbExtensions.delete_extensions
288
+ end
289
+ end
290
+
291
+
292
+ ###########################################
293
+ #
294
+ #
295
+ #
296
+ desc 'Deploy to Elastic Beanstalk'
297
+ task :deploy, [:environment, :version] => [:config] do |t, args|
298
+
299
+ # If called individually, this is not necessary, but if called in succession to eb:package, we may need to re-resolve an MD5 hashed name.
300
+ # Since we allow variable use of arguments, it is easiest just to quickly re-enable and re-run the eb:config task since all the resolution
301
+ # of values is contained there.
302
+ Rake::Task['eb:config'].reenable
303
+ Rake::Task['eb:config'].invoke(*args)
304
+
305
+
306
+ # Leave off the dependency of :package, we need to package this in the build phase and save
307
+ # the artifact on bamboo. The deploy plan will handle this separately.
308
+ from_time = Time.now
309
+
310
+ # ensure credentials
311
+ credentials!
312
+
313
+ package = resolve_absolute_package_file
314
+
315
+ # check package file
316
+ raise "Package file not found #{package} (also checked current dir). Be sure to run the :package task subsequent to any :deploy attempts." if !File.exists? package
317
+
318
+ # Don't deploy to test or cucumber (or whatever is specified by :disallow_environments)
319
+ raise "#{EbConfig.environment} is one of the #{EbConfig.disallow_environments} disallowed environments. Configure it by changing the :disallow_environments in the eb.yml" if EbConfig.disallow_environments.include? EbConfig.environment
320
+
321
+ print_config
322
+
323
+ # Avoid known problems
324
+ if EbConfig.find_option_setting_value('InstanceType').nil?
325
+ sleep 1 # let the puts from :config task finish first
326
+ raise "Failure to set an InstanceType is known to cause problems with deployments (i.e. .aws-eb-startup-version error). Please set InstanceType in the eb.yml with something like:\n #{{options: {:'aws:autoscaling:launchconfiguration' => {InstanceType: 't1.micro'}}}.to_yaml}\n"
327
+ end
328
+
329
+ options = {
330
+ application: EbConfig.app,
331
+ environment: EbConfig.eb_environment || EbConfig.environment,
332
+ version_label: find_option_app_version,
333
+ solution_stack_name: EbConfig.solution_stack_name,
334
+ option_settings: EbConfig.option_settings,
335
+ inactive_settings: EbConfig.inactive_settings,
336
+ strategy: EbConfig.strategy.to_sym,
337
+ package: package
338
+ }
339
+
340
+ options[:package_bucket] = EbConfig.package_bucket unless EbConfig.package_bucket.nil?
341
+ options[:keep_latest] = EbConfig.keep_latest unless EbConfig.keep_latest.nil?
342
+ options[:version_prefix] = EbConfig.version_prefix unless EbConfig.version_prefix.nil?
343
+ options[:tier] = EbConfig.tier unless EbConfig.tier.nil?
344
+ options[:cname_prefix] = EbConfig.custom_cname unless EbConfig.custom_cname.nil?
345
+
346
+ unless EbConfig.smoke_test.nil?
347
+ options[:smoke_test] = eval EbConfig.smoke_test
348
+ end
349
+
350
+ user = (`id -u -n` rescue '').chomp
351
+ user = 'Someone(?)' if user.strip.blank?
352
+ version = find_option_app_version[0,10] rescue '?'
353
+ send_notification "(beanstalk) #{user} started deploy of #{EbConfig[:app].upcase} (#{version}) to #{EbConfig.environment.to_s.upcase}...", { color: 'purple' }
354
+ begin
355
+ EbDeployer.deploy(options)
356
+
357
+ time = Time.diff(from_time, Time.now, '%N %S')[:diff]
358
+ success_emoji = %w[ success successful yey goldstar excellent awesome ].sample
359
+ send_notification "(#{success_emoji}) Deployment of #{EbConfig[:app].upcase} (#{version}) to #{EbConfig.environment.to_s.upcase} finished in #{time}.", { color: 'green' }
360
+ puts "\nDeployment finished in #{time}.\n"
361
+ rescue Exception => e
362
+ send_notification "(ohcrap) Deployment of #{EbConfig[:app].upcase} (#{version}) to #{EbConfig.environment.to_s.upcase} failed.", { color: 'red' }
363
+ puts e.message
364
+ end
365
+ end
366
+
367
+ ###########################################
368
+ #
369
+ #
370
+ #
371
+ desc '** Warning: Destroy Elastic Beanstalk application and *all* environments.'
372
+ task :destroy, [:environment, :force] => [:config] do |t, args|
373
+
374
+ if args[:force].eql? 'y'
375
+ destroy()
376
+ else
377
+ puts "Are you sure you wish to destroy #{EbConfig.app}-#{EbConfig.environment}? (y/n)"
378
+ input = STDIN.gets.strip
379
+ if input == 'y'
380
+ destroy()
381
+ else
382
+ puts 'Destroy canceled.'
383
+ end
384
+ end
385
+ end
386
+
387
+ ##########################################
388
+ private
389
+
390
+ def rails_or_rack_env
391
+ EbConfig
392
+ end
393
+
394
+ def send_notification(msg, opts={})
395
+ return false unless EbConfig[:notifications]
396
+ EbConfig[:notifications].each do |service, settings|
397
+ case service.to_s.downcase
398
+ when 'hipchat'
399
+ send_hipchat_notification(msg, (opts[:color] || 'yellow'), settings)
400
+ else puts "[!] Unknown notification service: #{service}"
401
+ end
402
+ end
403
+ end
404
+
405
+ def send_hipchat_notification(msg, color, settings, api_version: 'v2')
406
+ client = HipChat::Client.new(settings[:api_token], :api_version => api_version)
407
+ client[settings[:room]].send('', msg, color: color, message_format: 'text')
408
+ end
409
+
410
+ # Use the version if given, otherwise use the MD5 hash. Make available via the eb APP_VERSION environment variable
411
+ def find_option_app_version
412
+
413
+ # if already set by a dependency call to :config, get out early
414
+ version = EbConfig.find_option_setting_value('APP_VERSION')
415
+ return version unless version.nil?
416
+ end
417
+
418
+ def print_config
419
+ # display helpful for figuring out problems later in the deployment logs.
420
+ puts "\n----------------------------------------------------------------------------------"
421
+ puts 'Elastic Beanstalk configuration:'
422
+ puts "\taccess_key_id: #{credentials['access_key_id']}"
423
+ puts "\tenvironment: #{EbConfig.environment}"
424
+ puts "\tversion: #{find_option_app_version}"
425
+
426
+ # pretty print things that will be useful to see in the deploy logs and omit clutter that usually doesn't cause us problems.
427
+ h = EbConfig.configuration.dup
428
+ h.delete(:package)
429
+ h.delete(:disallow_environments)
430
+ puts Hash[h.sort].deep_symbolize(true).to_yaml.gsub(/---\n/, "\n").gsub(/\n/, "\n\t")
431
+ puts "----------------------------------------------------------------------------------\n"
432
+ end
433
+
434
+ def destroy
435
+ Rake::Task['eb:config'].invoke
436
+ EbDeployer.destroy(application: EbConfig.app, environment: EbConfig.environment)
437
+ end
438
+
439
+ # validate file exists
440
+ def credentials!
441
+ raise "\nFailed to load AWS secrets: #{aws_secrets_file}.\nFile contents should look like:\naccess_key_id: XXXX\nsecret_access_key: XXXX\n\n" unless File.exists?(aws_secrets_file)
442
+ credentials
443
+
444
+ ['access_key_id', 'secret_access_key'].each do |key|
445
+ value = credentials[key]
446
+ raise "\nThe #{key} must be specified in the #{aws_secrets_file}.\n\n" if value.nil?
447
+ end
448
+ end
449
+
450
+ # load from a user directory i.e. ~/.aws/acme.yml
451
+ def credentials
452
+ # load secrets from the user home directory
453
+ @credentials = YAML::load_file(aws_secrets_file) if @credentials.nil?
454
+ @credentials
455
+ end
456
+
457
+ def package_verbose?
458
+ EbConfig.package[:verbose] || false
459
+ end
460
+
461
+ def resolve_absolute_package_file
462
+
463
+ # first see if it is in the current dir, i.e. CI environment where the generated rakefile and pkg is dropped in the same place
464
+ file = EbConfig.resolve_path(package_file_name)
465
+ return file if File.exists? file
466
+
467
+ file = EbConfig.resolve_path(package_file)
468
+ return file
469
+ end
470
+
471
+ def package_file
472
+ "#{EbConfig.package[:dir]}/#{package_file_name}"
473
+ end
474
+
475
+ def package_file_name
476
+ "#{EbConfig.app}.zip"
477
+ end
478
+
479
+ def package_rakefile
480
+ "#{EbConfig.package[:dir]}/Rakefile"
481
+ end
482
+
483
+ def aws_secrets_file
484
+ File.expand_path("#{EbConfig.secrets_dir}/#{EbConfig.app}.yml")
485
+ end
486
+ end