elastic-beanstalk 0.0.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,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