lib/vagrant-orchestrate/command/push.rb in vagrant-orchestrate-0.3.2 vs lib/vagrant-orchestrate/command/push.rb in vagrant-orchestrate-0.4.0
- old
+ new
@@ -1,46 +1,134 @@
require "optparse"
require "vagrant"
+class Array
+ def in_groups(num_groups)
+ return [] if num_groups == 0
+ slice_size = (size / Float(num_groups)).ceil
+ each_slice(slice_size).to_a
+ end
+end
+
module VagrantPlugins
module Orchestrate
module Command
class Push < Vagrant.plugin("2", :command)
include Vagrant::Util
+ @logger = Log4r::Logger.new("vagrant_orchestrate::command::push")
+
+ # rubocop:disable Metrics/AbcSize, MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def execute
options = {}
+ options[:force] = @env.vagrantfile.config.orchestrate.force_push
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant orchestrate push"
o.separator ""
o.on("--reboot", "Reboot a managed server after the provisioning step") do
options[:reboot] = true
end
+
+ o.on("--strategy strategy", "The orchestration strategy to use. Default is serial") do |v|
+ options[:strategy] = v
+ end
+
+ o.on("-f", "--force", "Suppress prompting in between groups") do
+ options[:force] = true
+ end
end
# Parse the options
argv = parse_options(opts)
+ return unless argv
+ machines = []
with_target_vms(argv) do |machine|
- unless machine.provider_name.to_sym == :managed
- @env.ui.info("Skipping machine #{machine.name}")
- next
+ if machine.provider_name.to_sym == :managed
+ machines << machine
+ else
+ @logger.debug("Skipping #{machine.name} because it doesn't use the :managed provider")
end
- push(machine, options)
end
+
+ if machines.empty?
+ @env.ui.info("No servers with :managed provider found. Skipping.")
+ return
+ end
+
+ # This environment variable is used as a signal to the filtermanaged
+ # action so that we don't filter managed commands that are really just
+ # the implementation of a push action.
+
+ options[:parallel] = true
+ strategy = options[:strategy] || @env.vagrantfile.config.orchestrate.strategy
+ @env.ui.info("Pushing to managed servers using #{strategy} strategy.")
+ case strategy.to_sym
+ when :serial
+ options[:parallel] = false
+ result = deploy(options, machines)
+ when :parallel
+ result = deploy(options, machines)
+ when :canary
+ # A single canary server and then the rest
+ result = deploy(options, machines.take(1), machines.drop(1))
+ when :blue_green
+ # Split into two (almost) equal groups
+ groups = machines.in_groups(2)
+ result = deploy(options, groups.first, groups.last)
+ when :canary_blue_green
+ # A single canary and then two equal groups
+ canary = machines.take(1)
+ groups = machines.drop(1).in_groups(2)
+ result = deploy(options, canary, groups.first, groups.last)
+ else
+ @env.ui.error("Invalid deployment strategy specified")
+ result = false
+ end
+
+ return 1 unless result
+ 0
end
+ # rubocop:enable Metrics/AbcSize, MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
- def push(machine, options)
- ENV["VAGRANT_ORCHESTRATE_COMMAND"] = "PUSH"
- begin
- machine.action(:up, options)
- machine.action(:provision, options)
- machine.action(:reload, options) if options[:reboot]
- machine.action(:destroy, options)
- ensure
- ENV.delete "VAGRANT_ORCHESTRATE_COMMAND"
+ def deploy(options, *groups)
+ groups.each_with_index do |machines, index|
+ @logger.debug("Orchestrating push to group number #{index + 1} of #{groups.size}.")
+ @logger.debug(" -- Hosts: #{machines.collect { |m| m.name.to_s }.join(',')}")
+ ENV["VAGRANT_ORCHESTRATE_COMMAND"] = "PUSH"
+ begin
+ batchify(machines, :up, options)
+ batchify(machines, :provision, options)
+ batchify(machines, :reload, options) if options[:reboot]
+ batchify(machines, :destroy, options)
+ @logger.debug("Finished orchestrating push to group number #{index + 1} of #{groups.size}.")
+ ensure
+ ENV.delete "VAGRANT_ORCHESTRATE_COMMAND"
+ end
+
+ # Don't prompt on the last group, that would be annoying
+ unless index == groups.size - 1 || options[:force]
+ return false unless prompt_for_continue
+ end
+ end
+ end
+
+ def prompt_for_continue
+ result = @env.ui.ask("Deployment paused for manual review. Would you like to continue? (y/n)")
+ if result.upcase != "Y"
+ @env.ui.info("Deployment push action by user")
+ return false
+ end
+ true
+ end
+
+ def batchify(machines, action, options)
+ @env.batch(options[:parallel]) do |batch|
+ machines.each do |machine|
+ batch.action(machine, action, options)
+ end
end
end
end
end
end