From: Chris Roberts <croberts@hashicorp.com>
Date: Fri, 14 Oct 2022 10:44:49 -0700
Subject: Add support for VirtualBox 7.0

Signed-off-by: Antonio Terceiro <terceiro@debian.org>
Changes from the original patch:

- replace `require "rexml"` with `require "rexml/document"` to work with with
  the rexml shipped with Ruby 2.7

---
 lib/vagrant/errors.rb                              |   4 +
 plugins/providers/virtualbox/driver/meta.rb        |   1 +
 plugins/providers/virtualbox/driver/version_7_0.rb |  67 +++++++++++++
 plugins/providers/virtualbox/plugin.rb             |   1 +
 templates/locales/en.yml                           |   5 +
 .../virtualbox/driver/version_7_0_test.rb          | 109 +++++++++++++++++++++
 6 files changed, 187 insertions(+)
 create mode 100644 plugins/providers/virtualbox/driver/version_7_0.rb
 create mode 100644 test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb

diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb
index 782615b..4329d29 100644
--- a/lib/vagrant/errors.rb
+++ b/lib/vagrant/errors.rb
@@ -940,6 +940,10 @@ module Vagrant
       error_key(:virtualbox_broken_version_040214)
     end
 
+    class VirtualBoxConfigNotFound < VagrantError
+      error_key(:virtualbox_config_not_found)
+    end
+
     class VirtualBoxDisksDefinedExceedLimit < VagrantError
       error_key(:virtualbox_disks_defined_exceed_limit)
     end
diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb
index c3be8c8..04c130c 100644
--- a/plugins/providers/virtualbox/driver/meta.rb
+++ b/plugins/providers/virtualbox/driver/meta.rb
@@ -65,6 +65,7 @@ module VagrantPlugins
             "5.2" => Version_5_2,
             "6.0" => Version_6_0,
             "6.1" => Version_6_1,
+            "7.0" => Version_7_0,
           }
 
           if @@version.start_with?("4.2.14")
diff --git a/plugins/providers/virtualbox/driver/version_7_0.rb b/plugins/providers/virtualbox/driver/version_7_0.rb
new file mode 100644
index 0000000..d94e66b
--- /dev/null
+++ b/plugins/providers/virtualbox/driver/version_7_0.rb
@@ -0,0 +1,67 @@
+require "rexml/document"
+require File.expand_path("../version_6_1", __FILE__)
+
+module VagrantPlugins
+  module ProviderVirtualBox
+    module Driver
+      # Driver for VirtualBox 7.0.x
+      class Version_7_0 < Version_6_1
+        def initialize(uuid)
+          super
+
+          @logger = Log4r::Logger.new("vagrant::provider::virtualbox_7_0")
+        end
+
+        # The initial VirtualBox 7.0 release has an issue with displaying port
+        # forward information. When a single port forward is defined, the forwarding
+        # information can be found in the `showvminfo` output. Once more than a
+        # single port forward is defined, no forwarding information is provided
+        # in the `showvminfo` output. To work around this we grab the VM configuration
+        # file from the `showvminfo` output and extract the port forward information
+        # from there instead.
+        def read_forwarded_ports(uuid=nil, active_only=false)
+          @version ||= Meta.new.version
+
+          # Only use this override for the 7.0.0 release. If it is still broken
+          # on the 7.0.1 release we can modify the version check.
+          return super if @version != "7.0.0"
+
+          uuid ||= @uuid
+
+          @logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}")
+
+          results = []
+
+          info = execute("showvminfo", uuid, "--machinereadable", retryable: true)
+          result = info.match(/CfgFile="(?<path>.+?)"/)
+          if result.nil?
+            raise Vagrant::Errors::VirtualBoxConfigNotFound,
+                  uuid: uuid
+          end
+
+          File.open(result[:path], "r") do |f|
+            doc = REXML::Document.new(f)
+            networks = REXML::XPath.each(doc.root, "//Adapter")
+            networks.each do |net|
+              REXML::XPath.each(doc.root, net.xpath + "/NAT/Forwarding") do |fwd|
+                # Result Array values:
+                # [NIC Slot, Name, Host Port, Guest Port, Host IP]
+                result = [
+                  net.attribute("slot").value.to_i + 1,
+                  fwd.attribute("name")&.value.to_s,
+                  fwd.attribute("hostport")&.value.to_i,
+                  fwd.attribute("guestport")&.value.to_i,
+                  fwd.attribute("hostip")&.value.to_s
+                ]
+                @logger.debug(" - #{result.inspect}")
+                results << result
+              end
+            end
+          end
+
+          results
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb
index ad48bd0..dbc1492 100644
--- a/plugins/providers/virtualbox/plugin.rb
+++ b/plugins/providers/virtualbox/plugin.rb
@@ -95,6 +95,7 @@ module VagrantPlugins
       autoload :Version_5_2, File.expand_path("../driver/version_5_2", __FILE__)
       autoload :Version_6_0, File.expand_path("../driver/version_6_0", __FILE__)
       autoload :Version_6_1, File.expand_path("../driver/version_6_1", __FILE__)
+      autoload :Version_7_0, File.expand_path("../driver/version_7_0", __FILE__)
     end
 
     module Model
diff --git a/templates/locales/en.yml b/templates/locales/en.yml
index 782904f..c75c802 100644
--- a/templates/locales/en.yml
+++ b/templates/locales/en.yml
@@ -1693,6 +1693,11 @@ en:
         4.2.14 contains a critical bug which prevents it from working with
         Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade
         VirtualBox.
+      virtualbox_config_not_found: |-
+        Vagrant was unable to locate the configuration file for the requested
+        VirtualBox VM. Verify the requested VM exists and try again.
+
+          UUID provided: %{uuid}
       virtualbox_disks_controller_not_found: |-
         Vagrant expected to find a storage controller called '%{name}',
         but there is no controller with this name attached to the current VM.
diff --git a/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb
new file mode 100644
index 0000000..58c9e6d
--- /dev/null
+++ b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb
@@ -0,0 +1,109 @@
+require "stringio"
+require_relative "../base"
+
+describe VagrantPlugins::ProviderVirtualBox::Driver::Version_7_0 do
+  include_context "virtualbox"
+
+  let(:vbox_version) { "7.0.0" }
+
+  subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_7_0.new(uuid) }
+
+  it_behaves_like "a version 5.x virtualbox driver"
+  it_behaves_like "a version 6.x virtualbox driver"
+
+  describe "#read_forwarded_ports" do
+    let(:uuid) { "MACHINE-UUID" }
+    let(:cfg_path) { "MACHINE_CONFIG_PATH" }
+    let(:vm_info) {
+%(name="vagrant-test_default_1665781960041_56631"
+Encryption:     disabled
+groups="/"
+ostype="Ubuntu (64-bit)"
+UUID="#{uuid}"
+CfgFile="#{cfg_path}"
+SnapFldr="/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Snapshots"
+LogFldr="/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Logs"
+memory=1024)
+    }
+    let(:config_file) {
+      StringIO.new(
+%(<?xml version="1.0"?>
+<VirtualBox xmlns="http://www.virtualbox.org/" version="1.19-linux">
+  <Machine uuid="{623842dc-0947-4143-aa4e-7d180c5eb348}" name="vagrant-test_default_1665781960041_56631" OSType="Ubuntu_64" snapshotFolder="Snapshots">
+    <Hardware>
+      <Network>
+        <Adapter slot="0" enabled="true" MACAddress="080027BB1475" type="82540EM">
+          <NAT localhost-reachable="true">
+            <DNS use-proxy="true"/>
+            <Forwarding name="ssh" proto="1" hostip="127.0.0.1" hostport="2222" guestport="22"/>
+            <Forwarding name="tcp7700" proto="1" hostport="7700" guestport="80"/>
+          </NAT>
+        </Adapter>
+        <Adapter slot="1" enabled="true" MACAddress="080027DD5ADF" type="82540EM">
+          <DisabledModes>
+            <InternalNetwork name="intnet"/>
+            <NATNetwork name="NatNetwork"/>
+          </DisabledModes>
+          <HostOnlyInterface name="vboxnet0"/>
+        </Adapter>
+      </Network>
+    </Hardware>
+  </Machine>
+</VirtualBox>)
+      )
+    }
+
+    before do
+      allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).to receive(:version).and_return(vbox_version)
+    end
+
+    describe "VirtualBox version 7.0.0" do
+      let(:vbox_version) { "7.0.0" }
+
+      before do
+        allow(subject).to receive(:execute).with("showvminfo", uuid, any_args).and_return(vm_info)
+        allow(File).to receive(:open).with(cfg_path, "r").and_yield(config_file)
+      end
+
+      it "should return two port forward values" do
+        expect(subject.read_forwarded_ports.size).to eq(2)
+      end
+
+      it "should have port forwards on slot one" do
+        subject.read_forwarded_ports.each do |fwd|
+          expect(fwd.first).to eq(1)
+        end
+      end
+
+      it "should include host ip for ssh forward" do
+        fwd = subject.read_forwarded_ports.detect { |f| f[1] == "ssh" }
+        expect(fwd).not_to be_nil
+        expect(fwd.last).to eq("127.0.0.1")
+      end
+
+      describe "when config file cannot be determine" do
+        let(:vm_info) { %(name="vagrant-test_default_1665781960041_56631") }
+
+        it "should raise a custom error" do
+          expect(File).not_to receive(:open).with(cfg_path, "r")
+
+          expect { subject.read_forwarded_ports }.to raise_error(Vagrant::Errors::VirtualBoxConfigNotFound)
+        end
+      end
+    end
+
+    describe "VirtualBox version greater than 7.0.0" do
+      let(:vbox_version) { "7.0.1" }
+
+      before do
+        allow(subject).to receive(:execute).with("showvminfo", uuid, any_args).and_return(vm_info)
+      end
+
+      it "should not read configuration file" do
+        expect(File).not_to receive(:open).with(cfg_path, "r")
+        subject.read_forwarded_ports
+      end
+    end
+
+  end
+end
