Module: EbDeployer

Defined in:
lib/eb_deployer.rb,
lib/eb_deployer/package.rb,
lib/eb_deployer/version.rb,
lib/eb_deployer/aws_driver.rb,
lib/eb_deployer/smoke_test.rb,
lib/eb_deployer/application.rb,
lib/eb_deployer/environment.rb,
lib/eb_deployer/event_poller.rb,
lib/eb_deployer/config_loader.rb,
lib/eb_deployer/default_config.rb,
lib/eb_deployer/eb_environment.rb,
lib/eb_deployer/resource_stacks.rb,
lib/eb_deployer/version_cleaner.rb,
lib/eb_deployer/deployment_strategy.rb,
lib/eb_deployer/aws_driver/beanstalk.rb,
lib/eb_deployer/aws_driver/s3_driver.rb,
lib/eb_deployer/cloud_formation_provisioner.rb,
lib/eb_deployer/deployment_strategy/blue_green.rb,
lib/eb_deployer/aws_driver/cloud_formation_driver.rb,
lib/eb_deployer/deployment_strategy/inplace_update.rb

Defined Under Namespace

Modules: AWSDriver, DeploymentStrategy Classes: Application, CloudFormationProvisioner, ConfigLoader, DefaultConfig, EbEnvironment, Environment, EventPoller, Package, ResourceNotInReadyState, ResourceStacks, SmokeTest, VersionCleaner

Constant Summary collapse

TIERS =
[
  {:name=>"Worker", :type=>"SQS/HTTP", :version=>"1.0"},
  {:name=>"WebServer", :type=>"Standard", :version=>"1.0"}
]
VERSION =
"0.4.0"

Class Method Summary collapse

Class Method Details

.cli ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/eb_deployer.rb', line 233

def self.cli
  options = {
    :action => :deploy,
    :environment => 'dev',
    :config_file => 'config/eb_deployer.yml'
  }

  parser = cli_parser(options)
  parser.parse!
  action = options.delete(:action)

  raise "--all is only valid with --destroy" if (options[:all_envs] && action != :destroy)

  if File.exists?(options[:config_file])
    puts "Found configuration at #{options[:config_file]}."
  else
    puts "Generated default configuration at #{options[:config_file]}."
    DefaultConfig.new(File.basename(Dir.pwd)).write_to(options[:config_file])
    exit(2)
  end

  if !options[:package] && action == :deploy
    puts "Missing options: -p (--package)"
    puts "'eb_deploy --help' for details"
    puts parser
    exit(-1)
  end

  self.send(action, ConfigLoader.new.load(options))
end

.cli_parser(options) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/eb_deployer.rb', line 266

def self.cli_parser(options)
  OptionParser.new do |opts|
    opts.banner = "Usage: eb_deployer [options]"
    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 with bucket name saperated by colon, e.g. bucket_name:key_name") do |v|
      options[:package] = v
    end

    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|
      options[:environment] = v
    end

    opts.on("-c", "--config-file [FILE]", "eb_deployer config file. Default location is config/eb_deployer.yml") do |v|
      options[:config_file] = v
    end

    opts.on("-d", "--destroy", "Destroy specified environment") do |v|
      options[:action] = :destroy
    end

    opts.on("--all", "Operate on all environments, only valid with --destroy") do |v|
      options[:all_envs] = true
    end

    opts.on("--skip-resource-stack-update", "skip cloud-formation stack update. (only for extreme situation like hitting a cloudformation bug)") do |v|
      options[:skip_resource_stack_update] = true
    end

    opts.on("-v", "--version", "Print current version") do |v|
      puts "eb_deployer v#{VERSION}"
      exit(0)
    end

    opts.on("--debug", "Output AWS debug log") do |d|
      require 'logger'
      logger = Logger.new($stdout)
      logger.level = Logger::DEBUG
      AWS.config(:logger => logger)
    end

    opts.on("-h", "--help", "help")  do
      puts opts
      puts ""
      puts "S3 object package format: s3_bucket_name:s3_object_key"
      puts "YAML package file format:"
      puts "s3_bucket: <bucket_name>"
      puts "s3_key: <object_path>"
      exit(0)
    end
  end
end

.deploy(opts) ⇒ Object

Deploy a package to specfied environments on elastic beanstalk

packages will be stored. Note that the string ".packages" will be added as a suffix to your bucket. So, if "thoughtworks.simple" is passed as the bucket name, the actual s3 bucket name will be thoughtworks.simple.packages.

Parameters:

  • opts (Hash)

Options Hash (opts):

  • :application (Symbol) —

    required Application name, this used for isolate packages and contribute to your elastic beanstalk cname for environments

  • :environment (Symbol) —

    required Environment for same application, e.g. testing, staging, production. This will map to 2 elastic beanstalk environments (env-a-xxx, env-b-xxx) if blue-green deployment strategy specified

  • :package (Symbol) —

    required package for the application which should be suitable for elastic beanstalk deploying. For example, a war file should be provided for java solution stacks and a ZIP file should be provided for Rails or Sinatra stack.

  • :option_settings (Symbol) — default: optional —

    Elastic Beanstalk settings that will apply to the environments you deploying. Value should be array of hash with format such as:

    [{
     :namespace => 'aws:autoscaling:launchconfiguration',
     :option_name => 'InstanceType',
     :value => 'm1.small' }]
    

    When there are many, Using an external yaml file to hold those configuration is recommended. Such as:

    YAML.load(File.read("my_settings_file.yml"))
    

    For all available options take a look at http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html

  • :phoenix_mode (Symbol) — default: false —

    If phoenix mode is turn on, it will terminate the old elastic beanstalk environment and recreate on deploy. For blue-green deployment it terminate the inactive environment first then recreate it. This is useful to avoiding configuration drift and accumulating state on the EC2 instances. Also it has the benifit of keeping your EC2 instance system package upto date, because everytime EC2 instance boot up from AMI it does a system update.

  • :region (Symbol) —

    set the region for application deployment (e.g. "us-west-2", "us-east-1"). See available zones at http://aws.amazon.com/elasticbeanstalk/faqs/#regions

  • :resources (Symbol) —

    If :resources specified, EBDeployer will use the CloudFormation template you provide to create a default CloudFormation stack with name <application_name>- for the environment current deploying. Value of resources need to be hash with following keys:

    :template => CloudFormation template file with JSON format
    :parameters (or :inputs) => A Hash, input values for the CloudFormation template
    :transforms => A Hash with key map to your CloudFormation
    

    template outputs and value as lambda that return a single or array of elastic beanstalk settings.

    :capabilities => An array. You need set it to ['CAPABILITY_IAM']
    

    if you want to provision IAM Instance Profile.

  • :settings (Symbol) —

    See option_settings

  • :package_bucket (Symbol) —

    Name of s3 bucket where uploaded application

  • :smoke_test (Symbol) —

    Value should be a proc or a lambda which accept single argument that will passed in as environment DNS name. Smoke test proc or lambda will be called at the end of the deployment for inplace-update deployment strategy. For blue-green deployment it will run after inactive environment update finish and before switching. Defining a smoke test is high recommended for serious usage. The simplest one could just be checking the server is up using curl, e.g.

    :smoke_test => lambda { |host|
    curl_http_code = "curl -k -s -o /dev/null -w \"%{http_code}\" https://#{host}"
    Timeout.timeout(600) do
      while `#{curl_http_code}`.strip != '200'
        sleep 5
      end
    end
    }
  • :strategy (Symbol) — default: :blue-green —

    There are two options: blue-green or inplace-update. Blue green keep two elastic beanstalk environments and always deploy to inactive one, to achive zero downtime. inplace-update strategy will only keep one environment, and update the version inplace on deploy. this will save resources but will have downtime.

  • :solution_stack_name (Symbol) — default: "64bit Amazon Linux 2013.09 running Tomcat 7 Java 7" —

    The elastic beanstalk solution stack you want to deploy on top of.

  • :tier (Symbol) — default: "WebServer" —

    The environment tier. Either "WebServer" or "Worker"

  • :version_label (Symbol) —

    required. Version label give the package uploaded a unique identifier. Should use something related to pipeline counter if you have build pipeline setup to build the installer. For the convient of dev we recommend use md5 digest of the installer so that everytime you upload new installer it forms a new version. e.g.

    :version_label => ENV['MY_PIPELINE_COUNTER']
                    || "dev-" + Digest::MD5.file(my_package).hexdigest
  • :keep_latest. (Symbol) —

    Specifies the maximum number of versions to keep. Older versions are removed and deleted from the S3 source bucket as well. If specified as zero or not specified, all versions will be kept. If a version_prefix is given, only removes version starting with the prefix.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/eb_deployer.rb', line 176

def self.deploy(opts)
  if region = opts[:region]
    AWS.config(:region => region)
  end

  bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
  s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
  cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
  stack_name = opts[:solution_stack_name] || "64bit Amazon Linux 2013.09 running Tomcat 7 Java 7"
  app = opts[:application]
  env_name = opts[:environment]
  version_prefix = opts[:version_prefix].to_s.strip
  version_label = "#{version_prefix}#{opts[:version_label].to_s.strip}"
  cname = opts[:cname]
  env_settings = opts[:option_settings] || opts[:settings] || []
  strategy_name = opts[:strategy] || :blue_green
  cname_prefix = opts[:cname_prefix]
  smoke_test = opts[:smoke_test] || Proc.new {}
  phoenix_mode = opts[:phoenix_mode]
  bucket = opts[:package_bucket] || app
  skip_resource = opts[:skip_resource_stack_update]
  keep_latest = opts[:keep_latest].to_i || 0
  app_tier = self.environment_tier(opts[:tier] || 'WebServer')

  resource_stacks = ResourceStacks.new(opts[:resources], cf, skip_resource)
  application = Application.new(app, bs, s3, bucket)
  environment = Environment.new(application,
                                env_name,
                                resource_stacks,
                                env_settings,
                                {
                                  :solution_stack => stack_name,
                                  :cname_prefix => cname_prefix,
                                  :smoke_test => smoke_test,
                                  :phoenix_mode => phoenix_mode,
                                  :tier => app_tier
                                },
                                bs)

  application.create_version(version_label, opts[:package])
  environment.deploy(version_label, strategy_name)
  application.clean_versions(version_prefix, keep_latest)
end

.destroy(opts) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/eb_deployer.rb', line 220

def self.destroy(opts)
  if region = opts[:region]
    AWS.config(:region => region)
  end

  app = opts[:application]
  bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
  s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
  cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new

  Application.new(app, bs, s3).delete(opts[:environment])
end

.environment_tier(name) ⇒ Object



33
34
35
# File 'lib/eb_deployer.rb', line 33

def environment_tier(name)
  TIERS.find {|t| t[:name].downcase == name.downcase} || raise("No tier found with name #{name.inspect}")
end

.query_resource_output(key, opts) ⇒ Object

Query ouput value of the cloud formation stack

Parameters:

  • key (String) —

    CloudFormation output key

  • opts (Hash)

Options Hash (opts):

  • :application (Symbol) —

    application name

  • :environment (Symbol) —

    environment name (e.g. staging, production)

  • :region (Symbol) —

    AWS Region (e.g. "us-west-2", "us-east-1")



46
47
48
49
50
51
52
53
54
55
# File 'lib/eb_deployer.rb', line 46

def self.query_resource_output(key, opts)
  if region = opts[:region]
    AWS.config(:region => region)
  end
  app = opts[:application]
  env_name = opts[:environment]
  cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
  provisioner = CloudFormationProvisioner.new("#{app}-#{env_name}", cf)
  provisioner.output(key)
end