Skip to content

Instantly share code, notes, and snippets.

@swinton
Created February 13, 2013 15:23
Show Gist options
  • Select an option

  • Save swinton/4945349 to your computer and use it in GitHub Desktop.

Select an option

Save swinton/4945349 to your computer and use it in GitHub Desktop.
Ugly way of opening a Django shell via a Vagrant command... there must be a better way?
Vagrant::Config.run do |config|
config.vm.box = "lucid64"
config.vm.box_url = "http://files.vagrantup.com/lucid64.box"
config.vm.host_name = "myvagrantbox"
# etc.
end
class DjangoShellCommand < Vagrant::Command::Base
def execute
# Open the interactive Django shell on the Vagrant box
exec 'ssh -t myvagrantbox "python manage.py shell"'
end
end
Vagrant.commands.register(:shell) { DjangoShellCommand }
@swinton

swinton commented Feb 13, 2013

Copy link
Copy Markdown
Author

@swinton

swinton commented Feb 13, 2013

Copy link
Copy Markdown
Author

content of vagrant/ssh.rb

require 'log4r'

require 'vagrant/util/file_util'
require 'vagrant/util/file_mode'
require 'vagrant/util/platform'
require 'vagrant/util/safe_exec'

module Vagrant
  # Manages SSH connection information as well as allows opening an
  # SSH connection.
  class SSH
    include Util::SafeExec

    def initialize(vm)
      @vm     = vm
      @logger = Log4r::Logger.new("vagrant::ssh")
    end

    # Returns a hash of information necessary for accessing this
    # virtual machine via SSH.
    #
    # @return [Hash]
    def info
      results = {
        :host          => @vm.config.ssh.host,
        :port          => @vm.config.ssh.port || @vm.driver.ssh_port(@vm.config.ssh.guest_port),
        :username      => @vm.config.ssh.username,
        :forward_agent => @vm.config.ssh.forward_agent,
        :forward_x11   => @vm.config.ssh.forward_x11
      }

      # This can happen if no port is set and for some reason Vagrant
      # can't detect an SSH port.
      raise Errors::SSHPortNotDetected if !results[:port]

      # Determine the private key path, which is either set by the
      # configuration or uses just the built-in insecure key.
      pk_path = @vm.config.ssh.private_key_path || @vm.env.default_private_key_path
      results[:private_key_path] = File.expand_path(pk_path, @vm.env.root_path)

      # We need to check and fix the private key permissions
      # to make sure that SSH gets a key with 0600 perms.
      check_key_permissions(results[:private_key_path])

      # Return the results
      return results
    end

    # Connects to the environment's virtual machine, replacing the ruby
    # process with an SSH process.
    #
    # @param [Hash] opts Options hash
    # @options opts [Boolean] :plain_mode If True, doesn't authenticate with
    #   the machine, only connects, allowing the user to connect.
    def exec(opts={})
      # Get the SSH information and cache it here
      ssh_info = info

      # Ensure the platform supports ssh. On Windows there are several programs which
      # include ssh, notably git, mingw and cygwin, but make sure ssh is in the path!
      if !Util::FileUtil.which("ssh")
        if Util::Platform.windows?
          raise Errors::SSHUnavailableWindows, :host => ssh_info[:host],
                                               :port => ssh_info[:port],
                                               :username => ssh_info[:username],
                                               :key_path => ssh_info[:private_key_path]
        end
        raise Errors::SSHUnavailable 
      end

      # If plain mode is enabled then we don't do any authentication (we don't
      # set a user or an identity file)
      plain_mode = opts[:plain_mode]

      options = {}
      options[:host] = ssh_info[:host]
      options[:port] = ssh_info[:port]
      options[:username] = ssh_info[:username]
      options[:private_key_path] = ssh_info[:private_key_path]

      # Command line options
      command_options = ["-p", options[:port].to_s, "-o", "UserKnownHostsFile=/dev/null",
                         "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=QUIET"]

      # Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the IdentitiesOnly option
      # (Also don't use it in plain mode, it'll skip user agents.)
      command_options += ["-o", "IdentitiesOnly=yes"] if !(Util::Platform.solaris? || plain_mode)

      command_options += ["-i", options[:private_key_path]] if !plain_mode
      command_options += ["-o", "ForwardAgent=yes"] if ssh_info[:forward_agent]

      # If there are extra options, then we append those
      command_options.concat(opts[:extra_args]) if opts[:extra_args]

      if ssh_info[:forward_x11]
        # Both are required so that no warnings are shown regarding X11
        command_options += ["-o", "ForwardX11=yes"]
        command_options += ["-o", "ForwardX11Trusted=yes"]
      end

      host_string = options[:host]
      host_string = "#{options[:username]}@#{host_string}" if !plain_mode
      command_options << host_string
      @logger.info("Invoking SSH: #{command_options.inspect}")
      safe_exec("ssh", *command_options)
    end

    # Checks the file permissions for a private key, resetting them
    # if needed.
    def check_key_permissions(key_path)
      # Windows systems don't have this issue
      return if Util::Platform.windows?

      @logger.debug("Checking key permissions: #{key_path}")
      stat = File.stat(key_path)

      if stat.owned? && Util::FileMode.from_octal(stat.mode) != "600"
        @logger.info("Attempting to correct key permissions to 0600")
        File.chmod(0600, key_path)

        stat = File.stat(key_path)
        if Util::FileMode.from_octal(stat.mode) != "600"
          raise Errors::SSHKeyBadPermissions, :key_path => key_path
        end
      end
    rescue Errno::EPERM
      # This shouldn't happen since we verified we own the file, but
      # it is possible in theory, so we raise an error.
      raise Errors::SSHKeyBadPermissions, :key_path => key_path
    end
  end
end

@swinton

swinton commented Feb 13, 2013

Copy link
Copy Markdown
Author

More clues, from https://github.com/mitchellh/vagrant/blob/v1.0.6/lib/vagrant/command/ssh.rb

require 'optparse'

module Vagrant
  module Command
    class SSH < Base
      def execute
        options = {}

        opts = OptionParser.new do |opts|
          opts.banner = "Usage: vagrant ssh [vm-name] [-c command] [-- extra ssh args]"

          opts.separator ""

          opts.on("-c", "--command COMMAND", "Execute an SSH command directly.") do |c|
            options[:command] = c
          end
          opts.on("-p", "--plain", "Plain mode, leaves authentication up to user.") do |p|
            options[:plain_mode] = p
          end
        end

        # Parse the options and return if we don't have any target.
        argv = parse_options(opts)
        return if !argv

        # Parse out the extra args to send to SSH, which is everything
        # after the "--"
        ssh_args = ARGV.drop_while { |i| i != "--" }
        ssh_args = ssh_args[1..-1]
        options[:ssh_args] = ssh_args

        # If the remaining arguments ARE the SSH arguments, then just
        # clear it out. This happens because optparse returns what is
        # after the "--" as remaining ARGV, and Vagrant can think it is
        # a multi-vm name (wrong!)
        argv = [] if argv == ssh_args

        # Execute the actual SSH
        with_target_vms(argv, :single_target => true) do |vm|
          # Basic checks that are required for proper SSH
          raise Errors::VMNotCreatedError if !vm.created?
          raise Errors::VMInaccessible if !vm.state == :inaccessible
          raise Errors::VMNotRunningError if vm.state != :running

          if options[:command]
            ssh_execute(vm, options[:command])
          else
            opts = {
              :plain_mode => options[:plain_mode],
              :extra_args => options[:ssh_args]
            }

            ssh_connect(vm, opts)
          end
        end

        # Success, exit status 0
        0
       end

      protected

      def ssh_execute(vm, command=nil)
        exit_status = 0

        @logger.debug("Executing command: #{command}")
        exit_status = vm.channel.execute(command, :error_check => false) do |type, data|
          # Determine the proper channel to send the output onto depending
          # on the type of data we are receiving.
          channel = type == :stdout ? :out : :error

          # Print the SSH output as it comes in, but don't prefix it and don't
          # force a new line so that the output is properly preserved
          vm.ui.info(data.to_s,
                     :prefix => false,
                     :new_line => false,
                     :channel => channel)
        end

        # Exit with the exit status we got from executing the command
        exit exit_status
      end

      def ssh_connect(vm, opts)
        @logger.debug("`exec` into ssh prompt")
        vm.ssh.exec(opts)
      end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment