Repository: hashicorp/vagrant (Ruby)

Issue:
ansible_local provisioner unable to install specific version of Ansible with pip install_mode
The `ansible_local` provisioner is able to install a specific version of Ansible using the `version` attribute. You can combine this with the `install_mode` attribute, specifically using `pip` to install and manage Ansible versions.

Ansible switched their versioning logic to rather display the version of `ansible-core` instead of the actual `ansible` package (i.e. when running `ansible --version` you get `[core 2.x.x.x]` instead of the version of the `ansible` `pip` package, which is higher than than the `ansible-core` package). This leads to the issue of Ansible getting installed correctly when specifying the `version` but the version check itself failing because it detects the version of `ansible-core` instead.

Unfortunately, you also cannot use the version of `ansible-core` because the installation command line that's executed by Vagrant is `pip install --upgrade ansible==<version>`, meaning it's installing the `ansible` package, not `ansible-core`, so using the `ansible-core` version fails since that's not a compatible semantic version of the package `ansible`.

A way out of this would be to use `importlib.metadata` for checking on the Ansible version if `install_mode: 'pip'` is specified:

```console
$ python -c "import importlib.metadata; print(importlib.metadata.version('ansible'))"
7.0.0
```

or `pip` itself:

```console
$ pip show ansible | awk '/Version/ { print $2 }'
```

Either way, there needs to be a remedy for the mismatch in behavior between Vagrant trying to install the `ansible` metapackage and then checking against a version of `ansible-core`.

### Expected behavior

The package `ansible` with the requested version should have been installed using `pip`.

### Actual behavior

The package `ansible` was installed with the requested version but the version check fails because it's looking at `ansible --version` and finds the version string `[core 2.x.x.x]` for `ansible-core`.

### Reproduction information 

#### Vagrant version

2.3.3

#### Host operating system

Ubuntu Kinetic Kudu (22.10)

#### Guest operating system

- Ubuntu Jammy Jellyfish (22.04)
- Ubuntu Focal Fossa (20.04)
- Rocky Linux 8
- Rocky Linux 9

#### Steps to reproduce

Simply run `vagrant up` on the example. The `playbook.yml` just needs to be an empty file in the same directory as the `Vagrantfile`.

#### Vagrantfile

```ruby
Vagrant.configure('2') do |config|
  config.vm.define 'jammy' do |bare|
    bare.vm.box = 'ubuntu/jammy64'
    bare.vm.network 'private_network', type: 'dhcp'

    bare.vm.provision 'ansible_local' do |ansible|
      ansible.version = '7.0.0'
      ansible.playbook = 'playbook.yml'
      ansible.install_mode = 'pip'
    end
  end
end
```

Output from a Vagrant run:

```console
Bringing machine 'jammy' up with 'virtualbox' provider...
==> jammy: Checking if box 'ubuntu/jammy64' version '20221124.0.0' is up to date...
==> jammy: Running provisioner: ansible_local...
    jammy: Installing Ansible...
    jammy: Installing pip... (for Ansible installation)
The requested Ansible version (7.0.0) was not found on the guest.
Please check the Ansible installation on your Vagrant guest system (currently: [core 2.14.0]),
or adapt the provisioner `version` option in your Vagrantfile.
See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version
for more information.
```



Discussion: Instead of using importlib, you can also call `ansible-community --version` to get the version of the "community distribution"



Fix (patch):
diff --git a/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb b/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb
index 329eab8a917..ab171ee5daa 100644
--- a/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb
+++ b/plugins/provisioners/ansible/cap/guest/posix/ansible_installed.rb
@@ -10,8 +10,8 @@ module AnsibleInstalled
             def self.ansible_installed(machine, version)
               command = 'test -x "$(command -v ansible)"'
 
-              if !version.empty?
-                command << "&& ansible --version | grep 'ansible #{version}'"
+              unless version.empty?
+                command << "&& [[ $(python3 -c \"import importlib.metadata; print(importlib.metadata.version('ansible'))\") == \"#{version}\" ]]"
               end
 
               machine.communicate.test command, sudo: false
diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb
index 1474b837b82..2e800a999e9 100644
--- a/plugins/provisioners/ansible/provisioner/guest.rb
+++ b/plugins/provisioners/ansible/provisioner/guest.rb
@@ -75,7 +75,7 @@ def gather_ansible_version
           raw_output = ""
 
           result = @machine.communicate.execute(
-            "ansible --version",
+            "python3 -c \"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))\"",
             error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
             error_key: :ansible_not_found_on_guest) do |type, output|
             if type == :stdout && output.lines[0]
diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb
index f2a57133c79..ef700f03a3b 100644
--- a/plugins/provisioners/ansible/provisioner/host.rb
+++ b/plugins/provisioners/ansible/provisioner/host.rb
@@ -108,8 +108,9 @@ def execute_command_from_host(command)
         end
 
         def gather_ansible_version
-          raw_output = ""
-          command = %w(ansible --version)
+          raw_output = ''
+          command = ['python3', '-c',
+                     "\"import importlib.metadata; print('ansible ' + importlib.metadata.version('ansible'))\""]
 
           command << {
             notify: [:stdout, :stderr]