297 lines
11 KiB
Ruby
297 lines
11 KiB
Ruby
require 'log4r'
|
|
|
|
require "vagrant/util/safe_puts"
|
|
|
|
module Vagrant
|
|
module Plugin
|
|
module V2
|
|
# This is the base class for a CLI command.
|
|
class Command
|
|
include Util::SafePuts
|
|
|
|
# This should return a brief (60 characters or less) synopsis of what
|
|
# this command does. It will be used in the output of the help.
|
|
#
|
|
# @return [String]
|
|
def self.synopsis
|
|
""
|
|
end
|
|
|
|
def initialize(argv, env)
|
|
@argv = argv
|
|
@env = env
|
|
@logger = Log4r::Logger.new("vagrant::command::#{self.class.to_s.downcase}")
|
|
end
|
|
|
|
# This is what is called on the class to actually execute it. Any
|
|
# subclasses should implement this method and do any option parsing
|
|
# and validation here.
|
|
def execute
|
|
end
|
|
|
|
protected
|
|
|
|
# Parses the options given an OptionParser instance.
|
|
#
|
|
# This is a convenience method that properly handles duping the
|
|
# originally argv array so that it is not destroyed.
|
|
#
|
|
# This method will also automatically detect "-h" and "--help"
|
|
# and print help. And if any invalid options are detected, the help
|
|
# will be printed, as well.
|
|
#
|
|
# If this method returns `nil`, then you should assume that help
|
|
# was printed and parsing failed.
|
|
def parse_options(opts=nil)
|
|
# Creating a shallow copy of the arguments so the OptionParser
|
|
# doesn't destroy the originals.
|
|
argv = @argv.dup
|
|
|
|
# Default opts to a blank optionparser if none is given
|
|
opts ||= OptionParser.new
|
|
|
|
# Add the help option, which must be on every command.
|
|
opts.on_tail("-h", "--help", "Print this help") do
|
|
safe_puts(opts.help)
|
|
return nil
|
|
end
|
|
|
|
opts.parse!(argv)
|
|
return argv
|
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
|
raise Errors::CLIInvalidOptions, help: opts.help.chomp
|
|
end
|
|
|
|
# Yields a VM for each target VM for the command.
|
|
#
|
|
# This is a convenience method for easily implementing methods that
|
|
# take a target VM (in the case of multi-VM) or every VM if no
|
|
# specific VM name is specified.
|
|
#
|
|
# @param [String] name The name of the VM. Nil if every VM.
|
|
# @param [Hash] options Additional tweakable settings.
|
|
# @option options [Symbol] :provider The provider to back the
|
|
# machines with. All machines will be backed with this
|
|
# provider. If none is given, a sensible default is chosen.
|
|
# @option options [Boolean] :reverse If true, the resulting order
|
|
# of machines is reversed.
|
|
# @option options [Boolean] :single_target If true, then an
|
|
# exception will be raised if more than one target is found.
|
|
def with_target_vms(names=nil, options=nil)
|
|
@logger.debug("Getting target VMs for command. Arguments:")
|
|
@logger.debug(" -- names: #{names.inspect}")
|
|
@logger.debug(" -- options: #{options.inspect}")
|
|
|
|
# Setup the options hash
|
|
options ||= {}
|
|
|
|
# Require that names be an array
|
|
names ||= []
|
|
names = [names] if !names.is_a?(Array)
|
|
|
|
# Determine if we require a local Vagrant environment. There are
|
|
# two cases that we require a local environment:
|
|
#
|
|
# * We're asking for ANY/EVERY VM (no names given).
|
|
#
|
|
# * We're asking for specific VMs, at least once of which
|
|
# is NOT in the local machine index.
|
|
#
|
|
requires_local_env = false
|
|
requires_local_env = true if names.empty?
|
|
requires_local_env ||= names.any? { |n|
|
|
!@env.machine_index.include?(n)
|
|
}
|
|
raise Errors::NoEnvironmentError if requires_local_env && !@env.root_path
|
|
|
|
# Cache the active machines outside the loop
|
|
active_machines = @env.active_machines
|
|
|
|
# This is a helper that gets a single machine with the proper
|
|
# provider. The "proper provider" in this case depends on what was
|
|
# given:
|
|
#
|
|
# * If a provider was explicitly specified, then use that provider.
|
|
# But if an active machine exists with a DIFFERENT provider,
|
|
# then throw an error (for now), since we don't yet support
|
|
# bringing up machines with different providers.
|
|
#
|
|
# * If no provider was specified, then use the active machine's
|
|
# provider if it exists, otherwise use the default provider.
|
|
#
|
|
get_machine = lambda do |name|
|
|
# Check for an active machine with the same name
|
|
provider_to_use = options[:provider]
|
|
provider_to_use = provider_to_use.to_sym if provider_to_use
|
|
|
|
# If we have this machine in our index, load that.
|
|
entry = @env.machine_index.get(name.to_s)
|
|
if entry
|
|
@env.machine_index.release(entry)
|
|
|
|
# Create an environment for this location and yield the
|
|
# machine in that environment. We silence warnings here because
|
|
# Vagrantfiles often have constants, so people would otherwise
|
|
# constantly (heh) get "already initialized constant" warnings.
|
|
begin
|
|
env = entry.vagrant_env(
|
|
@env.home_path, ui_class: @env.ui_class)
|
|
rescue Vagrant::Errors::EnvironmentNonExistentCWD
|
|
# This means that this environment working directory
|
|
# no longer exists, so delete this entry.
|
|
entry = @env.machine_index.get(name.to_s)
|
|
@env.machine_index.delete(entry) if entry
|
|
raise
|
|
end
|
|
|
|
next env.machine(entry.name.to_sym, entry.provider.to_sym)
|
|
end
|
|
|
|
active_machines.each do |active_name, active_provider|
|
|
if name == active_name
|
|
# We found an active machine with the same name
|
|
|
|
if provider_to_use && provider_to_use != active_provider
|
|
# We found an active machine with a provider that doesn't
|
|
# match the requested provider. Show an error.
|
|
raise Errors::ActiveMachineWithDifferentProvider,
|
|
name: active_name.to_s,
|
|
active_provider: active_provider.to_s,
|
|
requested_provider: provider_to_use.to_s
|
|
else
|
|
# Use this provider and exit out of the loop. One of the
|
|
# invariants [for now] is that there shouldn't be machines
|
|
# with multiple providers.
|
|
@logger.info("Active machine found with name #{active_name}. " +
|
|
"Using provider: #{active_provider}")
|
|
provider_to_use = active_provider
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
# Use the default provider if nothing else
|
|
provider_to_use ||= @env.default_provider(machine: name)
|
|
|
|
# Get the right machine with the right provider
|
|
@env.machine(name, provider_to_use)
|
|
end
|
|
|
|
# First determine the proper array of VMs.
|
|
machines = []
|
|
if names.length > 0
|
|
names.each do |name|
|
|
if pattern = name[/^\/(.+?)\/$/, 1]
|
|
@logger.debug("Finding machines that match regex: #{pattern}")
|
|
|
|
# This is a regular expression name, so we convert to a regular
|
|
# expression and allow that sort of matching.
|
|
regex = Regexp.new(pattern)
|
|
|
|
@env.machine_names.each do |machine_name|
|
|
if machine_name =~ regex
|
|
machines << get_machine.call(machine_name)
|
|
end
|
|
end
|
|
|
|
raise Errors::VMNoMatchError if machines.empty?
|
|
else
|
|
# String name, just look for a specific VM
|
|
@logger.debug("Finding machine that match name: #{name}")
|
|
machines << get_machine.call(name.to_sym)
|
|
raise Errors::VMNotFoundError, name: name if !machines[0]
|
|
end
|
|
end
|
|
else
|
|
# No name was given, so we return every VM in the order
|
|
# configured.
|
|
@logger.debug("Loading all machines...")
|
|
machines = @env.machine_names.map do |machine_name|
|
|
get_machine.call(machine_name)
|
|
end
|
|
end
|
|
|
|
# Make sure we're only working with one VM if single target
|
|
if options[:single_target] && machines.length != 1
|
|
@logger.debug("Using primary machine since single target")
|
|
primary_name = @env.primary_machine_name
|
|
raise Errors::MultiVMTargetRequired if !primary_name
|
|
machines = [get_machine.call(primary_name)]
|
|
end
|
|
|
|
# If we asked for reversed ordering, then reverse it
|
|
machines.reverse! if options[:reverse]
|
|
|
|
# Go through each VM and yield it!
|
|
color_order = [:default]
|
|
color_index = 0
|
|
|
|
machines.each do |machine|
|
|
# Set the machine color
|
|
machine.ui.opts[:color] = color_order[color_index % color_order.length]
|
|
color_index += 1
|
|
|
|
@logger.info("With machine: #{machine.name} (#{machine.provider.inspect})")
|
|
yield machine
|
|
|
|
# Call the state method so that we update our index state. Don't
|
|
# worry about exceptions here, since we just care about updating
|
|
# the cache.
|
|
begin
|
|
# Called for side effects
|
|
machine.state
|
|
rescue Errors::VagrantError
|
|
end
|
|
end
|
|
end
|
|
|
|
# This method will split the argv given into three parts: the
|
|
# flags to this command, the subcommand, and the flags to the
|
|
# subcommand. For example:
|
|
#
|
|
# -v status -h -v
|
|
#
|
|
# The above would yield 3 parts:
|
|
#
|
|
# ["-v"]
|
|
# "status"
|
|
# ["-h", "-v"]
|
|
#
|
|
# These parts are useful because the first is a list of arguments
|
|
# given to the current command, the second is a subcommand, and the
|
|
# third are the commands given to the subcommand.
|
|
#
|
|
# @return [Array] The three parts.
|
|
def split_main_and_subcommand(argv)
|
|
# Initialize return variables
|
|
main_args = nil
|
|
sub_command = nil
|
|
sub_args = []
|
|
|
|
# We split the arguments into two: One set containing any
|
|
# flags before a word, and then the rest. The rest are what
|
|
# get actually sent on to the subcommand.
|
|
argv.each_index do |i|
|
|
if !argv[i].start_with?("-")
|
|
# We found the beginning of the sub command. Split the
|
|
# args up.
|
|
main_args = argv[0, i]
|
|
sub_command = argv[i]
|
|
sub_args = argv[i + 1, argv.length - i + 1]
|
|
|
|
# Break so we don't find the next non flag and shift our
|
|
# main args.
|
|
break
|
|
end
|
|
end
|
|
|
|
# Handle the case that argv was empty or didn't contain any subcommand
|
|
main_args = argv.dup if main_args.nil?
|
|
|
|
return [main_args, sub_command, sub_args]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|