man_eb_deployer 0.8.0

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.
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'