elastic-beanstalk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ module ElasticBeanstalk
2
+
3
+ module EbExtensions
4
+ # it's a singleton, thus implemented as a self-extended module
5
+ extend self
6
+
7
+ def write_extensions
8
+
9
+ ebextensions = EbConfig.ebextensions
10
+ return if ebextensions.nil?
11
+
12
+ Dir.mkdir absolute_file_name(nil) rescue nil
13
+
14
+ ebextensions.each_key do |filename|
15
+ contents = EbConfig.ebextensions[filename]
16
+
17
+ filename = absolute_file_name(filename)
18
+
19
+ # when converting to_yaml, kill the symbols as EB doesn't like it.
20
+ contents = contents.deep_symbolize(true).to_yaml.gsub(/---\n/, "")
21
+ #puts "\n#{filename}:\n----------------------------------------------------\n#{contents}----------------------------------------------------\n"
22
+ File.write(filename, contents)
23
+ end
24
+ end
25
+
26
+ def delete_extensions
27
+ ebextensions = EbConfig.ebextensions
28
+ return if ebextensions.nil?
29
+
30
+ ebextensions.each_key do |filename|
31
+ File.delete(absolute_file_name filename)
32
+ end
33
+ end
34
+
35
+ def absolute_file_name(filename)
36
+ EbConfig.resolve_path(".ebextensions/#{filename}")
37
+ end
38
+
39
+ def ebextensions_dir(filename)
40
+ EbConfig.resolve_path(".ebextensions/#{filename}")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ module ElasticBeanstalk
2
+
3
+ module EbSmokeTester
4
+ # it's a singleton, thus implemented as a self-extended module
5
+ extend self
6
+
7
+ def test_url(url, timeout, sleep_wait, expected_text)
8
+
9
+ puts '-------------------------------------------------------------------------------'
10
+ # puts "Smoke Testing: \n\turl: #{url}\n\ttimeout: #{timeout}\n\tsleep_wait: #{sleep_wait}\n\texpected_text: #{expected_text}\n"
11
+ puts "Smoke Testing: \n\turl: #{url}\n\ttimeout: #{timeout}\n\texpected_text: #{expected_text}\n"
12
+ response = nil
13
+ begin
14
+ Timeout.timeout(timeout) do
15
+ i = 0
16
+ begin
17
+ sleep sleep_wait.to_i unless (i == 0)
18
+ i += 1
19
+ begin
20
+ response = Net::HTTP.get_response(URI(url))
21
+ rescue SocketError => e
22
+ response = ResponseStub.new({code: e.message, body: ''})
23
+ end
24
+
25
+ puts "\t\t[#{response.code}]"
26
+ #puts "\t#{response.body}"
27
+ end until (!response.nil? && response.code.to_i == 200 && response.body.include?(expected_text))
28
+ end
29
+ ensure
30
+ puts "\nFinal response: \n\tcode: [#{response.code}] \n\texpectation met: #{response.body.include?(expected_text)}"
31
+ puts '-------------------------------------------------------------------------------'
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ class ResponseStub
38
+
39
+ attr_reader :code, :body
40
+
41
+ def initialize(args)
42
+ @code = args[:code]
43
+ @body = args[:body]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ require 'elastic_beanstalk'
2
+ require 'rails'
3
+
4
+ module ElasticBeanstalk
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :elastic_beanstalk
7
+
8
+ rake_tasks do
9
+ load '/lib/tasks/eb.rake'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module ElasticBeanstalk
2
+ VERSION = '0.0.1'
3
+ end
data/lib/tasks/eb.rake ADDED
@@ -0,0 +1,250 @@
1
+ require 'digest'
2
+ require 'zipruby' # gem 'zipruby'
3
+ require 'ap' # gem 'awesome_print'
4
+ require 'elastic_beanstalk/eb_config'
5
+ require 'elastic_beanstalk/eb_extensions'
6
+
7
+ namespace :eb do
8
+
9
+ ###########################################
10
+ #
11
+ #
12
+ #
13
+ desc 'Setup AWS.config and merge/override environments into one resolved configuration'
14
+ task :config do |t, args|
15
+
16
+ # set the default environment to be development
17
+ #env = ENV['RAILS_ENV'] || Rails.env || 'development'
18
+ env = ENV['RAILS_ENV'] || 'development'
19
+
20
+ # load the configuration
21
+ EbConfig.load!(env)
22
+
23
+ # 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
24
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', "#{EbConfig.environment}")
25
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RAILS_ENV', "#{EbConfig.environment}")
26
+
27
+ # resolve the version and set the APP_VERSION environment variable
28
+ resolve_version(args)
29
+
30
+ # configure aws credentials
31
+ AWS.config(credentials)
32
+
33
+ # configure aws region if specified in the eb.yml
34
+ AWS.config(region: EbConfig.region) unless EbConfig.region.nil?
35
+ end
36
+
37
+ ###########################################
38
+ #
39
+ #
40
+ #
41
+ desc 'Show resolved configuration without doing anything.'
42
+ task :show_config, [:version] => [:config] do |t, args|
43
+
44
+
45
+ puts "Working Directory: #{Rake.original_dir}"
46
+
47
+ resolve_version(args)
48
+ print_config
49
+ end
50
+
51
+ ###########################################
52
+ #
53
+ #
54
+ #
55
+ desc 'Remove any generated package.'
56
+ task :clobber do |t, args|
57
+ # kill the old package dir
58
+ rm_r EbConfig.package[:dir] rescue nil
59
+ #puts "Clobbered #{EbConfig.package[:dir]}."
60
+ end
61
+
62
+ ###########################################
63
+ #
64
+ # Elastic Beanstalk seems to be finicky with a tar.gz. Using a zip, EB wants the files to be at the
65
+ # root of the archive, not under a top level folder. Include this package task to make
66
+ # sure we don't need to learn about this again through long deploy cycles!
67
+ #
68
+ desc 'Package zip source bundle for Elastic Beanstalk'
69
+ task :package => [:clobber, :config] do |t, args|
70
+
71
+ begin
72
+ # write .ebextensions
73
+ EbExtensions.write_extensions
74
+
75
+ # include all
76
+ files = FileList[EbConfig.package[:includes]]
77
+
78
+ # exclude files
79
+ EbConfig.package[:exclude_files].each do |file|
80
+ files.exclude(file)
81
+ end
82
+
83
+ EbConfig.package[:exclude_dirs].each do |dir|
84
+ files.exclude("#{dir}/**/*")
85
+ files.exclude("#{dir}")
86
+ end
87
+
88
+ # ensure dir exists
89
+ mkdir_p EbConfig.package[:dir] rescue nil
90
+
91
+ # zip it up
92
+ Zip::Archive.open(package_file, Zip::CREATE) do |archive|
93
+
94
+ puts "\nCreating archive (#{package_file}):" if package_verbose?
95
+ files.each do |f|
96
+
97
+ if File.directory?(f)
98
+ puts "\t#{f}" if package_verbose?
99
+ archive.add_dir(f)
100
+ else
101
+ puts "\t\t#{f}" if package_verbose?
102
+ archive.add_file(f, f)
103
+ end
104
+ end
105
+ end
106
+ puts "\nFinished creating archive (#{package_file})."
107
+ ensure
108
+ EbExtensions.delete_extensions
109
+ end
110
+ end
111
+
112
+
113
+ ###########################################
114
+ #
115
+ #
116
+ #
117
+ desc 'Deploy to Elastic Beanstalk'
118
+ task :deploy, [:version] => [:config] do |t, args|
119
+ # Leave off the dependency of :package, we need to package this in the build phase and save
120
+ # the artifact on bamboo. The deploy plan will handle this separately.
121
+ from_time = Time.now
122
+
123
+ # check package file
124
+ raise "Package file not found (#{absolute_package_file}). Be sure to run the :package task subsequent to any :deploy attempts." if !File.exists? absolute_package_file
125
+
126
+ # Don't deploy to test or cucumber (or whatever is specified by :disallow_environments)
127
+ 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
128
+
129
+ # Use the version if given, otherwise use the MD5 hash. Make available via the eb environment variable
130
+ version = resolve_version(args)
131
+ print_config
132
+
133
+ # Avoid known problems
134
+ if EbConfig.find_option_setting_value('InstanceType').nil?
135
+ sleep 1 # let the puts from :config task finish first
136
+ 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"
137
+ end
138
+
139
+ options = {
140
+ application: EbConfig.app,
141
+ environment: EbConfig.environment,
142
+ version_label: version,
143
+ solution_stack_name: EbConfig.solution_stack_name,
144
+ settings: EbConfig.option_settings,
145
+ strategy: EbConfig.strategy.to_sym,
146
+ package: absolute_package_file
147
+ }
148
+
149
+ unless EbConfig.smoke_test.nil?
150
+ options[:smoke_test] = eval EbConfig.smoke_test
151
+ end
152
+
153
+ EbDeployer.deploy(options)
154
+
155
+ puts "\nDeployment finished in #{Time.diff(from_time, Time.now, '%N %S')[:diff]}.\n"
156
+ end
157
+
158
+ ###########################################
159
+ #
160
+ #
161
+ #
162
+ desc '** Warning: Destroy Elastic Beanstalk application and *all* environments.'
163
+ task :destroy, [:force] do |t, args|
164
+
165
+ if args[:force].eql? 'y'
166
+ destroy()
167
+ else
168
+ puts 'Are you sure you wish to destroy application and *all* environments? (y/n)'
169
+ input = STDIN.gets.strip
170
+ if input == 'y'
171
+ destroy()
172
+ else
173
+ puts 'Destroy canceled.'
174
+ end
175
+ end
176
+ end
177
+
178
+ ##########################################
179
+ private
180
+
181
+ # Use the version if given, otherwise use the MD5 hash. Make available via the eb APP_VERSION environment variable
182
+ def resolve_version(args)
183
+
184
+ # if already set by a dependency call to :config, get out early
185
+ version = EbConfig.find_option_setting_value('APP_VERSION')
186
+ return version unless version.nil?
187
+
188
+ # try to grab from task argument first
189
+ version = args[:version]
190
+ if version.nil? && File.exists?(absolute_package_file)
191
+ # otherwise use the MD5 hash of the package file
192
+ version = Digest::MD5.file(absolute_package_file).hexdigest
193
+ end
194
+
195
+ # set the var, depending on the sequence of calls, this may be nil
196
+ # (i.e. :show_config with no :version argument) so omit it until we have something worthwhile.
197
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'APP_VERSION', "#{version}") unless version.nil?
198
+ end
199
+
200
+ def print_config
201
+ # display helpful for figuring out problems later in the deployment logs.
202
+ puts "\n----------------------------------------------------------------------------------"
203
+ puts "Elastic Beanstalk configuration:"
204
+ puts "\t:access_key_id: #{credentials['access_key_id']}"
205
+
206
+ # pretty print things that will be useful to see in the deploy logs and omit clutter that usually doesn't cause us problems.
207
+ h = EbConfig.configuration.dup
208
+ h.delete(:package)
209
+ h.delete(:disallow_environments)
210
+ puts Hash[h.sort].deep_symbolize(true).to_yaml.gsub(/---\n/, "\n").gsub(/\n/, "\n\t")
211
+ puts "----------------------------------------------------------------------------------\n"
212
+ end
213
+
214
+ def destroy
215
+ Rake::Task['eb:config'].invoke
216
+ EbDeployer.destroy(application: EbConfig.app)
217
+ puts "Destroy issued to AWS."
218
+ end
219
+
220
+ # load from a user directory i.e. ~/.aws.acme.yml
221
+ def credentials
222
+
223
+ raise "Failed to load AWS secrets: #{aws_secrets_file}.\nFile contents should look like:\naccess_key_id: XXXX\nsecret_access_key: XXXX" unless File.exists?(aws_secrets_file)
224
+
225
+ # load secrets from the user home directory
226
+ @credentials = YAML.safe_load_file(aws_secrets_file) if @credentials.nil?
227
+ @credentials
228
+ end
229
+
230
+ def package_verbose?
231
+ EbConfig.package[:verbose]
232
+ end
233
+
234
+ def package_file
235
+ "#{EbConfig.package[:dir]}/#{EbConfig.app}.zip"
236
+ end
237
+
238
+ def aws_secrets_file
239
+ File.expand_path("~/.aws.#{EbConfig.app}.yml")
240
+ end
241
+
242
+ def absolute_package_file
243
+ filename = package_file()
244
+ unless filename.start_with? '/'
245
+ filename = filename.gsub('[', '').gsub(']', '')
246
+ filename = EbConfig.resolve_path(filename)
247
+ end
248
+ filename
249
+ end
250
+ end
@@ -0,0 +1,47 @@
1
+ # https://gist.github.com/morhekil/998709
2
+
3
+ require 'rspec'
4
+ require 'deep_symbolize'
5
+
6
+ describe 'Hash#deep_symbolize' do
7
+ let(:hash) {{}}
8
+ subject do
9
+ #hash.extend DeepSymbolizable
10
+ hash.deep_symbolize
11
+ end
12
+
13
+ context 'on simple hash when inverting' do
14
+ let(:hash) {{ 'key1' => 'val1', 'key2' => 'val2' }}
15
+ it {
16
+ h2 = hash.deep_symbolize
17
+ h2.should == { :key1 => 'val1', :key2 => 'val2' }
18
+ h3 = h2.deep_symbolize(true)
19
+ h3.should == { 'key1' => 'val1', 'key2' => 'val2' }
20
+ }
21
+ end
22
+
23
+
24
+ context 'on simple hash' do
25
+ let(:hash) {{ :key1 => 'val1', 'key2' => 'val2' }}
26
+ it { should == { :key1 => 'val1', :key2 => 'val2' } }
27
+ end
28
+
29
+ context 'on nested hash' do
30
+ let(:hash) {{ 'key1' => 'val1', 'subkey' => { 'key2' => 'val2' } }}
31
+ it { should == { :key1 => 'val1', :subkey => { :key2 => 'val2' } } }
32
+ end
33
+
34
+ context 'on a hash with nested array' do
35
+ let(:hash) {{ 'key1' => 'val1', 'subkey' => [{ 'key2' => 'val2' }] }}
36
+ it { should == { :key1 => 'val1', :subkey => [{ :key2 => 'val2' }] } }
37
+ end
38
+
39
+ describe 'preprocessing keys' do
40
+ subject do
41
+ #hash.extend DeepSymbolizable
42
+ hash.deep_symbolize { |k| k.upcase }
43
+ end
44
+ let(:hash) {{ 'key1' => 'val1', 'subkey' => [{ 'key2' => 'val2' }] }}
45
+ it { should == { :KEY1 => 'val1', :SUBKEY => [{ :KEY2 => 'val2' }] } }
46
+ end
47
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe EbConfig do
4
+ # not sure why, but clear appears to unreliably run before each test. Sometimes does, sometimes doesn't.
5
+ #before do
6
+ # EbConfig.clear
7
+ # puts 'clear'
8
+ #end
9
+
10
+ it '#set_option' do
11
+ EbConfig.clear
12
+ EbConfig.set_option('aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'staging')
13
+ expect(EbConfig.options[:'aws:elasticbeanstalk:application:environment'][:'RACK_ENV']).to eq 'staging'
14
+ end
15
+
16
+ it '#find_option_setting_value' do
17
+ EbConfig.clear
18
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'staging')
19
+ expect(EbConfig.find_option_setting_value('RACK_ENV')).to eql 'staging'
20
+ end
21
+ it '#find_option_setting' do
22
+ EbConfig.clear
23
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'staging')
24
+ expect(EbConfig.find_option_setting('RACK_ENV')).to eql ({:namespace => 'aws:elasticbeanstalk:application:environment', :option_name => 'RACK_ENV', :value => 'staging'})
25
+ end
26
+
27
+ it '#set_option should allow options to be overridden' do
28
+ EbConfig.clear
29
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'staging')
30
+ assert_option 'RACK_ENV', 'staging'
31
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'foo')
32
+ assert_option 'RACK_ENV', 'foo'
33
+ end
34
+
35
+ it 'should read file with nil environment' do
36
+ EbConfig.clear
37
+ sleep 1
38
+ #expect(EbConfig.strategy).to be_nil
39
+
40
+ EbConfig.load!(nil, config_file_path)
41
+ assert_common_top_level_settings()
42
+ assert_option 'InstanceType', 'foo'
43
+ #expect(EbConfig.strategy).to be_nil
44
+ expect(EbConfig.environment).to be_nil
45
+ end
46
+
47
+ it 'should read file and override with development environment' do
48
+ EbConfig.clear
49
+ EbConfig.load!(:development, config_file_path)
50
+ assert_option 'InstanceType', 't1.micro'
51
+ expect(EbConfig.strategy).to eql 'inplace-update'
52
+ expect(EbConfig.environment).to eql 'development'
53
+ end
54
+
55
+ it 'should read file and override with production environment' do
56
+ EbConfig.clear
57
+ EbConfig.load!(:production, config_file_path)
58
+ assert_option 'InstanceType', 't1.small'
59
+ expect(EbConfig.environment).to eql 'production'
60
+ end
61
+
62
+ private
63
+ def assert_option(name, value)
64
+ expect(EbConfig.find_option_setting_value(name)).to eql value
65
+ end
66
+
67
+ def assert_common_top_level_settings
68
+ expect(EbConfig.app).to eql 'acme'
69
+ expect(EbConfig.region).to eql 'us-east-1'
70
+ expect(EbConfig.strategy).to eql :blue_green
71
+ expect(EbConfig.solution_stack_name).to eql '64bit Amazon Linux running Ruby 1.9.3'
72
+ expect(EbConfig.disallow_environments).to eql %w(cucumber test)
73
+
74
+ expect(EbConfig.package[:dir]).to eql 'pkg'
75
+ expect(EbConfig.package[:verbose]).to be_true
76
+ expect(EbConfig.package[:includes]).to eql %w(**/* .ebextensions/**/*)
77
+ expect(EbConfig.package[:exclude_files]).to eql %w(resetdb.sh rspec.xml README* db/*.sqlite3)
78
+ expect(EbConfig.package[:exclude_dirs]).to eql %w(pkg tmp log test-reports solr features)
79
+
80
+ # assert set_option new
81
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RACK_ENV', 'staging')
82
+ assert_option 'RACK_ENV', 'staging'
83
+
84
+ # assert set_option overwrite
85
+ EbConfig.set_option(:'aws:elasticbeanstalk:application:environment', 'RAILS_ENV', 'staging')
86
+ assert_option 'RAILS_ENV', 'staging'
87
+
88
+ assert_option 'EC2KeyName', 'eb-ssh'
89
+
90
+ assert_option 'SecurityGroups', 'acme-production-control'
91
+ assert_option 'MinSize', '1'
92
+ assert_option 'MaxSize', '5'
93
+ assert_option 'SSLCertificateId', 'arn:aws:iam::XXXXXXX:server-certificate/acme'
94
+ assert_option 'LoadBalancerHTTPSPort', '443'
95
+ assert_option 'Stickiness Policy', 'true'
96
+ assert_option 'Notification Endpoint', 'alerts@acme.com'
97
+ assert_option 'Application Healthcheck URL', '/healthcheck'
98
+ end
99
+
100
+ def config_file_path
101
+ File.expand_path('../eb_spec.yml', __FILE__)
102
+ end
103
+ end