vagrant-qemu 0.3.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/CLAUDE.md +45 -0
- data/README.md +216 -17
- data/Rakefile +24 -7
- data/lib/vagrant-qemu/action/cloud_init_network.rb +123 -0
- data/lib/vagrant-qemu/action/destroy.rb +7 -1
- data/lib/vagrant-qemu/action/import.rb +13 -6
- data/lib/vagrant-qemu/action/read_state.rb +2 -2
- data/lib/vagrant-qemu/action/start_instance.rb +44 -26
- data/lib/vagrant-qemu/action/stop_instance.rb +2 -1
- data/lib/vagrant-qemu/action/warn_networks.rb +21 -1
- data/lib/vagrant-qemu/action.rb +5 -0
- data/lib/vagrant-qemu/cap/disk.rb +104 -0
- data/lib/vagrant-qemu/cap.rb +8 -0
- data/lib/vagrant-qemu/config.rb +84 -5
- data/lib/vagrant-qemu/driver.rb +181 -35
- data/lib/vagrant-qemu/errors.rb +8 -0
- data/lib/vagrant-qemu/network/base.rb +21 -0
- data/lib/vagrant-qemu/network/socket.rb +28 -0
- data/lib/vagrant-qemu/network/tap.rb +20 -0
- data/lib/vagrant-qemu/network/vmnet.rb +53 -0
- data/lib/vagrant-qemu/network.rb +99 -0
- data/lib/vagrant-qemu/plugin.rb +25 -0
- data/lib/vagrant-qemu/provider.rb +1 -1
- data/lib/vagrant-qemu/version.rb +1 -1
- data/lib/vagrant-qemu.rb +2 -0
- data/locales/en.yml +24 -0
- data/spec/acceptance/basic_up_spec.rb +83 -0
- data/spec/acceptance/halt_destroy_spec.rb +69 -0
- data/spec/acceptance/helper.rb +69 -0
- data/spec/acceptance/network_spec.rb +134 -0
- data/spec/acceptance/port_collision_spec.rb +53 -0
- data/spec/e2e/advanced_network_spec.rb +117 -0
- data/spec/e2e/disk_spec.rb +35 -0
- data/spec/e2e/forwarded_port_spec.rb +107 -0
- data/spec/e2e/helper.rb +137 -0
- data/spec/e2e/provision_spec.rb +35 -0
- data/spec/e2e/reload_spec.rb +58 -0
- data/spec/e2e/smoke_spec.rb +60 -0
- data/spec/e2e/socket_network_spec.rb +178 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/unit/action/cloud_init_network_spec.rb +162 -0
- data/spec/unit/action/destroy_spec.rb +46 -0
- data/spec/unit/action/import_spec.rb +52 -0
- data/spec/unit/action/prepare_forwarded_port_collision_params_spec.rb +72 -0
- data/spec/unit/action/read_state_spec.rb +50 -0
- data/spec/unit/action/start_instance_port_spec.rb +77 -0
- data/spec/unit/action/start_instance_spec.rb +64 -0
- data/spec/unit/action/warn_networks_spec.rb +38 -0
- data/spec/unit/config_spec.rb +143 -0
- data/spec/unit/driver/delete_spec.rb +39 -0
- data/spec/unit/driver/options_yaml_spec.rb +49 -0
- data/spec/unit/driver/ssh_port_spec.rb +46 -0
- data/spec/unit/driver/start_cmd_advanced_spec.rb +109 -0
- data/spec/unit/driver/start_cmd_order_spec.rb +67 -0
- data/spec/unit/driver/start_cmd_spec.rb +161 -0
- data/spec/unit/driver/start_edge_cases_spec.rb +72 -0
- data/spec/unit/driver/state_spec.rb +60 -0
- data/spec/unit/driver/stop_spec.rb +94 -0
- data/spec/unit/network/backend_for_spec.rb +27 -0
- data/spec/unit/network/build_network_config_spec.rb +40 -0
- data/spec/unit/network/generate_mac_spec.rb +26 -0
- data/spec/unit/network/socket_spec.rb +44 -0
- data/spec/unit/network/tap_spec.rb +15 -0
- data/spec/unit/network/vmnet_spec.rb +58 -0
- metadata +49 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35de3b54145ee419e9cada4bd6b879fa0e9f7faef93e27da8e750346dcd6c0b7
|
|
4
|
+
data.tar.gz: 8e0069a3cce24d59ab4723108bd54199cb6019a8a47c587601795bd6a9cc91a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de578f059ba6ee08339738a906e366f8b8f33acc7b6389b05d68af4d365ceb6829e6d2ac7cf84214d7ff5f7af8510d657228ebd08fa0f444d40a962e23001cae
|
|
7
|
+
data.tar.gz: 5f74816cd5c18bf5ad492e546fea36d972b32585ebb0f360f1bd6cf2bbe70e475ea69365f6ff228addb5d8179feaa7944d74bfd80f4982dbd80eb7d2c7605a1b
|
data/CHANGELOG.md
CHANGED
|
@@ -93,3 +93,42 @@
|
|
|
93
93
|
# 0.3.11 (2025-05-06)
|
|
94
94
|
|
|
95
95
|
* Re-publish with repacked gem
|
|
96
|
+
|
|
97
|
+
# 0.3.12 (2025-05-19)
|
|
98
|
+
|
|
99
|
+
* Add support for extra `-drive` arguments
|
|
100
|
+
* Add `extra_image_opts` to customize image creation
|
|
101
|
+
* Add support for cloud-init and disks
|
|
102
|
+
* Add support for resizing disk on vm setup
|
|
103
|
+
|
|
104
|
+
# 0.4.0 (2026-06-12)
|
|
105
|
+
|
|
106
|
+
* Advanced networking (opt-in, `advanced_network = true`): dual-NIC `private_network`
|
|
107
|
+
support via vmnet (macOS), TAP (Linux), or the QEMU `socket` netdev, with
|
|
108
|
+
deterministic MAC addresses; static IP delivered through a plugin-built cloud-init
|
|
109
|
+
NoCloud seed ISO (MAC-matched, requires cloud-init in the guest). When
|
|
110
|
+
`config.vm.cloud_init` is also set, its user-data and the generated
|
|
111
|
+
network-config are merged into a single seed
|
|
112
|
+
* `net_mode = :socket` is now a thin wrapper around QEMU's `socket` netdev: the new
|
|
113
|
+
`socket_opts` option is emitted verbatim, so you pick the mode yourself —
|
|
114
|
+
`"mcast=230.0.0.1:1234"` (multicast, N-way) or `"listen=:1234"` / `"connect=host:1234"`
|
|
115
|
+
(point-to-point, no root, works on macOS where multicast does not — QEMU binds the
|
|
116
|
+
socket to the multicast group address, which Darwin rejects for sending). For
|
|
117
|
+
listen/connect you decide which VM listens and which connects (it is a 1:1 link,
|
|
118
|
+
not a hub). `mcast_addr` remains as a shortcut for the multicast address
|
|
119
|
+
* Fix SSH port not updated after forwarded-port collision auto-correction
|
|
120
|
+
* Persist only needed runtime state in options.yml; harden YAML loading
|
|
121
|
+
(`safe_load`); `vagrant halt` reads back the persisted control_port
|
|
122
|
+
* Graceful shutdown on halt with configurable `graceful_timeout` (default 60s),
|
|
123
|
+
force kill as fallback
|
|
124
|
+
* Host-aware defaults for `arch`/`machine`/`cpu`/`net_device`/`qemu_dir`: detect
|
|
125
|
+
the host arch (Apple Silicon vs Intel) and OS, default to native acceleration
|
|
126
|
+
(`hvf`/`kvm`/`whpx`) with `cpu=host` when guest arch matches the host, and to
|
|
127
|
+
`accel=tcg`/`cpu=max` when emulating; resolve `qemu_dir` from
|
|
128
|
+
`QEMU_DIR`/`HOMEBREW_PREFIX`/per-host path; skip the `qemu_dir` check for
|
|
129
|
+
x86_64 (SeaBIOS) so Intel Macs no longer fail with "Invalid qemu dir" (#59, #50)
|
|
130
|
+
* Validate the QEMU binary exists before starting the VM
|
|
131
|
+
* Destroy failures now surface the underlying error and preserve the machine ID
|
|
132
|
+
* Warn when private_network is configured without `advanced_network`, when the
|
|
133
|
+
network backend needs sudo, and when other unsupported network types are used
|
|
134
|
+
* Add test suite: unit + acceptance + e2e (`rake spec:unit|acceptance|e2e`)
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
vagrant-qemu is a Vagrant provider plugin that manages virtual machines using QEMU. It is primarily designed for Apple Silicon (aarch64) but also supports x86_64. The plugin is distributed as a Ruby gem.
|
|
8
|
+
|
|
9
|
+
## Build & Development Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies (first time setup)
|
|
13
|
+
bundle config set --local path 'vendor/bundle'
|
|
14
|
+
bundle install
|
|
15
|
+
|
|
16
|
+
# Build the gem (outputs to pkg/)
|
|
17
|
+
bundle exec rake build
|
|
18
|
+
|
|
19
|
+
# Install locally in Vagrant
|
|
20
|
+
vagrant plugin install ./pkg/vagrant-qemu-<version>.gem
|
|
21
|
+
|
|
22
|
+
# Verify installation
|
|
23
|
+
vagrant plugin list | grep vagrant-qemu
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Tests (rspec) are defined in the Gemfile but currently commented out in the Rakefile. There is no active test suite.
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
This is a standard Vagrant provider plugin following the Vagrant plugin v2 API. All source lives under `lib/vagrant-qemu/`.
|
|
31
|
+
|
|
32
|
+
**Key components:**
|
|
33
|
+
|
|
34
|
+
- **`plugin.rb`** — Registers the provider with Vagrant (name `:qemu`, box format `libvirt`), declares config and provider capabilities (disk management). Sets up i18n and logging.
|
|
35
|
+
- **`config.rb`** — All provider-specific config options (`qe.memory`, `qe.arch`, `qe.cpu`, etc.). Defaults target Apple Silicon (aarch64, HVF acceleration, cortex-a72 CPU). Options set to `nil` cause the corresponding QEMU arguments to be skipped entirely.
|
|
36
|
+
- **`provider.rb`** — Thin adapter between Vagrant and the Driver. Delegates actions to `Action.action_<name>`, exposes SSH info and machine state. NFS is explicitly disabled.
|
|
37
|
+
- **`driver.rb`** — Core QEMU interaction: builds the `qemu-system-*` command line, manages VM lifecycle via PID files and control sockets (unix socket or TCP port). Handles import (creates qcow2 overlay images via `qemu-img create`), start, stop (sends `system_powerdown` over control socket), and destroy.
|
|
38
|
+
- **`action.rb`** — Defines Vagrant action chains (up, halt, destroy, provision, reload, ssh) using `Vagrant::Action::Builder`. The `action_up` chain: HandleBox → ConfigValidate → Import (if not created) → CloudInit → Disk → Provision → Networking → StartInstance → WaitForCommunicator.
|
|
39
|
+
- **`cap/disk.rb`** — Provider capability for Vagrant's disk management (qcow2 and iso formats). Creates additional disks via `qemu-img create` and attaches them to the driver.
|
|
40
|
+
|
|
41
|
+
**VM state management:** State is determined by checking if the data directory exists (created?) and if the PID file references a running process (running?). VM data lives in `<data_dir>/<vm_id>/`, temp files in `~/.vagrant.d/tmp/vagrant-qemu/<vm_id>/`.
|
|
42
|
+
|
|
43
|
+
**Versioning:** Single source of truth in `lib/vagrant-qemu/version.rb`.
|
|
44
|
+
|
|
45
|
+
**I18n:** Locale strings in `locales/en.yml`.
|
data/README.md
CHANGED
|
@@ -37,6 +37,9 @@ Others:
|
|
|
37
37
|
* Synced folder support via SMB
|
|
38
38
|
* Basic operation: up, ssh, halt, destroy
|
|
39
39
|
* Basic suport to forwarded ports, see [vagrant doc](https://www.vagrantup.com/docs/networking/forwarded_ports) for details
|
|
40
|
+
* Support Cloud-init, see [vagrant doc](https://developer.hashicorp.com/vagrant/docs/cloud-init/usage) for details
|
|
41
|
+
* Support Disks, see [vagrant doc](https://developer.hashicorp.com/vagrant/docs/disks/usage) for details
|
|
42
|
+
* Advanced networking (opt-in): dual-NIC with `private_network` support via QEMU native vmnet (macOS), TAP (Linux), or a `socket` netdev — multicast (Linux/Windows) or point-to-point listen/connect (no root, works on macOS)
|
|
40
43
|
|
|
41
44
|
## Usage
|
|
42
45
|
|
|
@@ -79,26 +82,37 @@ This provider exposes a few provider-specific configuration options:
|
|
|
79
82
|
|
|
80
83
|
* basic
|
|
81
84
|
* `ssh_port` - The SSH port number used to access VM, default: `50022`
|
|
82
|
-
* `arch` - The architecture of VM, default: `aarch64`
|
|
83
|
-
* `machine` - The machine type of VM, default: `virt,accel=hvf,
|
|
84
|
-
* `cpu` - The cpu model of VM, default: `
|
|
85
|
+
* `arch` - The architecture of VM, default: auto-detected from the host (`aarch64` on Apple Silicon, `x86_64` on Intel)
|
|
86
|
+
* `machine` - The machine type of VM, default: auto-detected from host OS + arch. For native virtualization (guest arch == host arch): `virt,highmem=on,accel=hvf` (arm64) / `q35,accel=hvf` (x86_64) on macOS, `accel=kvm` on Linux, `accel=whpx` on Windows. When emulating a non-host arch it uses `accel=tcg`.
|
|
87
|
+
* `cpu` - The cpu model of VM, default: `host` for native virtualization, `max` when emulating a non-host arch
|
|
85
88
|
* `smp` - The smp setting (Simulate an SMP system with n CPUs) of VM, default: `2`
|
|
86
89
|
* `memory` - The memory setting of VM, default: `4G`
|
|
90
|
+
* `disk_resize` - The target disk size of the primary disk, requires resizing of filesystem inside of VM, default: `nil`.
|
|
87
91
|
* debug/expert
|
|
88
92
|
* `ssh_host` - The SSH IP used to access VM, default: `127.0.0.1`
|
|
89
93
|
* `ssh_auto_correct` - Auto correct port collisions for ssh port, default: `false`
|
|
90
|
-
* `net_device` - The network device, default: `virtio-net-device`
|
|
94
|
+
* `net_device` - The network device, default: auto-detected — `virtio-net-device` (arm64 `virt`) or `virtio-net-pci` (x86_64 `q35`)
|
|
91
95
|
* `drive_interface` - The interface type for the main drive, default `virtio`
|
|
92
96
|
* `image_path` - The path (or array of paths) to qcow2 image for box-less VM, default is nil value
|
|
93
97
|
* `qemu_bin` - Path to an alternative QEMU binary, default: autodetected
|
|
94
|
-
* `qemu_dir` - The path to QEMU's
|
|
98
|
+
* `qemu_dir` - The path to QEMU's data/firmware dir. Default resolution order: `ENV["QEMU_DIR"]` → `${HOMEBREW_PREFIX}/share/qemu` → per-host default (`/opt/homebrew/share/qemu` on Apple Silicon, `/usr/local/share/qemu` on Intel macOS, `/usr/share/qemu` on Linux). Only consumed for aarch64 firmware; ignored (and not validated) for x86_64.
|
|
95
99
|
* `extra_qemu_args` - The raw list of additional arguments to pass to QEMU. Use with extreme caution. (see "Force Multicore" below as example)
|
|
96
100
|
* `extra_netdev_args` - extra, comma-separated arguments to pass to the -netdev parameter. Use with caution. (see "Force Local IP" below as example)
|
|
101
|
+
* `extra_drive_args` - Add optional extra arguments to each drive attached, default: `[]`
|
|
97
102
|
* `control_port` - The port number used to control vm from vagrant, default is nil value. (nil means use unix socket)
|
|
98
103
|
* `debug_port` - The port number used to export serial port of the vm for debug, default is nil value. (nil means use unix socket, see "Debug" below for details)
|
|
99
104
|
* `no_daemonize` - Disable the "daemonize" mode of QEMU, default is false. (see "Windows host" below as example)
|
|
100
105
|
* `firmware_format` - The format of aarch64 firmware images (`edk2-aarch64-code.fd` and `edk2-arm-vars.fd`) loaded from `qemu_dir`, default: `raw`
|
|
101
106
|
* `other_default` - The other default arguments used by this plugin, default: `%W(-parallel null -monitor none -display none -vga none)`
|
|
107
|
+
* `extra_image_opts` - Options passed via `-o` to `qemu-img` when the base qcow2 images are created, default: `[]`
|
|
108
|
+
* `graceful_timeout` - Seconds to wait for the guest to shut down on `vagrant halt` before the QEMU process is force-killed, default: `60`
|
|
109
|
+
* advanced networking (requires `advanced_network = true`)
|
|
110
|
+
* `advanced_network` - Enable dual-NIC advanced networking with `private_network` support, default: `false`
|
|
111
|
+
* `net_mode` - Network backend: `:auto` (detect by platform), `:vmnet_shared`, `:vmnet_host`, `:vmnet_bridged` (macOS), `:tap` (Linux), `:socket` (QEMU `socket` netdev — multicast or point-to-point, see `socket_opts`), default: `:auto`
|
|
112
|
+
* `vmnet_interface` - Physical interface for vmnet-bridged mode, default: `en0`
|
|
113
|
+
* `tap_device` - TAP device name for Linux tap backend, default: `nil` (uses `tap0`)
|
|
114
|
+
* `mcast_addr` - Convenience shortcut for the `:socket` backend's multicast address, default: `nil` (uses `230.0.0.1:1234`)
|
|
115
|
+
* `socket_opts` - Raw options for the `:socket` netdev, emitted verbatim as `-netdev socket,id=netN,<socket_opts>`. You pick the mode: `"mcast=230.0.0.1:1234"` (multicast, N-way), `"listen=:1234"` / `"connect=127.0.0.1:1234"` (point-to-point; you decide which VM listens and which connects — the no-root, macOS-friendly path). Overrides `mcast_addr`. Default: `nil` (falls back to multicast)
|
|
102
116
|
|
|
103
117
|
### Usage
|
|
104
118
|
|
|
@@ -169,6 +183,11 @@ end
|
|
|
169
183
|
|
|
170
184
|
4. Work with a x86_64 box (basic config)
|
|
171
185
|
|
|
186
|
+
On an **Intel Mac** (or Linux x86_64 host) these defaults are now auto-detected,
|
|
187
|
+
so a plain `config.vm.box = "..."` with no provider overrides is usually enough.
|
|
188
|
+
The explicit settings below are only needed to **emulate** x86_64 on an Apple
|
|
189
|
+
Silicon host (cross-arch → TCG):
|
|
190
|
+
|
|
172
191
|
```
|
|
173
192
|
Vagrant.configure(2) do |config|
|
|
174
193
|
config.vm.box = "centos/7"
|
|
@@ -273,6 +292,109 @@ Thanks example from @Leandros.
|
|
|
273
292
|
|
|
274
293
|
See [pr#73](https://github.com/ppggff/vagrant-qemu/pull/73) for details.
|
|
275
294
|
|
|
295
|
+
11. Improved VM I/O performance
|
|
296
|
+
|
|
297
|
+
When creating the disks that are attached, each disk is an id assign in order
|
|
298
|
+
they appear in the `Vagrantfile`. The primary disk has the `id` of `disk0`.
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
Vagrant.configure("2") do |config|
|
|
302
|
+
# ... other stuff
|
|
303
|
+
|
|
304
|
+
config.vm.provider "qemu" do |qe|
|
|
305
|
+
# Use a `none` drive interface.
|
|
306
|
+
qe.drive_interface = "none"
|
|
307
|
+
qe.extra_drive_args = "cache=none,aio=threads"
|
|
308
|
+
|
|
309
|
+
# To improve I/O performance, create a separate I/O thread.
|
|
310
|
+
# We refer to the primary disk as `disk0`.
|
|
311
|
+
qe.extra_qemu_args = %w(
|
|
312
|
+
-object iothread,id=io1
|
|
313
|
+
-device virtio-blk-pci,drive=disk0,iothread=io1
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
See the [QEMU Documentation](https://www.qemu.org/docs/master/devel/multiple-iothreads.html) and [heiko-sieger.info/tuning-vm-disk-performance/](https://www.heiko-sieger.info/tuning-vm-disk-performance/) for more details.
|
|
320
|
+
|
|
321
|
+
12. Advanced networking with private_network
|
|
322
|
+
|
|
323
|
+
Pick a backend with `net_mode`: QEMU's native vmnet.framework on macOS (requires sudo), TAP on Linux, or the `socket` netdev. The `:socket` backend is a thin wrapper around QEMU's `socket` netdev — you choose the mode in `socket_opts`: `mcast=` (multicast, N-way, Linux/Windows) or `listen=`/`connect=` (point-to-point, no root, works on macOS). The plugin creates two NICs: NIC 0 (user-mode for SSH and port forwarding) and NIC 1 (platform backend for VM networking). The static IP is delivered via a cloud-init NoCloud seed ISO that the plugin builds and attaches automatically; the NICs are matched by MAC address, never by interface order.
|
|
324
|
+
|
|
325
|
+
For VM-to-VM networking on macOS without sudo, use `:socket` with a `listen`/`connect` pair — you decide which VM listens and which connects:
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
Vagrant.configure("2") do |config|
|
|
329
|
+
PORT = 12399
|
|
330
|
+
# vm1 listens; define it first so it is up before vm2 connects.
|
|
331
|
+
config.vm.define "vm1" do |c|
|
|
332
|
+
c.vm.box = "perk/ubuntu-2204-arm64" # an aarch64 cloud-init box
|
|
333
|
+
c.vm.network "private_network", ip: "192.168.105.51"
|
|
334
|
+
c.vm.provider "qemu" do |qe|
|
|
335
|
+
qe.advanced_network = true
|
|
336
|
+
qe.net_mode = :socket
|
|
337
|
+
qe.socket_opts = "listen=127.0.0.1:#{PORT}"
|
|
338
|
+
qe.ssh_auto_correct = true
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
# vm2 connects to vm1.
|
|
342
|
+
config.vm.define "vm2" do |c|
|
|
343
|
+
c.vm.box = "perk/ubuntu-2204-arm64"
|
|
344
|
+
c.vm.network "private_network", ip: "192.168.105.52"
|
|
345
|
+
c.vm.provider "qemu" do |qe|
|
|
346
|
+
qe.advanced_network = true
|
|
347
|
+
qe.net_mode = :socket
|
|
348
|
+
qe.socket_opts = "connect=127.0.0.1:#{PORT}"
|
|
349
|
+
qe.ssh_auto_correct = true
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
A single VM with a static IP (vmnet is the default backend on macOS when `net_mode` is `:auto`):
|
|
356
|
+
|
|
357
|
+
```ruby
|
|
358
|
+
Vagrant.configure("2") do |config|
|
|
359
|
+
config.vm.box = "ppggff/centos-7-aarch64-2009-4K"
|
|
360
|
+
config.vm.network "private_network", ip: "192.168.105.10"
|
|
361
|
+
|
|
362
|
+
config.vm.provider "qemu" do |qe|
|
|
363
|
+
qe.advanced_network = true
|
|
364
|
+
# qe.net_mode = :vmnet_shared # default on macOS when :auto
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Notes:
|
|
370
|
+
* The guest image must include cloud-init, otherwise the static IP is silently not applied
|
|
371
|
+
* On macOS, vmnet requires root: run `sudo vagrant up` (and the other lifecycle commands such as `halt`/`reload`/`destroy`), because the plugin launches QEMU as a child of the Vagrant process and does not elevate it on its own. The plugin warns when vmnet is selected and Vagrant is not running as root.
|
|
372
|
+
* Side effect of running under `sudo`: QEMU and everything it writes become **root-owned** — the per-VM data directory (`.vagrant/machines/<name>/qemu/` in your project) and any box Vagrant downloads while elevated (`~/.vagrant.d/boxes/<box>/`). A later command run **without** `sudo` then fails with `EACCES` — e.g. a plain `vagrant status`/`up`, an unprivileged test run, or switching to a rootless backend — often on the box's `box_update_check` file. To handle it, either keep using `sudo` consistently for that environment, or restore ownership:
|
|
373
|
+
```sh
|
|
374
|
+
sudo chown -R "$(id -un)":staff ~/.vagrant.d/boxes/<box> .vagrant
|
|
375
|
+
```
|
|
376
|
+
Pre-adding boxes as your normal user (`vagrant box add <box>`) before the first `sudo vagrant up` also avoids the box ending up root-owned.
|
|
377
|
+
* To avoid root (and this side effect) entirely on macOS, use [`socket_vmnet`](https://github.com/lima-vm/socket_vmnet) — a small root helper daemon you install once, which QEMU then connects to as a normal user (the approach Lima/Colima/minikube take). The `com.apple.developer.networking.vmnet` entitlement could also bypass root in principle, but it is a *restricted* Apple entitlement that requires an Apple-provisioned signing certificate and cannot be ad-hoc / self-signed onto Homebrew's QEMU, so it is not a practical option for individual users.
|
|
378
|
+
* Without `advanced_network = true`, the `private_network` configuration is ignored with a warning
|
|
379
|
+
* When only one NIC is needed (no `private_network`), no cloud-init seed is attached, avoiding compatibility issues
|
|
380
|
+
* Combining `advanced_network` with `config.vm.cloud_init` is supported: the plugin merges your user-data and the generated network-config into a single NoCloud seed
|
|
381
|
+
* The Linux `:tap` backend expects a pre-created tap device attached to a bridge, e.g.:
|
|
382
|
+
`sudo ip tuntap add tap0 mode tap && sudo ip link set tap0 master br0 && sudo ip link set tap0 up`
|
|
383
|
+
* `socket_opts = "mcast=..."` gives N-way VM-to-VM on Linux/Windows, but does **not** work on macOS: QEMU binds the netdev socket to the multicast group address, which the Darwin socket stack refuses to send from (`EADDRNOTAVAIL`). On macOS use a `listen`/`connect` pair (no root) or vmnet (sudo).
|
|
384
|
+
* `socket_opts = "listen=..."` / `"connect=..."` is a point-to-point QEMU TCP link and connects **exactly two** VMs (QEMU's listening socket accepts a single connection — it is not a hub). You choose which VM listens and which connects. The listener must be running before the connector starts, so define the listener first and bring the environment up together (`vagrant up`); starting a connector alone, or reloading the listener, drops the link.
|
|
385
|
+
|
|
386
|
+
Platform support:
|
|
387
|
+
|
|
388
|
+
| Platform | Backend (`net_mode`) | Host ↔ VM | VM ↔ VM | Root? | External dependency |
|
|
389
|
+
|----------|---------|:---------:|:-------:|:-----:|:-------------------:|
|
|
390
|
+
| macOS | `:vmnet_shared`/`_host`/`_bridged` | Yes | Yes | sudo (or socket_vmnet) | None (QEMU >= 7.0) |
|
|
391
|
+
| macOS | `:socket` (`listen`/`connect`) | No (use port forwarding) | Yes (2 VMs) | No | None |
|
|
392
|
+
| Linux | `:tap` + bridge | Yes | Yes | sudo | Pre-created tap device + bridge (`ip` command) |
|
|
393
|
+
| Linux | `:socket` (`mcast`) | No (use port forwarding) | Yes | No | None |
|
|
394
|
+
| Windows | `:socket` (`mcast`) | No (use port forwarding) | Yes | No | None |
|
|
395
|
+
|
|
396
|
+
(`socket_opts = "mcast=..."` is not usable on macOS — see the note above; use a `listen`/`connect` pair there.)
|
|
397
|
+
|
|
276
398
|
## Debug
|
|
277
399
|
|
|
278
400
|
Serial port is exported to unix socket: `<user_home>/.vagrant.d/tmp/vagrant-qemu/<id>/qemu_socket_serial`, or `debug_port`.
|
|
@@ -294,17 +416,84 @@ To send ctrl+c to GuestOS from `nc`, try:
|
|
|
294
416
|
|
|
295
417
|
## Build
|
|
296
418
|
|
|
297
|
-
To build the `vagrant-qemu` plugin
|
|
298
|
-
[Bundler](http://gembundler.com) to get the dependencies:
|
|
419
|
+
To build the `vagrant-qemu` plugin
|
|
299
420
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
421
|
+
**Development Environment:**
|
|
422
|
+
|
|
423
|
+
Ensure your development environment has the necessary tools installed, such as:
|
|
424
|
+
|
|
425
|
+
* **Ruby**:
|
|
426
|
+
* [Ruby installation](https://www.ruby-lang.org/en/documentation/installation/)
|
|
427
|
+
* [Ruby Version Manager (RVM)](https://rvm.io/rvm/install)
|
|
428
|
+
* [Ruby Installer for Windows](https://rubyinstaller.org/)
|
|
429
|
+
* [Bundler](https://bundler.io/):
|
|
430
|
+
```sh
|
|
431
|
+
gem install bundler
|
|
432
|
+
```
|
|
433
|
+
* [Rake](https://github.com/ruby/rake)
|
|
434
|
+
```sh
|
|
435
|
+
gem install rake
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
1. Clone this repository:
|
|
439
|
+
```sh
|
|
440
|
+
git clone https://github.com/ppggff/vagrant-qemu.git
|
|
441
|
+
cd vagrant-qemu
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
2. Use [bundler](http://gembundler.com) to install the necessary dependencies to ensure all required Ruby gems are available for buidling the plugin out
|
|
445
|
+
```sh
|
|
446
|
+
bundle config set --local path 'vendor/bundle'
|
|
447
|
+
bundle install
|
|
448
|
+
```
|
|
449
|
+
> This command tells Bundler to install gems in the vendor/bundle directory within your project.
|
|
303
450
|
|
|
304
|
-
|
|
451
|
+
3. Use `rake` to build the plugin. This command will package your changes into a gem file:
|
|
305
452
|
|
|
453
|
+
```sh
|
|
454
|
+
bundle exec rake build
|
|
455
|
+
```
|
|
456
|
+
> After running this command, you should see a `.gem` file created in the `pkg` directory within the repository. This file represents your built plugin.
|
|
457
|
+
|
|
458
|
+
4. Use `vagrant plugin install` to install the plugin from the local `.gem` file. This ensures that Vagrant uses the locally built version.
|
|
459
|
+
|
|
460
|
+
```sh
|
|
461
|
+
vagrant plugin install ./pkg/vagrant-qemu-<version>.gem
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
> Replace `<version>` with the actual version number of the locally built `.gem` file
|
|
465
|
+
|
|
466
|
+
### Check Installed Plugins
|
|
467
|
+
|
|
468
|
+
After installation, verify that the locally built `vagrant-qemu` plugin is installed by running:
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
vagrant plugin list | grep vagrant-qemu
|
|
306
472
|
```
|
|
473
|
+
|
|
474
|
+
> This command will list all installed plugins, and you should see the vagrant-qemu plugin with the locally built version.
|
|
475
|
+
|
|
476
|
+
### Running Tests
|
|
477
|
+
|
|
478
|
+
```sh
|
|
479
|
+
# Unit tests (fast, no QEMU needed)
|
|
480
|
+
bundle exec rake spec:unit
|
|
481
|
+
|
|
482
|
+
# Acceptance tests (mock QEMU, no real VM)
|
|
483
|
+
bundle exec rake spec:acceptance
|
|
484
|
+
|
|
485
|
+
# End-to-end tests (requires QEMU and a box image). e2e exercises the
|
|
486
|
+
# INSTALLED plugin — rebuild and reinstall first (the suite fails fast on
|
|
487
|
+
# a version mismatch):
|
|
307
488
|
bundle exec rake build
|
|
489
|
+
vagrant plugin install ./pkg/vagrant-qemu-<version>.gem
|
|
490
|
+
TEST_QEMU=1 bundle exec rake spec:e2e
|
|
491
|
+
|
|
492
|
+
# End-to-end with vmnet (requires sudo + macOS; needs an aarch64 cloud-init box)
|
|
493
|
+
TEST_QEMU=1 TEST_VMNET=1 TEST_BOX_CLOUDINIT=perk/ubuntu-2204-arm64 sudo -E bundle exec rake spec:e2e
|
|
494
|
+
|
|
495
|
+
# All tests
|
|
496
|
+
bundle exec rake spec
|
|
308
497
|
```
|
|
309
498
|
|
|
310
499
|
## Known issue / Troubleshooting
|
|
@@ -367,25 +556,35 @@ If you get this error when running `vagrant up`
|
|
|
367
556
|
|
|
368
557
|
### 4. The box you're using with the QEMU provider ('default') is invalid
|
|
369
558
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
559
|
+
`qemu_dir` is auto-detected (Homebrew prefix / per-host default) and is only
|
|
560
|
+
needed for **aarch64** firmware — x86_64 boots on SeaBIOS and no longer
|
|
561
|
+
validates it. If detection still picks the wrong path for an aarch64 box
|
|
562
|
+
(e.g. a MacPorts or custom QEMU install), set it explicitly. Find the correct
|
|
563
|
+
one with:
|
|
373
564
|
```
|
|
374
565
|
echo `brew --prefix`/share/qemu
|
|
375
566
|
```
|
|
376
567
|
|
|
377
|
-
|
|
568
|
+
Then either export `QEMU_DIR` / `HOMEBREW_PREFIX`, or set it in the `Vagrantfile`:
|
|
378
569
|
```
|
|
379
570
|
config.vm.provider "qemu" do |qe|
|
|
380
571
|
qe.qemu_dir = "/usr/local/share/qemu"
|
|
381
572
|
end
|
|
382
573
|
```
|
|
383
574
|
|
|
575
|
+
### 5. `conflicting dependencies logger (= 1.6.0) and logger (= 1.6.1)` when installing the plugin
|
|
576
|
+
|
|
577
|
+
This is a Vagrant 2.4.2 packaging bug (bundled `logger` gem version conflict),
|
|
578
|
+
not a problem with this plugin — see
|
|
579
|
+
[hashicorp/vagrant#13534](https://github.com/hashicorp/vagrant/issues/13534).
|
|
580
|
+
Upgrade Vagrant to **2.4.3 or newer** (the Homebrew cask may lag; install the
|
|
581
|
+
official build from [vagrantup.com](https://www.vagrantup.com/downloads) if
|
|
582
|
+
needed). Do **not** work around it by pinning `logger` in a Gemfile — that tends
|
|
583
|
+
to deepen the conflict.
|
|
584
|
+
|
|
384
585
|
## TODO
|
|
385
586
|
|
|
386
587
|
* Support NFS shared folder
|
|
387
588
|
* Support package VM to box
|
|
388
589
|
* More configures
|
|
389
|
-
* Better error messages
|
|
390
|
-
* Network
|
|
391
590
|
* GUI mode
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
|
3
|
-
|
|
3
|
+
require 'rspec/core/rake_task'
|
|
4
4
|
|
|
5
5
|
# Immediately sync all stdout so that tools like buildbot can
|
|
6
6
|
# immediately load in the output.
|
|
@@ -14,12 +14,29 @@ Dir.chdir(File.expand_path("../", __FILE__))
|
|
|
14
14
|
# publishing.
|
|
15
15
|
Bundler::GemHelper.install_tasks
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
# Test tasks
|
|
18
|
+
namespace :spec do
|
|
19
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
|
20
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
|
21
|
+
t.rspec_opts = "--order defined"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
RSpec::Core::RakeTask.new(:acceptance) do |t|
|
|
25
|
+
t.pattern = "spec/acceptance/**/*_spec.rb"
|
|
26
|
+
t.rspec_opts = "--order defined"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
RSpec::Core::RakeTask.new(:e2e) do |t|
|
|
30
|
+
t.pattern = "spec/e2e/**/*_spec.rb"
|
|
31
|
+
t.rspec_opts = "--order defined"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "Run all specs"
|
|
36
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
37
|
+
t.pattern = "spec/**/*_spec.rb"
|
|
38
|
+
t.rspec_opts = "--order defined"
|
|
39
|
+
end
|
|
23
40
|
|
|
24
41
|
# build
|
|
25
42
|
task :default => :build
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
require "vagrant/action/builtin/cloud_init_setup"
|
|
6
|
+
|
|
7
|
+
require_relative "../network"
|
|
8
|
+
|
|
9
|
+
module VagrantPlugins
|
|
10
|
+
module QEMU
|
|
11
|
+
module Action
|
|
12
|
+
# Carries the cloud-init network-config for the advanced-network private
|
|
13
|
+
# NIC into a NoCloud cidata seed. The ISO build is delegated to the
|
|
14
|
+
# :create_iso host capability and the attach goes through the provider
|
|
15
|
+
# disk capability (cap/disk.rb).
|
|
16
|
+
#
|
|
17
|
+
# NoCloud reads user-data, meta-data and network-config from a single
|
|
18
|
+
# filesystem labelled "cidata"; two cidata volumes are ambiguous. So when
|
|
19
|
+
# core CloudInitSetup (which runs earlier in the chain) has already built
|
|
20
|
+
# a user-data seed, we rebuild that same ISO in place with network-config
|
|
21
|
+
# added instead of attaching a second seed.
|
|
22
|
+
class CloudInitNetwork
|
|
23
|
+
# Disk name core Vagrant::Action::Builtin::CloudInitSetup gives its
|
|
24
|
+
# user-data seed.
|
|
25
|
+
CORE_SEED_DISK_NAME = "vagrant-cloud_init-disk".freeze
|
|
26
|
+
|
|
27
|
+
def initialize(app, env)
|
|
28
|
+
@app = app
|
|
29
|
+
@logger = Log4r::Logger.new("vagrant_qemu::action::cloud_init_network")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call(env)
|
|
33
|
+
machine = env[:machine]
|
|
34
|
+
|
|
35
|
+
pn = machine.config.vm.networks
|
|
36
|
+
.select { |t, _| t == :private_network }
|
|
37
|
+
.map { |_, opts| opts }
|
|
38
|
+
.first
|
|
39
|
+
|
|
40
|
+
if machine.provider_config.advanced_network && pn && pn[:ip]
|
|
41
|
+
existing = machine.config.vm.disks.find do |d|
|
|
42
|
+
d.type == :dvd && d.name == CORE_SEED_DISK_NAME
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if existing
|
|
46
|
+
merge_into_seed(machine, env, pn, existing.file)
|
|
47
|
+
else
|
|
48
|
+
attach_network_seed(machine, env, pn)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@app.call(env)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# No core cloud-init seed: build our own network-only seed and attach
|
|
58
|
+
# it as a fresh :dvd disk.
|
|
59
|
+
def attach_network_seed(machine, env, pn)
|
|
60
|
+
iso_path = build_seed(machine, env, pn,
|
|
61
|
+
user_data: "#cloud-config\n",
|
|
62
|
+
file_destination: machine.data_dir.join("vagrant-qemu-network.iso"))
|
|
63
|
+
|
|
64
|
+
machine.config.vm.disk :dvd, file: iso_path.to_s, name: "vagrant-qemu-network-disk"
|
|
65
|
+
machine.config.vm.disks.each do |d|
|
|
66
|
+
d.finalize! if d.type == :dvd && d.file == iso_path.to_s
|
|
67
|
+
end
|
|
68
|
+
@logger.info("Attached cloud-init network seed ISO at #{iso_path}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Core CloudInitSetup already built a user-data seed and attached it.
|
|
72
|
+
# Rebuild that same ISO in place, carrying both the user-data and our
|
|
73
|
+
# network-config in one cidata volume. No second disk is registered.
|
|
74
|
+
def merge_into_seed(machine, env, pn, iso_path)
|
|
75
|
+
ud_cfgs = machine.config.vm.cloud_init_configs
|
|
76
|
+
.select { |c| c.type == :user_data }
|
|
77
|
+
setup = Vagrant::Action::Builtin::CloudInitSetup.new(->(_) {}, env)
|
|
78
|
+
user_data = setup.setup_user_data(machine, env, ud_cfgs).to_s
|
|
79
|
+
|
|
80
|
+
FileUtils.rm_f(iso_path)
|
|
81
|
+
build_seed(machine, env, pn,
|
|
82
|
+
user_data: user_data,
|
|
83
|
+
file_destination: Pathname.new(iso_path))
|
|
84
|
+
@logger.info("Merged cloud-init network seed into #{iso_path}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Write a NoCloud cidata seed (network-config + meta-data + user-data)
|
|
88
|
+
# and build the ISO via the :create_iso host capability. Returns the
|
|
89
|
+
# ISO path.
|
|
90
|
+
def build_seed(machine, env, pn, user_data:, file_destination:)
|
|
91
|
+
if !env[:env].host.capability?(:create_iso)
|
|
92
|
+
raise Vagrant::Errors::CreateIsoHostCapNotFound
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
mac0, mac1 = Network.nic_macs(machine.id, pn)
|
|
96
|
+
network_config = Network.build_network_config(
|
|
97
|
+
mac0: mac0,
|
|
98
|
+
mac1: mac1,
|
|
99
|
+
ip: pn[:ip],
|
|
100
|
+
netmask: pn[:netmask] || "255.255.255.0"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
source_dir = Pathname.new(Dir.mktmpdir("vagrant-qemu-network-seed"))
|
|
104
|
+
begin
|
|
105
|
+
File.write(source_dir.join("network-config"), network_config)
|
|
106
|
+
File.write(source_dir.join("meta-data"),
|
|
107
|
+
{ "instance-id" => "i-#{machine.id.to_s.split("-").join}" }.to_yaml)
|
|
108
|
+
File.write(source_dir.join("user-data"), user_data)
|
|
109
|
+
|
|
110
|
+
env[:env].host.capability(
|
|
111
|
+
:create_iso,
|
|
112
|
+
source_dir,
|
|
113
|
+
file_destination: file_destination,
|
|
114
|
+
volume_id: "cidata"
|
|
115
|
+
)
|
|
116
|
+
ensure
|
|
117
|
+
FileUtils.remove_entry(source_dir)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -8,7 +8,13 @@ module VagrantPlugins
|
|
|
8
8
|
|
|
9
9
|
def call(env)
|
|
10
10
|
env[:ui].info(I18n.t("vagrant_qemu.destroying"))
|
|
11
|
-
|
|
11
|
+
begin
|
|
12
|
+
env[:machine].provider.driver.delete
|
|
13
|
+
rescue => e
|
|
14
|
+
# Vagrant errors only render error_key translations; a bare
|
|
15
|
+
# String here would be silently dropped.
|
|
16
|
+
raise Errors::DestroyError, message: e.message
|
|
17
|
+
end
|
|
12
18
|
env[:machine].id = nil
|
|
13
19
|
|
|
14
20
|
@app.call(env)
|
|
@@ -61,12 +61,17 @@ module VagrantPlugins
|
|
|
61
61
|
@logger.info("Found box image path: #{img_info}")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# qemu_dir holds the firmware images, which are only consumed for the
|
|
65
|
+
# aarch64 target (see driver.rb). x86_64 boots on SeaBIOS and never
|
|
66
|
+
# touches qemu_dir, so don't let a missing dir block it.
|
|
64
67
|
qemu_dir = Pathname.new(env[:machine].provider_config.qemu_dir)
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
if env[:machine].provider_config.arch == "aarch64"
|
|
69
|
+
if !qemu_dir.directory?
|
|
70
|
+
@logger.error("Invalid qemu dir: #{qemu_dir}")
|
|
71
|
+
raise Errors::ConfigError, err: "Invalid qemu dir: #{qemu_dir}"
|
|
72
|
+
else
|
|
73
|
+
@logger.info("Found qemu dir: #{qemu_dir}")
|
|
74
|
+
end
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
env[:ui].output("Importing a QEMU instance")
|
|
@@ -75,7 +80,9 @@ module VagrantPlugins
|
|
|
75
80
|
:image_path => image_path,
|
|
76
81
|
:qemu_dir => qemu_dir,
|
|
77
82
|
:arch => env[:machine].provider_config.arch,
|
|
78
|
-
:firmware_format => env[:machine].provider_config.firmware_format
|
|
83
|
+
:firmware_format => env[:machine].provider_config.firmware_format,
|
|
84
|
+
:extra_image_opts => env[:machine].provider_config.extra_image_opts,
|
|
85
|
+
:disk_resize => env[:machine].provider_config.disk_resize,
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
env[:ui].detail("Creating and registering the VM...")
|
|
@@ -24,9 +24,9 @@ module VagrantPlugins
|
|
|
24
24
|
env[:machine_state_id] = :not_created
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# Update ssh_port
|
|
27
|
+
# Update driver's runtime ssh_port from persisted options
|
|
28
28
|
if env[:machine_state_id] == :running
|
|
29
|
-
env[:machine].
|
|
29
|
+
env[:machine].provider.driver.get_ssh_port(env[:machine].provider_config.ssh_port)
|
|
30
30
|
end
|
|
31
31
|
@app.call(env)
|
|
32
32
|
end
|