class Slugforge::Commands::Deploy
Public Instance Methods
file(filename, *hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 34 def file(filename, *hosts) logger.say_status :deploy, "deploying local slug #{filename}", :green slug_name = File.basename(filename) deploy(hosts, slug_name, deploy_options(:copy_type => :scp, :filename => filename)) end
name(name_part, *hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 42 def name(name_part, *hosts) slug = find_slug(name_part) slug_name = File.basename(slug.key) logger.say_status :deploy, "deploying slug #{slug_name} from s3", :green url = expiring_url(slug) deploy(hosts, slug_name, deploy_options(:copy_type => :aws_cmd, :url => url, :aws_session => aws_session, :s3_url => "s3://#{aws_bucket}/#{slug.key}")) end
rollback(tag, *hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 52 def rollback(tag, *hosts) raise error_class, "There is no project found named '#{project_name}'. Try setting the project name with --project" unless tag_manager.projects.include?(project_name) data = tag_manager.rollback_slug_for_tag(project_name, tag) if data.nil? raise error_class, "could not find tag '#{tag}' for project '#{project_name}'" else logger.say_status :deploy, "deploying slug #{data}", :green slug = find_slug(data) slug_name = File.basename(data) url = expiring_url(slug) end raise error_class, "could not determine URL for tag" unless url deploy(hosts, slug_name, deploy_options(:copy_type => :aws_cmd, :url => url, :aws_session => aws_session, :s3_url => "s3://#{aws_bucket}/#{data}")) end
ssh(*hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 87 def ssh(*hosts) deploy hosts, nil, deploy_options(:copy_type => :ssh) end
tag(tag, *hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 69 def tag(tag, *hosts) raise error_class, "There is no project found named '#{project_name}'. Try setting the project name with --project" unless tag_manager.projects.include?(project_name) data = tag_manager.slug_for_tag(project_name, tag) if data.nil? raise error_class, "could not find tag '#{tag}' for project '#{project_name}'" else logger.say_status :deploy, "deploying slug #{data}", :green slug = find_slug(data) slug_name = File.basename(data) url = expiring_url(slug) end raise error_class, "could not determine URL for tag" unless url deploy(hosts, slug_name, deploy_options(:copy_type => :aws_cmd, :url => url, :aws_session => aws_session, :s3_url => "s3://#{aws_bucket}/#{data}")) end
Private Instance Methods
batch_size(host_count = 1)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 369 def batch_size(host_count = 1) if options[:'batch-count'] && host_count >= options[:'batch-count'].to_i batch_count = options[:'batch-count'] < 1 ? 1 : options[:'batch-count'] (host_count / batch_count.to_f).ceil elsif options[:'batch-size'] && host_count > options[:'batch-size'].to_i batch_size = options[:'batch-size'].to_i batch_size = batch_size < 1 ? host_count : batch_size else host_count > 1 ? host_count : 1 end end
confirm_deployment_start?(host_groups)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 213 def confirm_deployment_start?(host_groups) say_predeploy_status(host_groups) if !(force? || json? || options[:yes]) && (ask("Are you sure you wish to deploy? [yN]").downcase != 'y') logger.say_status :deploy, "deployment aborted!", :red return false end # Reset the start time for more useful reporting @command_start_time = Time.now() true end
deploy(host_patterns, slug_name, deploy_opts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 104 def deploy(host_patterns, slug_name, deploy_opts) host_groups = determine_host_groups(host_patterns) return unless confirm_deployment_start?(host_groups) # Stage file on all other hosts in facets, unless told otherwise unless options[:'no-stage'] host_groups.each do |group| group.hosts.each { |host| host.add_action(:stage) unless host.install? } end end logger.say_status :deploy, "beginning deployment", :green # Organize the list of hosts to more evenly spread load across impacted facets hosts = order_deploy(unique_hosts(host_groups)) batches = (batch_size(hosts.count) == 0) ? [hosts] : hosts.each_slice(batch_size(hosts.count)).to_a partial_deploy_type = [:count, :percent].detect{ |s| options[s] } publish('deploy.started', { :partial_deploy_method => partial_deploy_type, :partial_deploy_limit => options[partial_deploy_type], :host_groups => host_groups, :batch_size => batch_size(hosts.count), :project => project_name, :slug_name => slug_name }) deploy_in_batches(batches, slug_name, deploy_opts) logger.say_status :deploy, "deployment complete!", :green say_deploy_status(host_groups, slug_name) publish('deploy.finished', { :success => hosts.all?(&:success?), :partial_deploy_method => partial_deploy_type, :partial_deploy_limit => options[:count] || options[:percent], :host_groups => host_groups, :batch_size => batch_size(hosts.count), :project => project_name, :slug_name => slug_name }) host_groups end
deploy_in_batches(batches, slug_name, deploy_opts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 151 def deploy_in_batches(batches, slug_name, deploy_opts) pause = options[:'batch-pause'].to_i batches.each.with_index(1) do |batch, i| logger.say "deploying batch #{i} of #{batches.count}", :magenta if batches.count > 1 threads = {} batch.each do |host| thread = Thread.new do host.deploy(slug_name, logger, deploy_opts) end threads[host.ip] = thread end join_batch_threads(threads, batch, logger) unless (batches.length == i || pause == 0) logger.say "batch #{i} complete; pausing for #{pause} seconds", :magenta sleep pause end end end
deploy_options(opts={})
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 92 def deploy_options(opts={}) { :username => config.ssh_username, :deploy_dir => options[:deploy_dir] || self.deploy_dir, :owner => options[:owner], :identity => options[:identity], :env => options[:env], :force => force?, :pretend => pretend? }.merge(opts) end
determine_host_groups(host_patterns)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 207 def determine_host_groups(host_patterns) say_option_status host_patterns logger.say_status :deploy, "determining deployment targets", :green host_groups = partial_install_groups(host_groups_for_patterns(host_patterns)) end
host_groups_for_patterns(host_patterns)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 352 def host_groups_for_patterns(host_patterns) host_groups = HostGroup.discover(host_patterns, compute) raise error_class, "Unable to determine what host or group of hosts you meant with '#{pattern}'." unless host_groups # determine unique hosts in each list, then sort by IP (alphabetically) to make partial deploys essentially deterministic host_groups end
join_batch_threads(threads, hosts, logger)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 238 def join_batch_threads(threads, hosts, logger) joined = false while !joined begin threads.map { |ip,thread| thread }.map(&:join) joined = true rescue Interrupt # Ctrl+C logger.say "\nWe are #{elapsed_time} in. Stragglers for this batch:", :magenta hosts.reject { |host| host.complete? }.each_with_index do |host, stripe| logger.say " #{host.name} (Timeline: #{host.timeline})", stripe.odd? ? :cyan : :yellow end logger.say "Maybe you should give 'em them the clamps?", :magenta case ask("(T)erminate stragglers, (F)ail stragglers, (?) for help, or anything else to keep waiting:").downcase when 'f' break when 't' logger.say "Gee, you think? You think that maybe I should use these clamps that I use every day at every opportunity? You're a freakin' genius, you idiot!", :magenta hosts.each_with_index do |host, index| next if host.complete? if host.id.nil? || !host.is_autoscaled? logger.say "Can't terminate #{host.name} as it is not part of an autoscaler" else logger.say "Terminating #{host.name} (#{['Clamp.','Clamp!','Clampity Clamp!'][index%3]})", index.odd? ? :cyan : :yellow threads[host.ip].terminate host.record_event(:terminated) autoscaling.terminate_instance_in_auto_scaling_group(host.id, false) end end when '?' straggler_help end end end end
log_rollout_status(host_groups)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 320 def log_rollout_status(host_groups) return if host_groups.nil? result = { :environment => overall_status, :hostgroups => [] } host_groups.each do |host_group| host_group.hosts.each do |host| result[:hostgroups] << { :group => host_group.name }.merge(host.to_status) end end filename = "slugforge_status-#{date_stamp}.json" logger.say "Writing full status report to #{filename}" File.open(filename, "w") do |f| f.write(JSON.pretty_generate(result)) end purge_old_files 'slugforge_status-*.json' end
order_deploy(hosts)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 229 def order_deploy(hosts) # Percolate installations to the top, then stripe across batches hosts.sort! {|a,b| a.install? ? -1 : 1} results=[] batches = hosts.count / batch_size(hosts.count) hosts.each.with_index {|item, index| results[index % batches].nil? ? results[index % batches] = [item] : results[index % batches] << item } results.flatten end
overall_status()
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 336 def overall_status { :command_line => "#{$0} #{$*.join(' ')}", :options => @options, :ruby_version => RUBY_VERSION, :ec2_access_key => @ec2_access_key, :s3_access_key => @s3_access_key, :git_info => git_info, } end
partial_install_groups(host_groups)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 359 def partial_install_groups(host_groups) if options[:percent] return host_groups.each { |host_group| host_group.install_percent_of_hosts(options[:percent]) } elsif options[:count] return host_groups.each { |host_group| host_group.install_number_of_hosts(options[:count]) } end host_groups.each { |host_group| host_group.install_all } end
purge_old_files(file_mask, keep_count = 10)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 347 def purge_old_files(file_mask, keep_count = 10) old_files = Dir.glob(file_mask).sort_by{ |f| File.ctime(f) }.reverse.slice(keep_count..-1) File.delete(*old_files) end
say_deploy_status(host_groups, slug_name)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 170 def say_deploy_status(host_groups, slug_name) return nil if host_groups.nil? hosts = unique_hosts(host_groups) return nil if hosts.empty? total_count = hosts.count successful = hosts.select { |h| h.success? }.count overall_success = (total_count == successful) if json? logger.say_json :hosts => hosts.map(&:to_status), :success => overall_success else status_color = overall_success ? :green : :red logger.say "\n#{'-'*22}\n| Deployment Summary |\n#{'-'*22}", status_color logger.say "Deployed #{slug_name} to " logger.say "#{successful} ", status_color logger.say "of " logger.say "#{total_count} ", status_color logger.say "hosts in " logger.say "#{elapsed_time}", :yellow unless overall_success indent = Math.log10(total_count - successful).round + 4 logger.say "\nFailures:", :red count = 0 hosts.each do |host| unless host.success? logger.say "" logger.say "%#{indent}s" % "#{count += 1}) ", :red logger.say "#{host.name}", :red print_wrapped host.output.join("\n"), :indent => indent end end end log_rollout_status(host_groups) end end
say_option_status(host_patterns)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 282 def say_option_status(host_patterns) subset_name = if options[:count] "#{options[:count]} server#{(options[:count] == 1) ? '' : 's'}" elsif options[:percent] "#{options[:percent]}% of servers" else "all servers" end logger.say_status :deploy, "targeting #{subset_name} for: #{host_patterns.join(', ')}", :green end
say_predeploy_status(host_groups)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 293 def say_predeploy_status(host_groups) total_count = host_groups.inject(0) { |sum, host_group| sum += host_group.hosts.count } install_count = 0 host_groups.each_with_index do |host_group, stripe| raise error_class, "Host group #{host_group.name} was empty!" if host_group.hosts.nil? install_hosts = host_group.hosts_for_action(:install) install_count += install_hosts.count install_hosts.each do |host| unless json? logger.say # Add a newline for cleaner paste into Flowdock logger.say "#{host_group.name}: ", stripe.odd? ? :cyan : :yellow logger.say "#{host.name}" end end end logger.say # Add a newline for cleaner paste into Flowdock logger.say_status :deploy, "#{with_units(install_count, 'host')} targeted for installation out of #{with_units(total_count, 'host')} total", :green batch_host_count = options[:'no-stage'] ? host_groups.inject(0) { |sum, host_group| sum += host_group.hosts_for_action(:install).count } : total_count batches = (batch_host_count/batch_size(batch_host_count).to_f).ceil logger.say_status :deploy, "using #{batches} batch#{batches == 1 ? '' : 'es'} of #{with_units(batch_size(batch_host_count), 'host')} for installation #{options[:'no-stage'] ? '' : 'and staging'} ", :green end
straggler_help()
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 273 def straggler_help logger.say <<-EOF T) Attempt to terminate the stragglers and let their autoscaling group create new instances. The deploy will end if everyone who had not completed could be terminated. F) Mark the remaining stragglers are failed and end the deployment ?) Display this help and resume the deploy EOF end
unique_hosts(host_groups)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 224 def unique_hosts(host_groups) # If we're not staging the slug, return just the hosts being installed to options[:'no-stage'] ? host_groups.collect {|host_group| host_group.hosts_for_action(:install)}.flatten.uniq { |h| h.name } : host_groups.map(&:hosts).flatten.uniq { |h| h.name } end
with_units(value, unit)
click to toggle source
# File lib/slugforge/commands/deploy.rb, line 316 def with_units(value, unit) "#{value} #{unit}#{(value == 1) ? '' : 's'}" end