man_eb_deployer 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/release.yml +31 -0
  3. data/.github/workflows/test.yml +16 -0
  4. data/.gitignore +12 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +143 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE +22 -0
  10. data/README.md +138 -0
  11. data/Rakefile +12 -0
  12. data/TODOS.md +11 -0
  13. data/bin/eb_deploy +13 -0
  14. data/eb_deployer.gemspec +22 -0
  15. data/lib/eb_deployer/application.rb +96 -0
  16. data/lib/eb_deployer/aws_driver/beanstalk.rb +158 -0
  17. data/lib/eb_deployer/aws_driver/cloud_formation_driver.rb +53 -0
  18. data/lib/eb_deployer/aws_driver/s3_driver.rb +35 -0
  19. data/lib/eb_deployer/aws_driver.rb +8 -0
  20. data/lib/eb_deployer/cf_event_source.rb +26 -0
  21. data/lib/eb_deployer/cloud_formation_provisioner.rb +120 -0
  22. data/lib/eb_deployer/component.rb +45 -0
  23. data/lib/eb_deployer/config_loader.rb +64 -0
  24. data/lib/eb_deployer/default_component.rb +32 -0
  25. data/lib/eb_deployer/default_config.rb +20 -0
  26. data/lib/eb_deployer/default_config.yml +159 -0
  27. data/lib/eb_deployer/deployment_strategy/blue_green.rb +79 -0
  28. data/lib/eb_deployer/deployment_strategy/blue_only.rb +45 -0
  29. data/lib/eb_deployer/deployment_strategy/inplace_update.rb +16 -0
  30. data/lib/eb_deployer/deployment_strategy.rb +20 -0
  31. data/lib/eb_deployer/eb_environment.rb +204 -0
  32. data/lib/eb_deployer/eb_event_source.rb +35 -0
  33. data/lib/eb_deployer/environment.rb +60 -0
  34. data/lib/eb_deployer/event_poller.rb +51 -0
  35. data/lib/eb_deployer/package.rb +39 -0
  36. data/lib/eb_deployer/resource_stacks.rb +20 -0
  37. data/lib/eb_deployer/smoke_test.rb +23 -0
  38. data/lib/eb_deployer/tasks.rb +45 -0
  39. data/lib/eb_deployer/throttling_handling.rb +17 -0
  40. data/lib/eb_deployer/utils.rb +33 -0
  41. data/lib/eb_deployer/version.rb +3 -0
  42. data/lib/eb_deployer/version_cleaner.rb +30 -0
  43. data/lib/eb_deployer.rb +339 -0
  44. data/lib/generators/eb_deployer/install/install_generator.rb +82 -0
  45. data/lib/generators/eb_deployer/install/templates/eb_deployer.rake +1 -0
  46. data/lib/generators/eb_deployer/install/templates/eb_deployer.yml.erb +181 -0
  47. data/lib/generators/eb_deployer/install/templates/ebextensions/01_postgres_packages.config +5 -0
  48. data/lib/generators/eb_deployer/install/templates/postgres_rds.json +88 -0
  49. data/test/aws_driver_stubs.rb +350 -0
  50. data/test/beanstalk_test.rb +23 -0
  51. data/test/blue_green_deploy_test.rb +114 -0
  52. data/test/blue_only_deploy_test.rb +78 -0
  53. data/test/cf_event_poller_test.rb +32 -0
  54. data/test/cloud_formation_provisioner_test.rb +47 -0
  55. data/test/config_loader_test.rb +205 -0
  56. data/test/deploy_test.rb +42 -0
  57. data/test/eb_environment_test.rb +120 -0
  58. data/test/eb_event_poller_test.rb +32 -0
  59. data/test/inplace_update_deploy_test.rb +110 -0
  60. data/test/multi_components_deploy_test.rb +164 -0
  61. data/test/rails_generators_test.rb +67 -0
  62. data/test/resources_deploy_test.rb +191 -0
  63. data/test/smoke_test_test.rb +23 -0
  64. data/test/template_deploy_test.rb +13 -0
  65. data/test/test_helper.rb +68 -0
  66. data/test/tier_setting_deploy_test.rb +24 -0
  67. data/test/versions_deploy_test.rb +120 -0
  68. metadata +176 -0
@@ -0,0 +1,39 @@
1
+ module EbDeployer
2
+ class Package
3
+ def initialize(file, bucket_name, s3_driver)
4
+ @file, @bucket_name = file, bucket_name
5
+ @s3 = s3_driver
6
+ end
7
+
8
+ def upload
9
+ ensure_bucket(@bucket_name)
10
+ upload_if_not_exists(@file, @bucket_name)
11
+ end
12
+
13
+ def source_bundle
14
+ { :s3_bucket => @bucket_name, :s3_key => s3_path }
15
+ end
16
+
17
+ private
18
+
19
+ def s3_path
20
+ @_s3_path ||= Digest::MD5.file(@file).hexdigest + "-" + File.basename(@file)
21
+ end
22
+
23
+ def ensure_bucket(bucket_name)
24
+ @s3.create_bucket(@bucket_name) unless @s3.bucket_exists?(@bucket_name)
25
+ end
26
+
27
+ def upload_if_not_exists(file, bucket_name)
28
+ if @s3.object_length(@bucket_name, s3_path) != File.size(file)
29
+ log("start uploading to s3 bucket #{@bucket_name}...")
30
+ @s3.upload_file(@bucket_name, s3_path, file)
31
+ log("uploading finished")
32
+ end
33
+ end
34
+
35
+ def log(message)
36
+ puts "[#{Time.now.utc}][package:#{File.basename(@file)}] #{message}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ module EbDeployer
2
+ class ResourceStacks
3
+ def initialize(resources, cf_driver, skip_provision, tags)
4
+ @resources = resources
5
+ @cf_driver = cf_driver
6
+ @skip_provision = skip_provision
7
+ @tags = (tags || {}).map { |k, v| { key: k, value: v } }
8
+ end
9
+
10
+ def provision(stack_name)
11
+ provisioner = CloudFormationProvisioner.new(stack_name, @cf_driver)
12
+ if @resources
13
+ provisioner.provision(@resources, @tags) unless @skip_provision
14
+ provisioner.transform_outputs(@resources)
15
+ else
16
+ []
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module EbDeployer
2
+ class SmokeTest
3
+ def initialize(test_body)
4
+ @test_body = test_body
5
+ end
6
+
7
+ def run(host_name, logger=nil)
8
+ return unless @test_body
9
+ logger.log("running smoke test for #{host_name}...") if logger
10
+
11
+ case @test_body
12
+ when Proc
13
+ @test_body.call(host_name)
14
+ when String
15
+ eval(@test_body, binding)
16
+ else
17
+ raise "smoke test can only be a string to evaluate or a proc object such as lambda"
18
+ end
19
+
20
+ logger.log("smoke test succeeded.") if logger
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+
4
+ namespace :eb do
5
+ def eb_deployer_env
6
+ ENV['EB_DEPLOYER_ENV'] || 'dev'
7
+ end
8
+
9
+ def eb_deployer_package
10
+ name = File.basename(Dir.pwd).downcase.gsub(/[^0-9a-z]/, '-').gsub(/--/, '-')
11
+ "tmp/#{name}.zip"
12
+ end
13
+
14
+ def eb_package_files
15
+ ignore_file = File.join(Dir.pwd, ".ebdeployerignore")
16
+ ignore_patterns = File.exist?(ignore_file) ? File.readlines(ignore_file).map(&:strip) : []
17
+ `git ls-files`.lines.reject { |f| ignore_patterns.any? { |p| File.fnmatch(p, f.strip) } }
18
+ end
19
+
20
+ desc "Remove the package file we generated."
21
+ task :clean do
22
+ sh "rm -rf #{eb_deployer_package}"
23
+ end
24
+
25
+ desc "Build package for eb_deployer to deploy to a Ruby environment in tmp directory. It zips all file list by 'git ls-files'"
26
+ task :package => [:clean] do
27
+ package = eb_deployer_package
28
+ FileUtils.mkdir_p(File.dirname(package))
29
+ Open3.popen2("zip #{package} -@") do |i, o, t|
30
+ i.write(eb_package_files.join)
31
+ i.close
32
+ puts o.read
33
+ end
34
+ end
35
+
36
+ desc "Deploy package we built in tmp directory. default to dev environment, specify environment variable EB_DEPLOYER_ENV to override, for example: EB_DEPLOYER_ENV=production rake eb:deploy."
37
+ task :deploy => [:package] do
38
+ sh "eb_deploy -p #{eb_deployer_package} -e #{eb_deployer_env}"
39
+ end
40
+
41
+ desc "Destroy Elastic Beanstalk environments. It won't destroy resources defined in eb_deployer.yml. Default to dev environment, specify EB_DEPLOYER_ENV to override."
42
+ task :destroy do
43
+ sh "eb_deploy -d -e #{eb_deployer_env}"
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ module EbDeployer
2
+ class ThrottlingHandling
3
+ include Utils
4
+
5
+ def initialize(delegatee, throttling_error)
6
+ @delegatee = delegatee
7
+ @throttling_error = throttling_error
8
+ end
9
+
10
+ def method_missing(method, *args, &block)
11
+ super unless @delegatee.respond_to?(method)
12
+ backoff(@throttling_error) do
13
+ @delegatee.send(method, *args, &block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module EbDeployer
2
+ module Utils
3
+ BACKOFF_INITIAL_SLEEP = 1
4
+
5
+ # A util deal with throttling exceptions
6
+ # example:
7
+ # backoff(Aws::EC2::Errors::RequestLimitExceeded) do
8
+ # ...
9
+ # end
10
+ def backoff(error_class, retry_limit=9, &block)
11
+ next_sleep = BACKOFF_INITIAL_SLEEP
12
+ begin
13
+ yield
14
+ rescue error_class
15
+ raise if retry_limit == 0
16
+ sleep(next_sleep)
17
+ next_sleep *= 2
18
+ retry_limit -= 1
19
+ retry
20
+ end
21
+ end
22
+
23
+ # convert top level key in a hash to symbol
24
+ def symbolize_keys(hash)
25
+ hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
26
+ end
27
+
28
+
29
+ def reject_nil(hash)
30
+ hash.reject{| k, v| v.nil?}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module EbDeployer
2
+ VERSION = "0.8.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ module EbDeployer
2
+ class VersionCleaner
3
+ def initialize(app, number_to_keep)
4
+ @app = app
5
+ @number_to_keep = number_to_keep
6
+ end
7
+
8
+ def clean(version_prefix = "")
9
+ if @number_to_keep > 0
10
+ versions_to_remove = versions_to_clean(version_prefix)
11
+ @app.remove(versions_to_remove, true)
12
+ end
13
+ end
14
+
15
+ private
16
+ def versions_to_clean(version_prefix = "")
17
+ all_versions = @app.versions.select do |apv|
18
+ apv[:version].start_with?(version_prefix)
19
+ end
20
+
21
+ all_versions.sort! { |x, y| y[:date_updated] <=> x[:date_updated] }
22
+ all_versions.slice!(range_to_keep)
23
+ all_versions.map { |apv| apv[:version] }
24
+ end
25
+
26
+ def range_to_keep
27
+ (0..(@number_to_keep-1))
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,339 @@
1
+ require 'digest'
2
+ require 'set'
3
+ require 'time'
4
+ require 'json'
5
+ require 'timeout'
6
+ require 'aws-sdk-s3'
7
+ require 'aws-sdk-elasticbeanstalk'
8
+ require 'aws-sdk-cloudformation'
9
+ require 'optparse'
10
+ require 'erb'
11
+ require 'fileutils'
12
+
13
+ require 'eb_deployer/version'
14
+ require 'eb_deployer/utils'
15
+ require 'eb_deployer/aws_driver'
16
+ require 'eb_deployer/deployment_strategy'
17
+ require 'eb_deployer/cloud_formation_provisioner'
18
+ require 'eb_deployer/application'
19
+ require 'eb_deployer/resource_stacks'
20
+ require 'eb_deployer/throttling_handling'
21
+ require 'eb_deployer/eb_environment'
22
+ require 'eb_deployer/environment'
23
+ require 'eb_deployer/default_component'
24
+ require 'eb_deployer/component'
25
+ require 'eb_deployer/eb_event_source'
26
+ require 'eb_deployer/cf_event_source'
27
+ require 'eb_deployer/event_poller'
28
+ require 'eb_deployer/package'
29
+ require 'eb_deployer/config_loader'
30
+ require 'eb_deployer/default_config'
31
+ require 'eb_deployer/smoke_test'
32
+ require 'eb_deployer/version_cleaner'
33
+
34
+ module EbDeployer
35
+
36
+ #
37
+ # Query ouput value of the cloud formation stack
38
+ #
39
+ # @param [String] key CloudFormation output key
40
+ # @param [Hash] opts
41
+ # @option opts [Symbol] :application application name
42
+ # @option opts [Symbol] :environment environment name (e.g. staging, production)
43
+ # @option opts [Symbol] :region AWS Region (e.g. "us-west-2", "us-east-1")
44
+ #
45
+ def self.query_resource_output(key, opts)
46
+ if region = opts[:region]
47
+ Aws.config.update({
48
+ region: region
49
+ })
50
+ end
51
+ app = opts[:application]
52
+ env_name = opts[:environment]
53
+ cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
54
+ stack_name = opts[:stack_name] || "#{app}-#{env_name}"
55
+ provisioner = CloudFormationProvisioner.new(stack_name, cf)
56
+ provisioner.output(key)
57
+ end
58
+
59
+
60
+ #
61
+ # Deploy a package to specified environments on elastic beanstalk
62
+ #
63
+ # @param [Hash] opts
64
+ #
65
+ # @option opts [Symbol] :application *required* Application name, this
66
+ # used for isolate packages and contribute to your elastic beanstalk cname
67
+ # for environments
68
+ #
69
+ # @option opts [Symbol] :environment *required* Environment for same
70
+ # application, e.g. testing, staging, production. This will map to 2 elastic
71
+ # beanstalk environments (env-a-xxx, env-b-xxx) if blue-green deployment
72
+ # strategy specified
73
+ #
74
+ # @option opts [Symbol] :package *required* package for the application
75
+ # which should be suitable for elastic beanstalk deploying. For example, a
76
+ # war file should be provided for java solution stacks and a ZIP file
77
+ # should be provided for Rails or Sinatra stack.
78
+ #
79
+ # @option opts [Symbol] :option_settings (optional) Elastic Beanstalk
80
+ # settings that will apply to the environments you deploying. Value should be
81
+ # array of hash with format such as:
82
+ #
83
+ # [{
84
+ # :namespace => 'aws:autoscaling:launchconfiguration',
85
+ # :option_name => 'InstanceType',
86
+ # :value => 'm1.small' }]
87
+ #
88
+ # When there are many, using an external yaml file to hold those
89
+ # configuration is recommended. Such as:
90
+ #
91
+ # YAML.load(File.read("my_settings_file.yml"))
92
+ #
93
+ # For all available options take a look at
94
+ # http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
95
+ #
96
+ # @option opts [Symbol] :accepted_healthy_states (['Green']) If :accepted_healthy_states
97
+ # is specified, EBDeployer will accept provided values when checking
98
+ # health of an environment instead of default value 'Green'. You can use it
99
+ # to specify additional healthy states, for example: ['Green', "Yellow"]
100
+ #
101
+ # @option opts [Symbol] :phoenix_mode (false) If phoenix mode is turn on, it
102
+ # will terminate the old elastic beanstalk environment and recreate on
103
+ # deploy. For blue-green deployment it terminate the inactive environment
104
+ # first then recreate it. This is useful to avoiding configuration drift and
105
+ # accumulating state on the EC2 instances. Also it has the benefit of keeping
106
+ # your EC2 instance system package upto date, because every time EC2 instance
107
+ # boot up from AMI it does a system update.
108
+ #
109
+ #
110
+ # @option opts [Symbol] :region set the region for application deployment
111
+ # (e.g. "us-west-2", "us-east-1"). See available zones at
112
+ # http://aws.amazon.com/elasticbeanstalk/faqs/#regions
113
+ #
114
+ # @option opts [Symbol] :resources If :resources specified, EBDeployer will
115
+ # use the CloudFormation template you provide to create a default
116
+ # CloudFormation stack with name <application_name>-<env-name> for the
117
+ # environment current deploying. Value of resources need to be hash with
118
+ # following keys:
119
+ #
120
+ # :template => CloudFormation template file with JSON format
121
+ # :policy => CloudFormation policy file with JSON format
122
+ # :override_policy => (false) If override_policy is true and a policy file is provided then the
123
+ # policy will temporarily override any existing policy on the resource stack during this update,
124
+ # otherwise the provided policy will replace any existing policy on the resource stack
125
+ # :parameters (or :inputs) => A Hash, input values for the CloudFormation template
126
+ # :transforms => A Hash with key map to your CloudFormation
127
+ # template outputs and value as lambda that return a single or array of
128
+ # elastic beanstalk settings.
129
+ #
130
+ # :capabilities => An array. You need set it to ['CAPABILITY_IAM']
131
+ #
132
+ # if you want to provision IAM Instance Profile.
133
+ #
134
+ # @option opts [Symbol] :settings See `option_settings`
135
+ #
136
+ # @option opts [Symbol] :package_bucket Name of s3 bucket where uploaded application
137
+ # packages will be stored. Note that the string ".packages" will be added as
138
+ # a suffix to your bucket. So, if "thoughtworks.simple" is passed as the bucket name,
139
+ # the actual s3 bucket name will be thoughtworks.simple.packages.
140
+ #
141
+ # @option opts [Symbol] :smoke_test Value should be a proc or a lambda which
142
+ # accept single argument that will passed in as environment DNS name. Smoke
143
+ # test proc or lambda will be called at the end of the deployment for
144
+ # inplace-update deployment strategy. For blue-green deployment it will run
145
+ # after inactive environment update finish and before switching. Defining a
146
+ # smoke test is high recommended for serious usage. The simplest one could
147
+ # just be checking the server is up using curl, e.g.
148
+ #
149
+ # :smoke_test => lambda { |host|
150
+ # curl_http_code = "curl -k -s -o /dev/null -w \"%{http_code}\" https://#{host}"
151
+ # Timeout.timeout(600) do
152
+ # while `#{curl_http_code}`.strip != '200'
153
+ # sleep 5
154
+ # end
155
+ # end
156
+ # }
157
+ #
158
+ # @option opts [Symbol] :strategy (:blue-green) There are two options:
159
+ # blue-green or inplace-update. Blue green keep two elastic beanstalk
160
+ # environments and always deploy to inactive one, to achieve zero downtime.
161
+ # inplace-update strategy will only keep one environment, and update the
162
+ # version inplace on deploy. this will save resources but will have downtime.
163
+ #
164
+ # @option opts [Symbol] :solution_stack_name ("64bit Amazon Linux 2013.09 running Tomcat 7 Java 7")
165
+ # The elastic beanstalk solution stack you want to deploy on top of.
166
+ #
167
+ # @option opts [Symbol] :tier ("WebServer")
168
+ # The environment tier. Either "WebServer" or "Worker"
169
+ #
170
+ # @option opts [Symbol] :version_label *required*. Version label give the
171
+ # package uploaded a unique identifier. Should use something related to
172
+ # pipeline counter if you have build pipeline setup to build the installer.
173
+ # For the convenience of dev we recommend use md5 digest of the installer so
174
+ # that every time you upload new installer it forms a new version. e.g.
175
+ #
176
+ # :version_label => ENV['MY_PIPELINE_COUNTER']
177
+ # || "dev-" + Digest::MD5.file(my_package).hexdigest
178
+ #
179
+ # @options opts [Symbol] :version_prefix. Specifies a prefix to prepend to the
180
+ # version label. This can be useful if you want to use different binaries for
181
+ # different environments.
182
+ #
183
+ # @option opts [Symbol] :keep_latest. Specifies the maximum number of versions to
184
+ # keep. Older versions are removed and deleted from the S3 source bucket as well.
185
+ # If specified as zero or not specified, all versions will be kept. If a
186
+ # version_prefix is given, only removes version starting with the prefix.
187
+ #
188
+ # @option opts [Symbol] :template_name. Specifies the environement template you wish
189
+ # to use to build your environment.
190
+ def self.deploy(opts)
191
+ if region = opts[:region]
192
+ Aws.config.update(:region => region)
193
+ end
194
+
195
+ bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
196
+ bs = ThrottlingHandling.new(bs, Aws::ElasticBeanstalk::Errors::Throttling)
197
+ s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
198
+ cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
199
+
200
+ app_name = opts[:application]
201
+ env_name = opts[:environment]
202
+ version_prefix = opts[:version_prefix].to_s.strip
203
+ version_label = "#{version_prefix}#{opts[:version_label].to_s.strip}"
204
+
205
+ application = Application.new(app_name, bs, s3, opts[:package_bucket])
206
+ resource_stacks = ResourceStacks.new(opts[:resources],
207
+ cf,
208
+ !!opts[:skip_resource_stack_update],
209
+ opts[:tags])
210
+
211
+ stack_name = opts[:stack_name] || "#{app_name}-#{env_name}"
212
+
213
+ environment = Environment.new(application, env_name, stack_name, bs) do |env|
214
+ env.resource_stacks = resource_stacks
215
+ env.settings = opts[:option_settings] || opts[:settings] || []
216
+ env.inactive_settings = opts[:inactive_settings] || []
217
+ env.creation_opts = {
218
+ :template_name => opts[:template_name],
219
+ :solution_stack => opts[:solution_stack_name],
220
+ :cname_prefix => opts[:cname_prefix],
221
+ :smoke_test => opts[:smoke_test],
222
+ :phoenix_mode => opts[:phoenix_mode],
223
+ :accepted_healthy_states => opts[:accepted_healthy_states],
224
+ :blue_green_terminate_inactive => opts[:blue_green_terminate_inactive] || false,
225
+ :blue_green_terminate_inactive_wait => opts[:blue_green_terminate_inactive_wait] || 600,
226
+ :blue_green_terminate_inactive_sleep => opts[:blue_green_terminate_inactive_sleep] || 15,
227
+ :tags => opts[:tags],
228
+ :tier => opts[:tier]
229
+ }
230
+ env.strategy_name = opts[:strategy] || :blue_green
231
+ env.components = opts[:components]
232
+ env.component_under_deploy = opts[:component]
233
+ end
234
+
235
+ application.create_version(version_label, opts[:package])
236
+ environment.deploy(version_label)
237
+ application.clean_versions(version_prefix, opts[:keep_latest].to_i || 0)
238
+ end
239
+
240
+ def self.destroy(opts)
241
+ if region = opts[:region]
242
+ Aws.config.update(:region => region)
243
+ end
244
+
245
+ app = opts[:application]
246
+ bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
247
+ s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
248
+
249
+ Application.new(app, bs, s3).delete(opts[:environment])
250
+ end
251
+
252
+ def self.cli
253
+ options = {
254
+ :action => :deploy,
255
+ :environment => 'dev',
256
+ :config_file => 'config/eb_deployer.yml'
257
+ }
258
+
259
+ parser = cli_parser(options)
260
+ parser.parse!
261
+ action = options.delete(:action)
262
+
263
+ if File.exist?(options[:config_file])
264
+ puts "Found configuration at #{options[:config_file]}."
265
+ else
266
+ puts "Generated default configuration at #{options[:config_file]}."
267
+ DefaultConfig.new(File.basename(Dir.pwd)).write_to(options[:config_file])
268
+ exit(2)
269
+ end
270
+
271
+ if !options[:package] && action == :deploy
272
+ puts "Missing options: -p (--package)"
273
+ puts "'eb_deploy --help' for details"
274
+ puts parser
275
+ exit(-1)
276
+ end
277
+
278
+ self.send(action, ConfigLoader.new.load(options))
279
+ end
280
+
281
+ private
282
+
283
+ def self.cli_parser(options)
284
+ OptionParser.new do |opts|
285
+ opts.banner = "Usage: eb_deployer [options]"
286
+ opts.on("-p", "--package [FILE/S3_OBJECT]", "Package to deploy, can be a war file for java application, or yaml specification for package location on S3, or a S3 object & bucket name separated by colon, e.g. bucket_name:key_name") do |v|
287
+ options[:package] = v
288
+ end
289
+
290
+ opts.on("-e", "--environment [ENV_NAME]", "(Defaults to 'dev') Environment on which to operate (e.g. dev, staging, production). This must be defined in 'environments' section of the config file") do |v|
291
+ options[:environment] = v
292
+ end
293
+
294
+ opts.on("-c", "--config-file [FILE]", "eb_deployer config file. Default location is config/eb_deployer.yml") do |v|
295
+ options[:config_file] = v
296
+ end
297
+
298
+ opts.on("-d", "--destroy", "Destroy all Elasticbeanstalk environments under the application which have specified environment as name prefix") do |v|
299
+ options[:action] = :destroy
300
+ end
301
+
302
+ opts.on("-s", "--stack-name [STACK_NAME]", "CloudFormation stack name to use. If not specified, defaults to {app}-{env_name}") do |v|
303
+ options[:stack_name] = v
304
+ end
305
+
306
+ opts.on("--skip-resource-stack-update", "Skip cloud-formation stack update. (only for extreme situation like hitting a cloudformation bug)") do |v|
307
+ options[:skip_resource_stack_update] = true
308
+ end
309
+
310
+ opts.on("--component [COMPONENT]", "Specify which component to deploy") do |v|
311
+ options[:component] = v
312
+ end
313
+
314
+ opts.on("-v", "--version", "Print current version") do |v|
315
+ puts "eb_deployer v#{VERSION}"
316
+ exit(0)
317
+ end
318
+
319
+ opts.on("--debug", "Output AWS debug log") do |d|
320
+ require 'logger'
321
+ logger = Logger.new($stdout)
322
+ logger.level = Logger::DEBUG
323
+ Aws.config.update(:logger => logger)
324
+ end
325
+
326
+ opts.on("-h", "--help", "help") do
327
+ puts opts
328
+ puts ""
329
+ puts "S3 object package format: s3_bucket_name:s3_object_key"
330
+ puts "YAML package file format:"
331
+ puts "s3_bucket: <bucket_name>"
332
+ puts "s3_key: <object_path>"
333
+ exit(0)
334
+ end
335
+ end
336
+ end
337
+
338
+
339
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails/generators'
2
+ require 'eb_deployer/default_config'
3
+ require 'aws-sdk-elasticbeanstalk'
4
+ require 'securerandom'
5
+
6
+ module EbDeployer
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ DEFAULT_STACK_NAME = '64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Passenger Standalone)'
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ def do_install
13
+ in_root do
14
+ copy_file 'eb_deployer.rake', 'lib/tasks/eb_deployer.rake'
15
+ template 'eb_deployer.yml.erb', 'config/eb_deployer.yml'
16
+ setup_database
17
+ end
18
+ end
19
+
20
+ private
21
+ def setup_database
22
+ gem 'pg'
23
+ setup_database_yml
24
+ copy_file 'postgres_rds.json', 'config/rds.json'
25
+ directory 'ebextensions', '.ebextensions'
26
+ end
27
+
28
+ def setup_database_yml
29
+ gsub_file('config/database.yml', /^production:.+/m) do |match|
30
+ prod_start = false
31
+ match.split("\n").map do |l|
32
+ case l
33
+ when /^production/
34
+ prod_start = true
35
+ "# #{l}"
36
+ when /^\s+/
37
+ prod_start ? "# #{l}" : l
38
+ else
39
+ prod_start = false
40
+ l
41
+ end
42
+ end.join("\n")
43
+ end
44
+ append_to_file('config/database.yml', <<-YAML)
45
+
46
+
47
+ production:
48
+ adapter: postgresql
49
+ database: <%= ENV['DATABASE_NAME'] || '#{app_name}_production' %>
50
+ host: <%= ENV['DATABASE_HOST'] || 'localhost' %>
51
+ port: <%= ENV['DATABASE_PORT'] || 5432 %>
52
+ username: <%= ENV['DATABASE_USERNAME'] || #{ENV['USER'].inspect} %>
53
+ password: <%= ENV['DATABASE_PASSWORD'] %>
54
+ min_messages: ERROR
55
+ YAML
56
+ end
57
+ def db_password
58
+ "PleaseChangeMe"
59
+ end
60
+
61
+ def solution_stack_name
62
+ Aws::ElasticBeanstalk.Client.new.list_available_solution_stacks[:solution_stacks].find do |s|
63
+ s =~ /Amazon Linux/ && s =~ /running Ruby 2.1 \(Passenger Standalone\)/
64
+ end
65
+ rescue
66
+ DEFAULT_STACK_NAME
67
+ end
68
+
69
+ def alphanumeric_name
70
+ app_name.gsub(/-/, '')
71
+ end
72
+
73
+ def secure_random(length)
74
+ SecureRandom.hex(length)
75
+ end
76
+
77
+ def app_name
78
+ File.basename(Dir.pwd).downcase.gsub(/[^0-9a-z]/, '-').gsub(/--/, '-')
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1 @@
1
+ require 'eb_deployer/tasks'