Merge pull request #6591 from mitchellh/sethvargo/ports

Add `vagrant port` command
This commit is contained in:
Seth Vargo 2015-11-25 15:39:59 -05:00
commit c24a44a7c6
8 changed files with 320 additions and 1 deletions

View File

@ -0,0 +1,90 @@
require "vagrant/util/presence"
require "optparse"
module VagrantPlugins
module CommandPort
class Command < Vagrant.plugin("2", :command)
include Vagrant::Util::Presence
def self.synopsis
"displays information about guest port mappings"
end
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant port [options] [name]"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--guest PORT", "Output the host port that maps to the given guest port") do |port|
options[:guest] = port
end
o.on("--machine-readable", "Display machine-readable output")
end
# Parse the options
argv = parse_options(opts)
return if !argv
with_target_vms(argv, single_target: true) do |vm|
vm.action_raw(:config_validate,
Vagrant::Action::Builtin::ConfigValidate)
if !vm.provider.capability?(:forwarded_ports)
@env.ui.error(I18n.t("port_command.missing_capability",
provider: vm.provider_name,
))
return 1
end
ports = vm.provider.capability(:forwarded_ports)
if !present?(ports)
@env.ui.info(I18n.t("port_command.empty_ports"))
return 0
end
if present?(options[:guest])
return print_single(vm, ports, options[:guest])
else
return print_all(vm, ports)
end
end
end
private
# Print all the guest <=> host port mappings.
# @return [0] the exit code
def print_all(vm, ports)
@env.ui.info(I18n.t("port_command.details"))
@env.ui.info("")
ports.each do |host, guest|
@env.ui.info("#{guest.to_s.rjust(6)} (guest) => #{host} (host)")
@env.ui.machine("forwarded_port", guest, host, target: vm.name.to_s)
end
return 0
end
# Print the host mapping that matches the given guest target.
# @return [0,1] the exit code
def print_single(vm, ports, target)
map = ports.find { |_, guest| "#{guest}" == "#{target}" }
if !present?(map)
@env.ui.error(I18n.t("port_command.no_matching_port",
port: target,
))
return 1
end
@env.ui.info("#{map[0]}")
return 0
end
end
end
end

View File

@ -0,0 +1,20 @@
en:
port_command:
details: |-
The forwarded ports for the machine are listed below. Please note that
these values may differ from values configured in the Vagrantfile if the
provider supports automatic port collision detection and resolution.
empty_ports: |-
The provider reported there are no forwarded ports for this virtual
machine. This can be caused if there are no ports specified in the
Vagrantfile or if the virtual machine is not currently running. Please
check that the virtual machine is running and try again.
missing_capability: |-
The %{provider} provider does not support listing forwarded ports. This is
most likely a limitation of the provider and not a bug in Vagrant. If you
believe this is a bug in Vagrant, please search existing issues before
opening a new one.
no_matching_port: |-
The guest is not currently mapping port %{port} to the host machine. Is
the port configured in the Vagrantfile? You may need to run `vagrant reload`
if changes were made to the port configuration in the Vagrantfile.

View File

@ -0,0 +1,27 @@
require "vagrant"
module VagrantPlugins
module CommandPort
class Plugin < Vagrant.plugin("2")
name "port command"
description <<-DESC
The `port` command displays guest port mappings.
DESC
command("port") do
require_relative "command"
self.init!
Command
end
protected
def self.init!
return if defined?(@_init)
I18n.load_path << File.expand_path("../locales/en.yml", __FILE__)
I18n.reload!
@_init = true
end
end
end
end

View File

@ -9,6 +9,8 @@ module VagrantPlugins
#
# @return [Hash<Integer, Integer>] Host => Guest port mappings.
def self.forwarded_ports(machine)
return nil if machine.state.id != :running
{}.tap do |result|
machine.provider.driver.read_forwarded_ports.each do |_, _, h, g|
result[h] = g

View File

@ -0,0 +1,137 @@
require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/port/command")
describe VagrantPlugins::CommandPort::Command do
include_context "unit"
include_context "command plugin helpers"
let(:iso_env) { isolated_environment }
let(:env) do
iso_env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
end
VF
iso_env.create_vagrant_env
end
let(:state) { double(:state, id: :running) }
let(:machine) { env.machine(env.machine_names[0], :dummy) }
before(:all) do
I18n.load_path << Vagrant.source_root.join("plugins/commands/port/locales/en.yml")
I18n.reload!
end
subject { described_class.new([], env) }
before do
allow(machine).to receive(:state).and_return(state)
allow(subject).to receive(:with_target_vms) { |&block| block.call(machine) }
end
describe "#execute" do
it "validates the configuration" do
iso_env.vagrantfile <<-EOH
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
config.push.define "noop" do |push|
push.bad = "ham"
end
end
EOH
subject = described_class.new([], iso_env.create_vagrant_env)
expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|
expect(err.message).to include("The following settings shouldn't exist: bad")
}
end
it "ensures the vm is running" do
allow(state).to receive(:id).and_return(:stopped)
expect(env.ui).to receive(:error).with { |message, _|
expect(message).to include("does not support listing forwarded ports")
}
expect(subject.execute).to eq(1)
end
it "shows a friendly error when the capability is not supported" do
allow(machine.provider).to receive(:capability?).and_return(false)
expect(env.ui).to receive(:error).with { |message, _|
expect(message).to include("does not support listing forwarded ports")
}
expect(subject.execute).to eq(1)
end
it "returns a friendly message when there are no forwarded ports" do
allow(machine.provider).to receive(:capability?).and_return(true)
allow(machine.provider).to receive(:capability).with(:forwarded_ports)
.and_return([])
expect(env.ui).to receive(:info).with { |message, _|
expect(message).to include("there are no forwarded ports")
}
expect(subject.execute).to eq(0)
end
it "returns the list of ports" do
allow(machine.provider).to receive(:capability?).and_return(true)
allow(machine.provider).to receive(:capability).with(:forwarded_ports)
.and_return([[2222,22], [1111,11]])
output = ""
allow(env.ui).to receive(:info) do |data|
output << data
end
expect(subject.execute).to eq(0)
expect(output).to include("forwarded ports for the machine")
expect(output).to include("22 (guest) => 2222 (host)")
expect(output).to include("11 (guest) => 1111 (host)")
end
it "prints the matching host port when --guest is given" do
argv = ["--guest", "22"]
subject = described_class.new(argv, env)
allow(machine.provider).to receive(:capability?).and_return(true)
allow(machine.provider).to receive(:capability).with(:forwarded_ports)
.and_return([[2222,22]])
output = ""
allow(env.ui).to receive(:info) do |data|
output << data
end
expect(subject.execute).to eq(0)
expect(output).to eq("2222")
end
it "returns an error with no port is mapped to the --guest option" do
argv = ["--guest", "80"]
subject = described_class.new(argv, env)
allow(machine.provider).to receive(:capability?).and_return(true)
allow(machine.provider).to receive(:capability).with(:forwarded_ports)
.and_return([[2222,22]])
output = ""
allow(env.ui).to receive(:error) do |data|
output << data
end
expect(subject.execute).to_not eq(0)
expect(output).to include("not currently mapping port 80")
end
end
end

View File

@ -15,14 +15,16 @@ describe VagrantPlugins::ProviderVirtualBox::Cap do
let(:machine) do
iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|
m.provider.stub(driver: driver)
m.stub(state: state)
end
end
let(:driver) { double("driver") }
let(:state) { double("state", id: :running) }
describe "#forwarded_ports" do
it "returns all the forwarded ports" do
expect(driver).to receive(:read_forwarded_ports).and_return([
allow(driver).to receive(:read_forwarded_ports).and_return([
[nil, nil, 123, 456],
[nil, nil, 245, 245],
])
@ -32,5 +34,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap do
245 => 245,
})
end
it "returns nil when the machine is not running" do
allow(machine).to receive(:state).and_return(double(:state, id: :stopped))
expect(described_class.forwarded_ports(machine)).to be(nil)
end
end
end

View File

@ -104,6 +104,7 @@
<li<%= sidebar_current("cli-login") %>><a href="/v2/cli/login.html">login</a></li>
<li<%= sidebar_current("cli-package") %>><a href="/v2/cli/package.html">package</a></li>
<li<%= sidebar_current("cli-plugin") %>><a href="/v2/cli/plugin.html">plugin</a></li>
<li<%= sidebar_current("cli-port") %>><a href="/v2/cli/port.html">port</a></li>
<li<%= sidebar_current("cli-powershell") %>><a href="/v2/cli/powershell.html">powershell</a></li>
<li<%= sidebar_current("cli-provision") %>><a href="/v2/cli/provision.html">provision</a></li>
<li<%= sidebar_current("cli-rdp") %>><a href="/v2/cli/rdp.html">rdp</a></li>

View File

@ -0,0 +1,35 @@
---
page_title: "vagrant port - Command-Line Interface"
sidebar_current: "cli-port"
---
# Port
**Command: `vagrant port`**
The port command displays the full list of guest ports mapped to the host
machine ports:
```
$ vagrant port
22 (guest) => 2222 (host)
80 (guest) => 8080 (host)
```
In a multi-machine Vagrantfile, the name of the machine must be specified:
```
$ vagrant port my-machine
```
## Options
* `--guest PORT` - This displays just the host port that corresponds to the
given guest port. If the guest is not forwarding that port, an error is
returned. This is useful for quick scripting, for example:
$ ssh -p $(vagrant port --guest 22)
* `--machine-readable` - This tells Vagrant to display machine-readable output
instead of the human-friendly output. More information is available in the
[machine-readable output](/v2/cli/machine-readable.html) documentation.