enhanced-elastic-beanstalk 1.2.3

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,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