From 063f60e593b6c13db3cdeb0c193586432e7dfc13 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 11:08:30 -0500 Subject: [PATCH 1/9] Add `vagrant port` command --- plugins/commands/port/command.rb | 71 ++++++++++++++ plugins/commands/port/plugin.rb | 17 ++++ .../plugins/commands/port/command_test.rb | 98 +++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 plugins/commands/port/command.rb create mode 100644 plugins/commands/port/plugin.rb create mode 100644 test/unit/plugins/commands/port/command_test.rb diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb new file mode 100644 index 000000000..276b6baba --- /dev/null +++ b/plugins/commands/port/command.rb @@ -0,0 +1,71 @@ +require "optparse" + +module VagrantPlugins + module CommandPort + class Command < Vagrant.plugin("2", :command) + def self.synopsis + "displays information about guest port mappings" + end + + # @todo support multiple strategies if requested by the community + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant port [options] [name]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("--machine-readable", "Display machine-readable output") + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + @logger.debug("Port command: #{argv.inspect}") + with_target_vms(argv, single_target: true) do |vm| + vm.action_raw(:config_validate, + Vagrant::Action::Builtin::ConfigValidate) + + if vm.state.id != :running + @env.ui.error "not running - make this a better error or use the middleware" + return 1 + end + + # This only works for vbox? should it be everywhere? + # vm.action_raw(:check_running, + # Vagrant::Action::Builtin::CheckRunning) + + if !vm.provider.capability?(:forwarded_ports) + @env.ui.error <<-EOH.strip +The #{vm.provider_name} 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. +EOH + return 1 + end + + ports = vm.provider.capability(:forwarded_ports) + + if ports.empty? + @env.ui.info("The machine has no configured forwarded ports") + return 0 + end + + @env.ui.info <<-EOH +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. +EOH + ports.each do |guest, host| + @env.ui.info("#{guest.to_s.rjust(6)} (guest) => #{host} (host)") + @env.ui.machine("forwarded_port", guest, host, target: vm.name.to_s) + end + end + + 0 + end + end + end +end diff --git a/plugins/commands/port/plugin.rb b/plugins/commands/port/plugin.rb new file mode 100644 index 000000000..be1572afd --- /dev/null +++ b/plugins/commands/port/plugin.rb @@ -0,0 +1,17 @@ +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 File.expand_path("../command", __FILE__) + Command + end + end + end +end diff --git a/test/unit/plugins/commands/port/command_test.rb b/test/unit/plugins/commands/port/command_test.rb new file mode 100644 index 000000000..7bd3dee4e --- /dev/null +++ b/test/unit/plugins/commands/port/command_test.rb @@ -0,0 +1,98 @@ +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(:argv) { [] } + let(:pushes) { {} } + let(:state) { double(:state, id: :running) } + + let(:machine) { env.machine(env.machine_names[0], :dummy) } + + subject { described_class.new(argv, 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(argv, 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("make this a better error") + } + + 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("has no configured 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("2222 (guest) => 22 (host)") + expect(output).to include("1111 (guest) => 11 (host)") + end + end +end From 958ce8983b1a64dcd067be284dc0ccf5429e2a09 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 14:22:59 -0500 Subject: [PATCH 2/9] Remove todo comment --- plugins/commands/port/command.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb index 276b6baba..2ffca44d9 100644 --- a/plugins/commands/port/command.rb +++ b/plugins/commands/port/command.rb @@ -7,7 +7,6 @@ module VagrantPlugins "displays information about guest port mappings" end - # @todo support multiple strategies if requested by the community def execute opts = OptionParser.new do |o| o.banner = "Usage: vagrant port [options] [name]" From beb84d32127ff0ecb6a393778ba543f6c65ba144 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 15:48:07 -0500 Subject: [PATCH 3/9] Move to I18n --- plugins/commands/port/command.rb | 18 ++++++------------ plugins/commands/port/locales/en.yml | 16 ++++++++++++++++ plugins/commands/port/plugin.rb | 12 +++++++++++- .../unit/plugins/commands/port/command_test.rb | 9 +++++++-- 4 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 plugins/commands/port/locales/en.yml diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb index 2ffca44d9..a62789f6b 100644 --- a/plugins/commands/port/command.rb +++ b/plugins/commands/port/command.rb @@ -36,27 +36,21 @@ module VagrantPlugins # Vagrant::Action::Builtin::CheckRunning) if !vm.provider.capability?(:forwarded_ports) - @env.ui.error <<-EOH.strip -The #{vm.provider_name} 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. -EOH + @env.ui.error(I18n.t("port_command.missing_capability", + provider: vm.provider_name, + )) return 1 end ports = vm.provider.capability(:forwarded_ports) if ports.empty? - @env.ui.info("The machine has no configured forwarded ports") + @env.ui.info(I18n.t("port_command.empty_ports")) return 0 end - @env.ui.info <<-EOH -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. -EOH + @env.ui.info(I18n.t("port_command.details")) + @env.ui.info("") ports.each do |guest, host| @env.ui.info("#{guest.to_s.rjust(6)} (guest) => #{host} (host)") @env.ui.machine("forwarded_port", guest, host, target: vm.name.to_s) diff --git a/plugins/commands/port/locales/en.yml b/plugins/commands/port/locales/en.yml new file mode 100644 index 000000000..8bcfdf1c7 --- /dev/null +++ b/plugins/commands/port/locales/en.yml @@ -0,0 +1,16 @@ +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. diff --git a/plugins/commands/port/plugin.rb b/plugins/commands/port/plugin.rb index be1572afd..7437f0436 100644 --- a/plugins/commands/port/plugin.rb +++ b/plugins/commands/port/plugin.rb @@ -9,9 +9,19 @@ module VagrantPlugins DESC command("port") do - require File.expand_path("../command", __FILE__) + 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 diff --git a/test/unit/plugins/commands/port/command_test.rb b/test/unit/plugins/commands/port/command_test.rb index 7bd3dee4e..5ec4027de 100644 --- a/test/unit/plugins/commands/port/command_test.rb +++ b/test/unit/plugins/commands/port/command_test.rb @@ -22,6 +22,11 @@ describe VagrantPlugins::CommandPort::Command do 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(argv, env) } before do @@ -51,7 +56,7 @@ describe VagrantPlugins::CommandPort::Command do 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("make this a better error") + expect(message).to include("does not support listing forwarded ports") } expect(subject.execute).to eq(1) @@ -72,7 +77,7 @@ describe VagrantPlugins::CommandPort::Command do .and_return([]) expect(env.ui).to receive(:info).with { |message, _| - expect(message).to include("has no configured forwarded ports") + expect(message).to include("there are no forwarded ports") } expect(subject.execute).to eq(0) From f20c08f57f94c138403a265e3c03c3748e854f2f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 15:48:17 -0500 Subject: [PATCH 4/9] Return nil if the VM is not running when looking at forwarded ports --- plugins/providers/virtualbox/cap.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/providers/virtualbox/cap.rb b/plugins/providers/virtualbox/cap.rb index 77f8ee1ad..1c0512407 100644 --- a/plugins/providers/virtualbox/cap.rb +++ b/plugins/providers/virtualbox/cap.rb @@ -9,6 +9,8 @@ module VagrantPlugins # # @return [Hash] 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 From 4cebe283e8915e22f3be3f3aa30d0afa64c7c746 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 15:48:29 -0500 Subject: [PATCH 5/9] Remove debug/todo comments --- plugins/commands/port/command.rb | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb index a62789f6b..76dfd6e17 100644 --- a/plugins/commands/port/command.rb +++ b/plugins/commands/port/command.rb @@ -1,8 +1,12 @@ +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 @@ -14,6 +18,9 @@ module VagrantPlugins o.separator "Options:" o.separator "" + o.on("--guest", "Output the host port that maps to the given guest port") do + end + o.on("--machine-readable", "Display machine-readable output") end @@ -21,20 +28,10 @@ module VagrantPlugins argv = parse_options(opts) return if !argv - @logger.debug("Port command: #{argv.inspect}") with_target_vms(argv, single_target: true) do |vm| vm.action_raw(:config_validate, Vagrant::Action::Builtin::ConfigValidate) - if vm.state.id != :running - @env.ui.error "not running - make this a better error or use the middleware" - return 1 - end - - # This only works for vbox? should it be everywhere? - # vm.action_raw(:check_running, - # Vagrant::Action::Builtin::CheckRunning) - if !vm.provider.capability?(:forwarded_ports) @env.ui.error(I18n.t("port_command.missing_capability", provider: vm.provider_name, @@ -44,7 +41,7 @@ module VagrantPlugins ports = vm.provider.capability(:forwarded_ports) - if ports.empty? + if !present?(ports) @env.ui.info(I18n.t("port_command.empty_ports")) return 0 end @@ -57,7 +54,7 @@ module VagrantPlugins end end - 0 + return 0 end end end From 050f8d4d7123a87df09ed9e3c9e858a894ee95ae Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 16:32:58 -0500 Subject: [PATCH 6/9] Allow specifying the guest port search --- plugins/commands/port/command.rb | 41 +++++++++++-- plugins/commands/port/locales/en.yml | 4 ++ .../plugins/commands/port/command_test.rb | 60 ++++++++++++++++--- 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/plugins/commands/port/command.rb b/plugins/commands/port/command.rb index 76dfd6e17..af35def4b 100644 --- a/plugins/commands/port/command.rb +++ b/plugins/commands/port/command.rb @@ -12,13 +12,16 @@ module VagrantPlugins 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", "Output the host port that maps to the given guest port") do + 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") @@ -46,14 +49,40 @@ module VagrantPlugins return 0 end - @env.ui.info(I18n.t("port_command.details")) - @env.ui.info("") - ports.each do |guest, host| - @env.ui.info("#{guest.to_s.rjust(6)} (guest) => #{host} (host)") - @env.ui.machine("forwarded_port", guest, host, target: vm.name.to_s) + 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 diff --git a/plugins/commands/port/locales/en.yml b/plugins/commands/port/locales/en.yml index 8bcfdf1c7..ba1389db7 100644 --- a/plugins/commands/port/locales/en.yml +++ b/plugins/commands/port/locales/en.yml @@ -14,3 +14,7 @@ en: 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. diff --git a/test/unit/plugins/commands/port/command_test.rb b/test/unit/plugins/commands/port/command_test.rb index 5ec4027de..ac3da72a0 100644 --- a/test/unit/plugins/commands/port/command_test.rb +++ b/test/unit/plugins/commands/port/command_test.rb @@ -16,9 +16,7 @@ describe VagrantPlugins::CommandPort::Command do iso_env.create_vagrant_env end - let(:argv) { [] } - let(:pushes) { {} } - let(:state) { double(:state, id: :running) } + let(:state) { double(:state, id: :running) } let(:machine) { env.machine(env.machine_names[0], :dummy) } @@ -27,7 +25,7 @@ describe VagrantPlugins::CommandPort::Command do I18n.reload! end - subject { described_class.new(argv, env) } + subject { described_class.new([], env) } before do allow(machine).to receive(:state).and_return(state) @@ -46,7 +44,7 @@ describe VagrantPlugins::CommandPort::Command do end EOH - subject = described_class.new(argv, iso_env.create_vagrant_env) + 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") @@ -96,8 +94,56 @@ describe VagrantPlugins::CommandPort::Command do expect(subject.execute).to eq(0) expect(output).to include("forwarded ports for the machine") - expect(output).to include("2222 (guest) => 22 (host)") - expect(output).to include("1111 (guest) => 11 (host)") + 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 + + + + + + + + + + + + From b4371725af7423a855424ee9c153f4331a52748b Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 16:33:05 -0500 Subject: [PATCH 7/9] Add documentation for port command --- website/docs/source/layouts/layout.erb | 1 + website/docs/source/v2/cli/port.html.md | 35 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 website/docs/source/v2/cli/port.html.md diff --git a/website/docs/source/layouts/layout.erb b/website/docs/source/layouts/layout.erb index e9831b329..db40d692b 100644 --- a/website/docs/source/layouts/layout.erb +++ b/website/docs/source/layouts/layout.erb @@ -104,6 +104,7 @@ >login >package >plugin + >port >powershell >provision >rdp diff --git a/website/docs/source/v2/cli/port.html.md b/website/docs/source/v2/cli/port.html.md new file mode 100644 index 000000000..97f815de8 --- /dev/null +++ b/website/docs/source/v2/cli/port.html.md @@ -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. From 3502042d96cfab0b7a0b9e69f6e2c038fe7b125f Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 16:36:36 -0500 Subject: [PATCH 8/9] Lol whitespace [ci skip] --- test/unit/plugins/commands/port/command_test.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/unit/plugins/commands/port/command_test.rb b/test/unit/plugins/commands/port/command_test.rb index ac3da72a0..23abf5d7b 100644 --- a/test/unit/plugins/commands/port/command_test.rb +++ b/test/unit/plugins/commands/port/command_test.rb @@ -135,15 +135,3 @@ describe VagrantPlugins::CommandPort::Command do end end end - - - - - - - - - - - - From 1bb9a48ae7de267aefe6532ca51f3aab01420db3 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 24 Nov 2015 16:41:13 -0500 Subject: [PATCH 9/9] Fix cap test --- test/unit/plugins/providers/virtualbox/cap_test.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/unit/plugins/providers/virtualbox/cap_test.rb b/test/unit/plugins/providers/virtualbox/cap_test.rb index baa7f610f..52eb9bc43 100644 --- a/test/unit/plugins/providers/virtualbox/cap_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap_test.rb @@ -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