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