class Vagrant::Plugin::V2::Command

This is the base class for a CLI command.

Public Class Methods

new(argv, env) click to toggle source
# File lib/vagrant/plugin/v2/command.rb, line 20
def initialize(argv, env)
  @argv = argv
  @env  = env
  @logger = Log4r::Logger.new("vagrant::command::#{self.class.to_s.downcase}")
end
synopsis() click to toggle source

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]

# File lib/vagrant/plugin/v2/command.rb, line 16
def self.synopsis
  ""
end

Public Instance Methods

execute() click to toggle source

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.

# File lib/vagrant/plugin/v2/command.rb, line 29
def execute
end

Protected Instance Methods

parse_options(opts=nil) click to toggle source

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.

# File lib/vagrant/plugin/v2/command.rb, line 45
def parse_options(opts=nil)
  # make sure optparse doesn't use POSIXLY_CORRECT parsing
  ENV["POSIXLY_CORRECT"] = 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 ||= Vagrant::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, OptionParser::AmbiguousOption
  raise Errors::CLIInvalidOptions, help: opts.help.chomp
end
split_main_and_subcommand(argv) click to toggle source

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.

# File lib/vagrant/plugin/v2/command.rb, line 273
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
with_target_vms(names=nil, options=nil) { |machine| ... } click to toggle source

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.
# File lib/vagrant/plugin/v2/command.rb, line 83
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|
    if (machine.state && machine.state.id != :not_created &&
        !machine.index_uuid.nil? && !@env.machine_index.include?(machine.index_uuid))
      machine.recover_machine(machine.state.id)
    end

    # 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