def launch(opts={})
      verify_required_options(opts, REQUIRED_LAUNCH_PARAMETERS)

      # attempt to accept keys as file paths
      opts['git_key'] = try_file(opts['git_key'])
      opts['chef_data_bag_secret'] = try_file(opts['chef_data_bag_secret'])

      # generate tags and merge in any that were specefied as inputs
      tags = {
        'Name' => "#{opts['chef_role']}-#{opts['chef_environment']}",
        'Group' => "#{opts['chef_role']}-#{opts['chef_environment']}",
        'created_by' => opts.fetch('user', ENV['USER']),
        'stemcell' => VERSION,
      }
      # Short name if we're in production
      tags['Name'] = opts['chef_role'] if opts['chef_environment'] == 'production'
      tags.merge!(opts['tags']) if opts['tags']

      # generate launch options
      launch_options = {
        :image_id => opts['image_id'],
        :instance_type => opts['instance_type'],
        :key_name => opts['key_name'],
        :count => opts['count'],
      }

      if opts['security_group_ids'] && !opts['security_group_ids'].empty?
        launch_options[:security_group_ids] = opts['security_group_ids']
      end

      if opts['security_groups'] && !opts['security_groups'].empty?
        if @vpc_id
          # convert sg names to sg ids as VPC only accepts ids
          security_group_ids = get_vpc_security_group_ids(@vpc_id, opts['security_groups'])
          launch_options[:security_group_ids] ||= []
          launch_options[:security_group_ids].concat(security_group_ids)
        else
          launch_options[:security_groups] = opts['security_groups']
        end
      end

      # specify availability zone (optional)
      if opts['availability_zone']
        launch_options[:availability_zone] = opts['availability_zone']
      end

      if opts['subnet']
        launch_options[:subnet] = opts['subnet']
      end

      if opts['private_ip_address']
        launch_options[:private_ip_address] = opts['private_ip_address']
      end

      if opts['dedicated_tenancy']
        launch_options[:dedicated_tenancy] = opts['dedicated_tenancy']
      end

      if opts['associate_public_ip_address']
        launch_options[:associate_public_ip_address] = opts['associate_public_ip_address']
      end

      # specify IAM role (optional)
      if opts['iam_role']
        launch_options[:iam_instance_profile] = opts['iam_role']
      end

      # specify placement group (optional)
      if opts['placement_group']
        launch_options[:placement] = {
          :group_name => opts['placement_group'],
        }
      end

      # specify an EBS-optimized instance (optional)
      launch_options[:ebs_optimized] = true if opts['ebs_optimized']

      # specify placement group (optional)
      if opts['instance_initiated_shutdown_behavior']
        launch_options[:instance_initiated_shutdown_behavior] =
          opts['instance_initiated_shutdown_behavior']
      end

      # specify raw block device mappings (optional)
      if opts['block_device_mappings']
        launch_options[:block_device_mappings] = opts['block_device_mappings']
      end

      # specify ephemeral block device mappings (optional)
      if opts['ephemeral_devices']
        launch_options[:block_device_mappings] ||= []
        opts['ephemeral_devices'].each_with_index do |device,i|
          launch_options[:block_device_mappings].push ({
            :device_name => device,
            :virtual_name => "ephemeral#{i}"
          })
        end
      end

      # generate user data script to bootstrap instance, include in launch
      # options UNLESS we have manually set the user-data (ie. for ec2admin)
      launch_options[:user_data] = opts.fetch('user_data', render_template(opts))

      # launch instances
      instances = do_launch(launch_options)

      # everything from here on out must succeed, or we kill the instances we just launched
      begin
        # set tags on all instances launched
        set_tags(instances, tags)
        @log.info "sent ec2 api tag requests successfully"

        # link to classiclink
        unless @vpc_id
          set_classic_link(instances, opts['classic_link'])
          @log.info "successfully applied classic link settings (if any)"
        end

        # turn on termination protection
        # we do this now to make sure all other settings worked
        if opts['termination_protection']
          enable_termination_protection(instances)
          @log.info "successfully enabled termination protection"
        end

        # wait for aws to report instance stats
        if opts.fetch('wait', true)
          wait(instances)
          print_run_info(instances)
          @log.info "launched instances successfully"
        end
      rescue => e
        @log.info "launch failed, killing all launched instances"
        begin
          kill(instances, :ignore_not_found => true)
        rescue => kill_error
          @log.warn "encountered an error during cleanup: #{kill_error.message}"
        end
        raise e
      end

      return instances
    end
def launch(opts={})
      verify_required_options(opts, REQUIRED_LAUNCH_PARAMETERS)

      # attempt to accept keys as file paths
      opts['git_key'] = try_file(opts['git_key'])
      opts['chef_data_bag_secret'] = try_file(opts['chef_data_bag_secret'])

      # generate tags and merge in any that were specefied as inputs
      tags = {
        'Name' => "#{opts['chef_role']}-#{opts['chef_environment']}",
        'Group' => "#{opts['chef_role']}-#{opts['chef_environment']}",
        'created_by' => opts.fetch('user', ENV['USER']),
        'stemcell' => VERSION,
      }
      # Short name if we're in production
      tags['Name'] = opts['chef_role'] if opts['chef_environment'] == 'production'
      tags.merge!(opts['tags']) if opts['tags']

      # generate launch options
      launch_options = {
        :image_id => opts['image_id'],
        :instance_type => opts['instance_type'],
        :key_name => opts['key_name'],
        :count => opts['count'],
      }

      if opts['security_group_ids'] && !opts['security_group_ids'].empty?
        launch_options[:security_group_ids] = opts['security_group_ids']
      end

      if opts['security_groups'] && !opts['security_groups'].empty?
        if @vpc_id
          # convert sg names to sg ids as VPC only accepts ids
          security_group_ids = get_vpc_security_group_ids(@vpc_id, opts['security_groups'])
          launch_options[:security_group_ids] ||= []
          launch_options[:security_group_ids].concat(security_group_ids)
        else
          launch_options[:security_groups] = opts['security_groups']
        end
      end

      # specify availability zone (optional)
      if opts['availability_zone']
        launch_options[:availability_zone] = opts['availability_zone']
      end

      if opts['subnet']
        launch_options[:subnet] = opts['subnet']
      end

      if opts['private_ip_address']
        launch_options[:private_ip_address] = opts['private_ip_address']
      end

      if opts['dedicated_tenancy']
        launch_options[:dedicated_tenancy] = opts['dedicated_tenancy']
      end

      if opts['associate_public_ip_address']
        launch_options[:associate_public_ip_address] = opts['associate_public_ip_address']
      end

      # specify IAM role (optional)
      if opts['iam_role']
        launch_options[:iam_instance_profile] = opts['iam_role']
      end

      # specify placement group (optional)
      if opts['placement_group']
        launch_options[:placement] = {
          :group_name => opts['placement_group'],
        }
      end

      # specify an EBS-optimized instance (optional)
      launch_options[:ebs_optimized] = true if opts['ebs_optimized']

      # specify placement group (optional)
      if opts['instance_initiated_shutdown_behavior']
        launch_options[:instance_initiated_shutdown_behavior] =
          opts['instance_initiated_shutdown_behavior']
      end

      # specify raw block device mappings (optional)
      if opts['block_device_mappings']
        launch_options[:block_device_mappings] = opts['block_device_mappings']
      end

      # specify ephemeral block device mappings (optional)
      if opts['ephemeral_devices']
        launch_options[:block_device_mappings] ||= []
        opts['ephemeral_devices'].each_with_index do |device,i|
          launch_options[:block_device_mappings].push ({
            :device_name => device,
            :virtual_name => "ephemeral#{i}"
          })
        end
      end

      # generate user data script to bootstrap instance, include in launch
      # options UNLESS we have manually set the user-data (ie. for ec2admin)
      launch_options[:user_data] = opts.fetch('user_data', render_template(opts))

      # launch instances
      instances = do_launch(launch_options)

      # everything from here on out must succeed, or we kill the instances we just launched
      begin
        # set tags on all instances launched
        set_tags(instances, tags)
        @log.info "sent ec2 api tag requests successfully"

        # link to classiclink
        unless @vpc_id
          set_classic_link(instances, opts['classic_link'])
          @log.info "successfully applied classic link settings (if any)"
        end

        # turn on termination protection
        # we do this now to make sure all other settings worked
        if opts['termination_protection']
          enable_termination_protection(instances)
          @log.info "successfully enabled termination protection"
        end

        # wait for aws to report instance stats
        if opts.fetch('wait', true)
          wait(instances)
          print_run_info(instances)
          @log.info "launched instances successfully"
        end
      rescue => e
        @log.info "launch failed, killing all launched instances"
        begin
          kill(instances, :ignore_not_found => true)
        rescue => kill_error
          @log.warn "encountered an error during cleanup: #{kill_error.message}"
        end
        raise e
      end

      return instances
    end
seconds