├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── vagrant-qemu.rb └── vagrant-qemu │ ├── action.rb │ ├── action │ ├── destroy.rb │ ├── import.rb │ ├── message_already_created.rb │ ├── message_not_created.rb │ ├── message_will_not_destroy.rb │ ├── prepare_forwarded_port_collision_params.rb │ ├── read_state.rb │ ├── start_instance.rb │ ├── stop_instance.rb │ └── warn_networks.rb │ ├── cap.rb │ ├── cap │ └── disk.rb │ ├── config.rb │ ├── driver.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── util │ └── timer.rb │ └── version.rb ├── locales └── en.yml └── vagrant-qemu.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | # OS-specific 2 | .DS_Store 3 | 4 | # editors 5 | *.swp 6 | 7 | # Bundler/Rubygems 8 | *.gem 9 | .bundle 10 | pkg/* 11 | tags 12 | Gemfile.lock 13 | vendor 14 | vendor/** 15 | vendor/**/* 16 | 17 | # Vagrant 18 | .vagrant 19 | Vagrantfile 20 | !example_box/Vagrantfile 21 | 22 | # RVM files for gemset/ruby setting 23 | .ruby-* 24 | .rvmrc 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 (2021-12-15) 2 | 3 | * Initial release. 4 | 5 | # 0.1.1 (2021-12-30) 6 | 7 | * Works with basic functions. 8 | 9 | # 0.1.2 (2022-01-06) 10 | 11 | * Support vm without box. 12 | 13 | # 0.1.3 (2022-01-25) 14 | # 0.1.4 (2022-01-25) 15 | # 0.1.5 (2022-01-25) 16 | # 0.1.6 (2022-01-25) 17 | 18 | * Add config 'net_device'. 19 | 20 | # 0.1.7 (2022-03-26) 21 | 22 | * Add basic support to forwarded ports. 23 | * Move unix_socket to `/.vagrant.d/tmp`. 24 | 25 | # 0.1.8 (2022-05-05) 26 | 27 | * Fix port collision problem with default ssh port 2222. 28 | * Export serial port to unix socket for debug. 29 | 30 | # 0.1.9 (2022-08-01) 31 | 32 | * Set default config for newer qemu (>=7.0.0) 33 | 34 | # 0.2.0 (2022-08-31) 35 | 36 | * Add config extra_qemu_args'. 37 | * Refine error message, such as 'Invalid qemu dir'. 38 | * Add a 'Force Multicore' to Readme. 39 | 40 | # 0.3.0 (2022-09-16) 41 | 42 | * Add config extra_netdev_args. 43 | * Replace `nc` with ruby's socket 44 | * Add config control_port, debug_port, no_daemonize config for window host 45 | 46 | # 0.3.1 (2022-09-16) 47 | 48 | * Fix missing :arch for driver.import(options) 49 | 50 | # 0.3.2 (2022-09-20) 51 | 52 | * Use kill 0 to check whether a process is running 53 | 54 | # 0.3.3 (2022-10-11) 55 | 56 | * Fix a compatibility issue about ruby 3.x 57 | 58 | # 0.3.4 (2023-03-09) 59 | 60 | * Add config 'drive_interface'. 61 | 62 | # 0.3.5 (2023-07-27) 63 | 64 | * Fix forwarded ports bug. #39 65 | * Add config 'firmware_format', 'ssh_host', 'other_default'. 66 | * Allow no cpu for riscv64. 67 | * Allow more config options to be nil. 68 | * Let id start with "vq_". 69 | 70 | # 0.3.6 (2024-02-27) 71 | 72 | * Config 'image_path' support array type 73 | * Try to support libvirt box v2 format 74 | 75 | # 0.3.7 (2025-02-02) 76 | 77 | * Ignore exception after sending 'system_powerdown' cmd, fix windows halt error 78 | * Move pid file to tmp dir 79 | * Be able to auto correct ssh port collisions, new config: ssh_auto_correct 80 | 81 | # 0.3.8 (2025-02-21) 82 | 83 | * Fix regression that ssh_port is not working #68 84 | 85 | # 0.3.9 (2025-02-22) 86 | 87 | * Support ssh_port in string (need better error message) 88 | 89 | # 0.3.10 (2025-05-06) 90 | 91 | * Add support for `qemu_bin` to customize QEMU binary 92 | 93 | # 0.3.11 (2025-05-06) 94 | 95 | * Re-publish with repacked gem 96 | 97 | # 0.3.22 (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 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :development do 4 | # We depend on Vagrant for development, but we don't add it as a 5 | # gem dependency because we expect to be installed within the 6 | # Vagrant environment itself using `vagrant plugin`. 7 | gem "vagrant", :git => "https://github.com/mitchellh/vagrant.git" 8 | 9 | gem "rake" 10 | gem "rspec", "~> 3.4" 11 | gem "rspec-its" 12 | end 13 | 14 | group :plugins do 15 | gem "vagrant-qemu" , path: "." 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2021 ppggff 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vagrant QEMU Provider 2 | 3 | This is a Vagrant plugin that adds a simple QEMU provider to Vagrant, allowing Vagrant 4 | to control and provision machines using QEMU. 5 | 6 | **Notes: test with Apple Silicon / M1 and CentOS / Ubuntu aarch64 image** 7 | 8 | ## Compatible with 9 | 10 | Tested: 11 | 12 | * MacOS >= 12.4 13 | * QEMU >= 7.0.0 14 | * CentOS (centos-7-aarch64-2009-4K) 15 | * Ubuntu (see [Wiki](https://github.com/ppggff/vagrant-qemu/wiki) for detais) 16 | * Debian buster64 on x86_64 (see [Wiki](https://github.com/ppggff/vagrant-qemu/wiki) for detais) 17 | 18 | Others: 19 | 20 | * (MacOS < 12.4) + (QEMU >= 7.0.0) : update OS, or use QEMU 6.x 21 | * QEMU 6.x: use following config: 22 | ``` 23 | config.vm.provider "qemu" do |qe| 24 | qe.machine = "virt,accel=hvf,highmem=off" 25 | qe.cpu = "cortex-a72" 26 | end 27 | ``` 28 | 29 | ## Features 30 | 31 | * Import from a Libvirt vagrant box or qcow2 image 32 | * To use box for **Paralles or VMware Fusion**, see [Wiki](https://github.com/ppggff/vagrant-qemu/wiki) for details 33 | * Libvirt box v2 format support is experimental 34 | * Start VM without GUI 35 | * SSH into VM 36 | * Provision the instances with any built-in Vagrant provisioner 37 | * Synced folder support via SMB 38 | * Basic operation: up, ssh, halt, destroy 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 | 43 | ## Usage 44 | 45 | Make sure QEMU is installed, if not: 46 | 47 | ``` 48 | brew install qemu 49 | ``` 50 | 51 | Install plugin: 52 | 53 | ``` 54 | vagrant plugin install vagrant-qemu 55 | ``` 56 | 57 | Prepare a `Vagrantfile`, see [Example](#example), and start: 58 | 59 | ``` 60 | vagrant up --provider qemu 61 | ``` 62 | 63 | Notes: 64 | * may need password to setup SMB on Mac, 65 | see [vagrant doc](https://www.vagrantup.com/docs/synced-folders/smb) for details 66 | * need username/password to access shared folder 67 | 68 | ## Box format 69 | 70 | Same as [vagrant-libvirt version-1](https://github.com/vagrant-libvirt/vagrant-libvirt#version-1): 71 | 72 | * qcow2 image file named `box.img` 73 | * `metadata.json` file describing box image (provider, virtual_size, format) 74 | * `Vagrantfile` that does default settings 75 | 76 | ## Configuration 77 | 78 | ### Options 79 | 80 | This provider exposes a few provider-specific configuration options: 81 | 82 | * basic 83 | * `ssh_port` - The SSH port number used to access VM, default: `50022` 84 | * `arch` - The architecture of VM, default: `aarch64` 85 | * `machine` - The machine type of VM, default: `virt,accel=hvf,highmem=off` 86 | * `cpu` - The cpu model of VM, default: `cortex-a72` 87 | * `smp` - The smp setting (Simulate an SMP system with n CPUs) of VM, default: `2` 88 | * `memory` - The memory setting of VM, default: `4G` 89 | * `disk_resize` - The target disk size of the primary disk, requires resizing of filesystem inside of VM, default: `nil`. 90 | * debug/expert 91 | * `ssh_host` - The SSH IP used to access VM, default: `127.0.0.1` 92 | * `ssh_auto_correct` - Auto correct port collisions for ssh port, default: `false` 93 | * `net_device` - The network device, default: `virtio-net-device` 94 | * `drive_interface` - The interface type for the main drive, default `virtio` 95 | * `image_path` - The path (or array of paths) to qcow2 image for box-less VM, default is nil value 96 | * `qemu_bin` - Path to an alternative QEMU binary, default: autodetected 97 | * `qemu_dir` - The path to QEMU's install dir, default: `/opt/homebrew/share/qemu` 98 | * `extra_qemu_args` - The raw list of additional arguments to pass to QEMU. Use with extreme caution. (see "Force Multicore" below as example) 99 | * `extra_netdev_args` - extra, comma-separated arguments to pass to the -netdev parameter. Use with caution. (see "Force Local IP" below as example) 100 | * `extra_drive_args` - Add optional extra arguments to each drive attached, default: `[]` 101 | * `control_port` - The port number used to control vm from vagrant, default is nil value. (nil means use unix socket) 102 | * `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) 103 | * `no_daemonize` - Disable the "daemonize" mode of QEMU, default is false. (see "Windows host" below as example) 104 | * `firmware_format` - The format of aarch64 firmware images (`edk2-aarch64-code.fd` and `edk2-arm-vars.fd`) loaded from `qemu_dir`, default: `raw` 105 | * `other_default` - The other default arguments used by this plugin, default: `%W(-parallel null -monitor none -display none -vga none)` 106 | * `extra_image_opts` - Options passed via `-o` to `qemu-img` when the base qcow2 images are created, default: `[]` 107 | 108 | ### Usage 109 | 110 | These can be set like typical provider-specific configuration: 111 | 112 | ``` 113 | # Basic Vagrant config (API version 2) 114 | Vagrant.configure(2) do |config| 115 | # ... other stuff 116 | 117 | config.vm.provider "qemu" do |qe| 118 | qe.memory = "8G" 119 | end 120 | end 121 | ``` 122 | 123 | ### With `nil` value 124 | 125 | To be able to custom the result qemu command deeply, you can set some config options 126 | to `nil` value to skip related qemu arguments. 127 | 128 | * `machine`: skip `-machine xxx` 129 | * `cpu`: skip `-cpu xxx` 130 | * `smp`: skip `-smp xxx` 131 | * `memory`: skip `-m xxx` 132 | * `net_device`: skip all network related arguments: 133 | * `-device xxx,netdev=net0` 134 | * `-netdev user,id=net0,xxx` 135 | * NOTES: there will be no network, ssh won't work 136 | * `drive_interface`: skip drive for the main image, `-drive if=xxx,xxx` 137 | * `firmware_format`: skip firmware setup for aarch64, `-drive if=pflash,xxx` 138 | 139 | With `other_default = []`, all default arguments will be skipped. 140 | 141 | ## Example 142 | 143 | 1. Try with a sample box 144 | 145 | ``` 146 | vagrant init ppggff/centos-7-aarch64-2009-4K 147 | vagrant up --provider qemu 148 | ``` 149 | 150 | 2. With a local box 151 | 152 | ``` 153 | # Basic Vagrant config (API version 2) 154 | Vagrant.configure(2) do |config| 155 | config.vm.box = "test-box" 156 | config.vm.box_url = "file:///Users/xxx/test.box" 157 | config.vm.box_check_update = false 158 | end 159 | ``` 160 | 161 | 3. With a local qcow2 162 | 163 | ``` 164 | # Basic Vagrant config (API version 2) 165 | Vagrant.configure(2) do |config| 166 | config.vm.provider "qemu" do |qe, override| 167 | override.ssh.username = "xxx" 168 | override.ssh.password = "vagrant" 169 | 170 | qe.image_path = "/Users/xxx/test.qcow2" 171 | end 172 | end 173 | ``` 174 | 175 | 4. Work with a x86_64 box (basic config) 176 | 177 | ``` 178 | Vagrant.configure(2) do |config| 179 | config.vm.box = "centos/7" 180 | 181 | config.vm.provider "qemu" do |qe| 182 | qe.arch = "x86_64" 183 | qe.machine = "q35" 184 | qe.cpu = "qemu64" 185 | qe.net_device = "virtio-net-pci" 186 | end 187 | end 188 | ``` 189 | 190 | 5. Forwarded ports 191 | 192 | ``` 193 | # Basic Vagrant config (API version 2) 194 | Vagrant.configure(2) do |config| 195 | # ... other stuff 196 | 197 | config.vm.network "forwarded_port", guest: 80, host: 8080 198 | end 199 | ``` 200 | 201 | 6. Force Multicore (x86) 202 | 203 | Thanks to [taraszka](https://github.com/taraszka) for providing this config. 204 | 205 | ``` 206 | Vagrant.configure("2") do |config| 207 | config.vm.box = "centos/7" 208 | 209 | config.vm.provider "qemu" do |qe| 210 | qe.arch = "x86_64" 211 | qe.machine = "q35" 212 | qe.cpu = "max" 213 | qe.smp = "cpus=2,sockets=1,cores=2,threads=1" 214 | qe.net_device = "virtio-net-pci" 215 | qe.extra_qemu_args = %w(-accel tcg,thread=multi,tb-size=512) 216 | qe.qemu_dir = "/usr/local/share/qemu" 217 | end 218 | end 219 | ``` 220 | 221 | 7. Force Local IP 222 | 223 | ``` 224 | Vagrant.configure("2") do |config| 225 | config.vm.box = "debian/bullseye64" 226 | 227 | config.vm.provider "qemu" do |qe| 228 | qe.extra_netdev_args = "net=192.168.51.0/24,dhcpstart=192.168.51.10" 229 | end 230 | end 231 | ``` 232 | 233 | 8. Windows host 234 | 235 | Windows version QEMU doesn't support `daemonize` mode and unix socket 236 | 237 | ``` 238 | Vagrant.configure("2") do |config| 239 | # ... other stuff 240 | 241 | config.vm.provider "qemu" do |qe| 242 | qe.no_daemonize = true 243 | qe.control_port = 33333 244 | qe.debug_port = 33334 245 | end 246 | end 247 | ``` 248 | 249 | 9. Auto port collisions for ssh port (multiple machine) 250 | 251 | ``` 252 | Vagrant.configure("2") do |config| 253 | 254 | config.vm.define "vm1" do |c| 255 | c.vm.box = "ppggff/centos-7-aarch64-2009-4K" 256 | c.vm.provider "qemu" do |qe| 257 | qe.memory = "2G" 258 | qe.ssh_auto_correct = true 259 | end 260 | c.vm.synced_folder ".", "/vagrant", disabled: true 261 | end 262 | 263 | config.vm.define "vm2" do |c| 264 | c.vm.box = "ppggff/centos-7-aarch64-2009-4K" 265 | c.vm.provider "qemu" do |qe| 266 | qe.memory = "2G" 267 | qe.ssh_auto_correct = true 268 | end 269 | c.vm.synced_folder ".", "/vagrant", disabled: true 270 | end 271 | 272 | end 273 | ``` 274 | 275 | 10. Use socket_vmnet to communicate between machines 276 | 277 | Thanks example from @Leandros. 278 | 279 | See [pr#73](https://github.com/ppggff/vagrant-qemu/pull/73) for details. 280 | 281 | 11. Improved VM I/O performance 282 | 283 | When creating the disks that are attached, each disk is an id assign in order 284 | they appear in the `Vagrantfile`. The primary disk has the `id` of `disk0`. 285 | 286 | ```ruby 287 | Vagrant.configure("2") do |config| 288 | # ... other stuff 289 | 290 | config.vm.provider "qemu" do |qe| 291 | # Use a `none` drive interface. 292 | qe.drive_interface = "none" 293 | qe.extra_drive_args = "cache=none,aio=threads" 294 | 295 | # To improve I/O performance, create a separate I/O thread. 296 | # We refer to the primary disk as `disk0`. 297 | qe.extra_qemu_args = %w( 298 | -object iothread,id=io1 299 | -device virtio-blk-pci,drive=disk0,iothread=io1 300 | ) 301 | end 302 | end 303 | ``` 304 | 305 | 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. 306 | 307 | ## Debug 308 | 309 | Serial port is exported to unix socket: `/.vagrant.d/tmp/vagrant-qemu//qemu_socket_serial`, or `debug_port`. 310 | 311 | To debug and login to the GuestOS from serial port: 312 | 313 | * unix socket 314 | 1. Get the id: `.vagrant/machines/default/qemu/id` in same directory with `Vagrantfile` 315 | 2. Get the path to `qemu_socket_serial` 316 | 3. Use `nc` to connect: `nc -U /Users/.../qemu_socket_serial` 317 | * `debug_port` (for example: 33334) 318 | * Use `nc` to connect: `nc localhost 33334` 319 | 320 | To send ctrl+c to GuestOS from `nc`, try: 321 | * unix socket 322 | * `echo 03 | xxd -r -p | nc -U /Users/.../qemu_socket_serial` 323 | * `debug_port` (for example: 33334) 324 | * `echo 03 | xxd -r -p | nc localhost 33334` 325 | 326 | ## Build 327 | 328 | To build the `vagrant-qemu` plugin 329 | 330 | **Development Environment:** 331 | 332 | Ensure your development environment has the necessary tools installed, such as: 333 | 334 | * **Ruby**: 335 | * [Ruby installation](https://www.ruby-lang.org/en/documentation/installation/) 336 | * [Ruby Version Manager (RVM)](https://rvm.io/rvm/install) 337 | * [Ruby Installer for Windows](https://rubyinstaller.org/) 338 | * [Bundler](https://bundler.io/): 339 | ```sh 340 | gem install bundler 341 | ``` 342 | * [Rake](https://github.com/ruby/rake) 343 | ```sh 344 | gem install rake 345 | ``` 346 | 347 | 1. Clone this repository: 348 | ```sh 349 | git clone https://github.com/ppggff/vagrant-qemu.git 350 | cd vagrant-qemu 351 | ``` 352 | 353 | 2. Use [bundler](http://gembundler.com) to install the necessary dependencies to ensure all required Ruby gems are available for buidling the plugin out 354 | ```sh 355 | bundle config set --local path 'vendor/bundle' 356 | bundle install 357 | ``` 358 | > This command tells Bundler to install gems in the vendor/bundle directory within your project. 359 | 360 | 3. Use `rake` to build the plugin. This command will package your changes into a gem file: 361 | 362 | ```sh 363 | bundle exec rake build 364 | ``` 365 | > After running this command, you should see a `.gem` file created in the `pkg` directory within the repository. This file represents your built plugin. 366 | 367 | 4. Use `vagrant plugin install` to install the plugin from the local `.gem` file. This ensures that Vagrant uses the locally built version. 368 | 369 | ```sh 370 | vagrant plugin install ./pkg/vagrant-qemu-.gem 371 | ``` 372 | 373 | > Replace `` with the actual version number of the locally built `.gem` file 374 | 375 | ### Check Installed Plugins 376 | 377 | After installation, verify that the locally built `vagrant-qemu` plugin is installed by running: 378 | 379 | ```sh 380 | vagrant plugin list | grep vagrant-qemu 381 | ``` 382 | 383 | > This command will list all installed plugins, and you should see the vagrant-qemu plugin with the locally built version. 384 | 385 | ## Known issue / Troubleshooting 386 | 387 | ### 1. failed to create shared folder 388 | 389 | ``` 390 | We couldn't detect an IP address that was routable to this 391 | machine from the guest machine! Please verify networking is properly 392 | setup in the guest machine and that it is able to access this 393 | host. 394 | 395 | As another option, you can manually specify an IP for the machine 396 | to mount from using the `smb_host` option to the synced folder. 397 | ``` 398 | 399 | The reason is that the user mode of qemu currently in use does not support ping. 400 | `smb_host` needs to be explicitly specified. For example: 401 | 402 | ``` 403 | Vagrant.configure("2") do |config| 404 | # ... other stuff 405 | 406 | config.vm.synced_folder ".", "/vagrant", type: "smb", smb_host: "10.0.2.2" 407 | end 408 | ``` 409 | 410 | As an alternative solution(helpful for macOS) it is possible to use 9p file system via virtio. 411 | 412 | ``` 413 | config.vm.synced_folder ".", "/vagrant", disabled: true 414 | config.vm.provider "qemu" do |qe| 415 | qe.extra_qemu_args = %w(-virtfs local,path=.,mount_tag=shared,security_model=mapped) 416 | end 417 | ``` 418 | This will pass "current directory" to mount point tagged "shared" 419 | Use the following /etc/fstab entry on the vagrant vm to mount the shared directory into /home/vagrant/shared 420 | ``` 421 | shared /home/vagrant/shared 9p _netdev,trans=virtio,msize=524288 0 422 | ``` 423 | Please keep in mind that the guest OS will need to install 9p dependencies to handle the 9p filestystem. 424 | 425 | ### 2. netcat does not support the -U parameter 426 | 427 | I had netcat installed through home brew and it does not support the -U parameter. 428 | 429 | I fixed it by uninstalling netcat in home brew brew uninstall netcat 430 | 431 | Thanks @kjeldahl fix this at [issue #6](https://github.com/ppggff/vagrant-qemu/issues/6) 432 | 433 | ### 3. Vagrant SMB synced folders require the account password to be stored in an NT compatible format 434 | 435 | If you get this error when running `vagrant up` 436 | 437 | 1. On your M1 Mac, go to System Preferences > Sharing > File Sharing > Options... 438 | 2. Tick "Share Files and Folders using SMB" 439 | 3. Tick your username 440 | 4. Click Done 441 | 5. Run `vagrant up` again 442 | 443 | ### 4. The box you're using with the QEMU provider ('default') is invalid 444 | 445 | This may cause by invalid default qemu dir (`/opt/homebrew/share/qemu`). 446 | 447 | You can find the correct one by: 448 | ``` 449 | echo `brew --prefix`/share/qemu 450 | ``` 451 | 452 | And then set it (for example `/usr/local/share/qemu`) in the `Vagrantfile` as: 453 | ``` 454 | config.vm.provider "qemu" do |qe| 455 | qe.qemu_dir = "/usr/local/share/qemu" 456 | end 457 | ``` 458 | 459 | ## TODO 460 | 461 | * Support NFS shared folder 462 | * Support package VM to box 463 | * More configures 464 | * Better error messages 465 | * Network 466 | * GUI mode 467 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | # require 'rspec/core/rake_task' 4 | 5 | # Immediately sync all stdout so that tools like buildbot can 6 | # immediately load in the output. 7 | $stdout.sync = true 8 | $stderr.sync = true 9 | 10 | # Change to the directory of this file. 11 | Dir.chdir(File.expand_path("../", __FILE__)) 12 | 13 | # This installs the tasks that help with gem creation and 14 | # publishing. 15 | Bundler::GemHelper.install_tasks 16 | 17 | # Install the `spec` task so that we can run tests. 18 | # RSpec::Core::RakeTask.new(:spec) do |t| 19 | # t.rspec_opts = "--order defined" 20 | # end 21 | # Default task is to run the unit tests 22 | # task :default => :spec 23 | 24 | # build 25 | task :default => :build 26 | -------------------------------------------------------------------------------- /lib/vagrant-qemu.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | require "vagrant-qemu/plugin" 4 | 5 | module VagrantPlugins 6 | module QEMU 7 | lib_path = Pathname.new(File.expand_path("../vagrant-qemu", __FILE__)) 8 | autoload :Action, lib_path.join("action") 9 | autoload :Cap, lib_path.join("cap") 10 | autoload :Errors, lib_path.join("errors") 11 | 12 | # This returns the path to the source of this plugin. 13 | # 14 | # @return [Pathname] 15 | def self.source_root 16 | @source_root ||= Pathname.new(File.expand_path("../../", __FILE__)) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | require "vagrant/action/builder" 4 | 5 | module VagrantPlugins 6 | module QEMU 7 | module Action 8 | # Include the built-in modules so we can use them as top-level things. 9 | include Vagrant::Action::Builtin 10 | 11 | def self.action_package 12 | lambda do |env| 13 | raise Errors::NotSupportedError 14 | end 15 | end 16 | 17 | # This action is called to halt the remote machine. 18 | def self.action_halt 19 | Vagrant::Action::Builder.new.tap do |b| 20 | b.use ConfigValidate 21 | b.use Call, IsState, :not_created do |env, b2| 22 | if env[:result] 23 | b2.use MessageNotCreated 24 | next 25 | end 26 | 27 | b2.use StopInstance 28 | end 29 | end 30 | end 31 | 32 | # This action is called to terminate the remote machine. 33 | def self.action_destroy 34 | Vagrant::Action::Builder.new.tap do |b| 35 | b.use Call, DestroyConfirm do |env, b2| 36 | if env[:result] 37 | b2.use ConfigValidate 38 | b2.use Call, IsState, :not_created do |env2, b3| 39 | if env2[:result] 40 | b3.use MessageNotCreated 41 | next 42 | end 43 | 44 | b3.use ProvisionerCleanup, :before if defined?(ProvisionerCleanup) 45 | b3.use StopInstance 46 | b3.use Destroy 47 | b3.use SyncedFolderCleanup 48 | end 49 | else 50 | b2.use MessageWillNotDestroy 51 | end 52 | end 53 | end 54 | end 55 | 56 | # This action is called when `vagrant provision` is called. 57 | def self.action_provision 58 | Vagrant::Action::Builder.new.tap do |b| 59 | b.use ConfigValidate 60 | b.use Call, IsState, :not_created do |env, b2| 61 | if env[:result] 62 | b2.use MessageNotCreated 63 | next 64 | end 65 | 66 | b2.use Provision 67 | end 68 | end 69 | end 70 | 71 | # This action is called to read the state of the machine. The 72 | # resulting state is expected to be put into the `:machine_state_id` 73 | # key. 74 | def self.action_read_state 75 | Vagrant::Action::Builder.new.tap do |b| 76 | b.use ConfigValidate 77 | b.use ReadState 78 | end 79 | end 80 | 81 | # This action is called to SSH into the machine. 82 | def self.action_ssh 83 | Vagrant::Action::Builder.new.tap do |b| 84 | b.use ConfigValidate 85 | b.use Call, IsState, :not_created do |env, b2| 86 | if env[:result] 87 | b2.use MessageNotCreated 88 | next 89 | end 90 | 91 | b2.use SSHExec 92 | end 93 | end 94 | end 95 | 96 | def self.action_ssh_run 97 | Vagrant::Action::Builder.new.tap do |b| 98 | b.use ConfigValidate 99 | b.use Call, IsState, :not_created do |env, b2| 100 | if env[:result] 101 | b2.use MessageNotCreated 102 | next 103 | end 104 | 105 | b2.use SSHRun 106 | end 107 | end 108 | end 109 | 110 | def self.action_start 111 | Vagrant::Action::Builder.new.tap do |b| 112 | b.use Call, IsState, :running do |env1, b1| 113 | if env1[:result] 114 | b1.use action_provision 115 | next 116 | end 117 | 118 | b1.use CloudInitSetup 119 | b1.use CleanupDisks 120 | b1.use Disk 121 | b1.use Provision 122 | b1.use EnvSet, port_collision_repair: true 123 | b1.use PrepareForwardedPortCollisionParams 124 | b1.use HandleForwardedPortCollisions 125 | b1.use SyncedFolderCleanup 126 | b1.use SyncedFolders 127 | b1.use WarnNetworks 128 | b1.use SetHostname 129 | b1.use StartInstance 130 | b1.use WaitForCommunicator, [:running] 131 | end 132 | end 133 | end 134 | 135 | # This action is called to bring the box up from nothing. 136 | def self.action_up 137 | Vagrant::Action::Builder.new.tap do |b| 138 | b.use HandleBox 139 | b.use ConfigValidate 140 | b.use BoxCheckOutdated 141 | b.use Call, IsState, :not_created do |env1, b1| 142 | if env1[:result] 143 | b1.use Import 144 | end 145 | 146 | b1.use action_start 147 | end 148 | end 149 | end 150 | 151 | def self.action_reload 152 | Vagrant::Action::Builder.new.tap do |b| 153 | b.use ConfigValidate 154 | b.use Call, IsState, :not_created do |env, b2| 155 | if env[:result] 156 | b2.use MessageNotCreated 157 | next 158 | end 159 | 160 | b2.use action_halt 161 | b2.use action_start 162 | end 163 | end 164 | end 165 | 166 | # The autoload farm 167 | action_root = Pathname.new(File.expand_path("../action", __FILE__)) 168 | autoload :MessageAlreadyCreated, action_root.join("message_already_created") 169 | autoload :MessageNotCreated, action_root.join("message_not_created") 170 | autoload :MessageWillNotDestroy, action_root.join("message_will_not_destroy") 171 | autoload :ReadState, action_root.join("read_state") 172 | autoload :Import, action_root.join("import") 173 | autoload :StartInstance, action_root.join("start_instance") 174 | autoload :StopInstance, action_root.join("stop_instance") 175 | autoload :Destroy, action_root.join("destroy") 176 | autoload :TimedProvision, action_root.join("timed_provision") # some plugins now expect this action to exist 177 | autoload :WarnNetworks, action_root.join("warn_networks") 178 | autoload :PrepareForwardedPortCollisionParams, action_root.join("prepare_forwarded_port_collision_params") 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/destroy.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class Destroy 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_qemu.destroying")) 11 | env[:machine].provider.driver.delete 12 | env[:machine].id = nil 13 | 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/import.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | require "open3" 3 | require "pathname" 4 | 5 | module VagrantPlugins 6 | module QEMU 7 | module Action 8 | class Import 9 | 10 | def initialize(app, env) 11 | @app = app 12 | @logger = Log4r::Logger.new("vagrant_qemu::action::import") 13 | end 14 | 15 | def call(env) 16 | image_path = Array.new 17 | if env[:machine].provider_config.image_path 18 | paths = env[:machine].provider_config.image_path 19 | paths = [paths] if !paths.kind_of?(Array) 20 | paths.each do |p| 21 | image_path.append(Pathname.new(p)) 22 | end 23 | else 24 | disks = env[:machine].box.metadata.fetch('disks', []) 25 | if disks.empty? 26 | # box v1 format 27 | image_path.append(env[:machine].box.directory.join("box.img")) 28 | else 29 | # box v2 format 30 | disks.each_with_index do |d, i| 31 | if d['path'].nil? 32 | @logger.error("Missing box image path for disk #{i}") 33 | raise Errors::BoxInvalid, name: env[:machine].name, err: "Missing box image path for disk #{i}" 34 | end 35 | image_path.append(env[:machine].box.directory.join(d['path'])) 36 | end 37 | end 38 | end 39 | 40 | if image_path.empty? 41 | @logger.error("Empty box image path") 42 | raise Errors::BoxInvalid, name: env[:machine].name, err: "Empty box image path" 43 | end 44 | image_path.each do |img| 45 | if !img.file? 46 | @logger.error("Invalid box image path: #{img}") 47 | raise Errors::BoxInvalid, name: env[:machine].name, err: "Invalid box image path: #{img}" 48 | end 49 | img_str = img.to_s 50 | stdout, stderr, status = Open3.capture3('qemu-img', 'info', '--output=json', img_str) 51 | if !status.success? 52 | @logger.error("Run qemu-img info failed, #{img_str}, out: #{stdout}, err: #{stderr}") 53 | raise Errors::BoxInvalid, name: env[:machine].name, err: "Run qemu-img info failed, #{img_str}, out: #{stdout}, err: #{stderr}" 54 | end 55 | img_info = JSON.parse(stdout) 56 | format = img_info['format'] 57 | if format != 'qcow2' 58 | @logger.error("Invalid box image format, #{img_str}, format: #{format}") 59 | raise Errors::BoxInvalid, name: env[:machine].name, err: "Invalid box image format, #{img_str}, format: #{format}" 60 | end 61 | @logger.info("Found box image path: #{img_info}") 62 | end 63 | 64 | qemu_dir = Pathname.new(env[:machine].provider_config.qemu_dir) 65 | if !qemu_dir.directory? 66 | @logger.error("Invalid qemu dir: #{qemu_dir}") 67 | raise Errors::ConfigError, err: "Invalid qemu dir: #{qemu_dir}" 68 | else 69 | @logger.info("Found qemu dir: #{qemu_dir}") 70 | end 71 | 72 | env[:ui].output("Importing a QEMU instance") 73 | 74 | options = { 75 | :image_path => image_path, 76 | :qemu_dir => qemu_dir, 77 | :arch => env[:machine].provider_config.arch, 78 | :firmware_format => env[:machine].provider_config.firmware_format, 79 | :extra_image_opts => env[:machine].provider_config.extra_image_opts, 80 | :disk_resize => env[:machine].provider_config.disk_resize, 81 | } 82 | 83 | env[:ui].detail("Creating and registering the VM...") 84 | server = env[:machine].provider.driver.import(options) 85 | 86 | env[:ui].detail("Successfully imported VM") 87 | env[:machine].id = server[:id] 88 | @app.call(env) 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/message_already_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class MessageAlreadyCreated 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_qemu.already_status", :status => "created")) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/message_not_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class MessageNotCreated 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_qemu.not_created")) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/message_will_not_destroy.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class MessageWillNotDestroy 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_qemu.will_not_destroy", name: env[:machine].name)) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/prepare_forwarded_port_collision_params.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class PrepareForwardedPortCollisionParams 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | machine = env[:machine] 11 | 12 | # TODO: not supported 13 | other_used_ports = {} 14 | env[:port_collision_extra_in_use] = other_used_ports 15 | 16 | # Build the remap for any existing collision detections 17 | remap = {} 18 | env[:port_collision_remap] = remap 19 | 20 | has_ssh_forward = false 21 | machine.config.vm.networks.each do |type, options| 22 | next if type != :forwarded_port 23 | 24 | # update ssh.host to ssh_port 25 | if options[:id] == "ssh" 26 | options[:host] = machine.provider_config.ssh_port 27 | options[:auto_correct] = machine.provider_config.ssh_auto_correct 28 | has_ssh_forward = true 29 | break 30 | end 31 | end 32 | 33 | if !has_ssh_forward 34 | machine.config.vm.network :forwarded_port, 35 | :guest => 22, 36 | :host => machine.provider_config.ssh_port, 37 | :host_ip => "127.0.0.1", 38 | :id => "ssh", 39 | :auto_correct => machine.provider_config.ssh_auto_correct, 40 | :protocol => "tcp" 41 | end 42 | 43 | @app.call(env) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/read_state.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module QEMU 5 | module Action 6 | # This action reads the state of the machine and puts it in the 7 | # `:machine_state_id` key in the environment. 8 | class ReadState 9 | def initialize(app, env) 10 | @app = app 11 | @logger = Log4r::Logger.new("vagrant_qemu::action::read_state") 12 | end 13 | 14 | def call(env) 15 | if env[:machine].id 16 | env[:machine_state_id] = env[:machine].provider.driver.get_current_state 17 | 18 | # If the machine isn't created, then our ID is stale, so just 19 | # mark it as not created. 20 | if env[:machine_state_id] == :not_created 21 | env[:machine].id = nil 22 | end 23 | else 24 | env[:machine_state_id] = :not_created 25 | end 26 | 27 | # Update ssh_port if needed 28 | if env[:machine_state_id] == :running 29 | env[:machine].provider_config.ssh_port = env[:machine].provider.driver.get_ssh_port(env[:machine].provider_config.ssh_port) 30 | end 31 | @app.call(env) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/start_instance.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module QEMU 5 | module Action 6 | # This starts a stopped instance. 7 | class StartInstance 8 | def initialize(app, env) 9 | @app = app 10 | @logger = Log4r::Logger.new("vagrant_qemu::action::start_instance") 11 | end 12 | 13 | def call(env) 14 | fwPorts = forwarded_ports(env) 15 | options = { 16 | :ssh_host => env[:machine].provider_config.ssh_host, 17 | :ssh_port => env[:machine].provider_config.ssh_port, 18 | :arch => env[:machine].provider_config.arch, 19 | :machine => env[:machine].provider_config.machine, 20 | :cpu => env[:machine].provider_config.cpu, 21 | :smp => env[:machine].provider_config.smp, 22 | :memory => env[:machine].provider_config.memory, 23 | :net_device => env[:machine].provider_config.net_device, 24 | :drive_interface => env[:machine].provider_config.drive_interface, 25 | :qemu_bin => env[:machine].provider_config.qemu_bin, 26 | :extra_qemu_args => env[:machine].provider_config.extra_qemu_args, 27 | :extra_netdev_args => env[:machine].provider_config.extra_netdev_args, 28 | :extra_drive_args => env[:machine].provider_config.extra_drive_args, 29 | :ports => fwPorts, 30 | :control_port => env[:machine].provider_config.control_port, 31 | :debug_port => env[:machine].provider_config.debug_port, 32 | :no_daemonize => env[:machine].provider_config.no_daemonize, 33 | :firmware_format => env[:machine].provider_config.firmware_format, 34 | :other_default => env[:machine].provider_config.other_default, 35 | :extra_image_opts => env[:machine].provider_config.extra_image_opts, 36 | } 37 | 38 | env[:ui].output(I18n.t("vagrant_qemu.starting")) 39 | env[:machine].provider.driver.start(options) 40 | @app.call(env) 41 | end 42 | 43 | def forwarded_ports(env) 44 | result = [] 45 | 46 | env[:machine].config.vm.networks.each do |type, options| 47 | next if type != :forwarded_port 48 | 49 | # Don't include SSH 50 | if options[:id] == "ssh" 51 | if options[:host] != env[:machine].provider_config.ssh_port 52 | env[:machine].provider_config.ssh_port = options[:host] 53 | end 54 | next 55 | end 56 | 57 | # Skip port if it is disabled 58 | next if options[:disabled] 59 | 60 | result.push("#{options[:protocol]}:#{options[:host_ip]}:#{options[:host]}-#{options[:guest_ip]}:#{options[:guest]}") 61 | end 62 | 63 | result 64 | end 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/stop_instance.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | # This stops the running instance. 5 | class StopInstance 6 | def initialize(app, env) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | options = { 12 | :control_port => env[:machine].provider_config.control_port 13 | } 14 | 15 | env[:ui].info(I18n.t("vagrant_qemu.stopping")) 16 | env[:machine].provider.driver.stop(options) 17 | @app.call(env) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/action/warn_networks.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Action 4 | class WarnNetworks 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | if env[:machine].config.vm.networks.length > 0 11 | env[:ui].warn(I18n.t("vagrant_qemu.warn_networks")) 12 | end 13 | 14 | @app.call(env) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/cap.rb: -------------------------------------------------------------------------------- 1 | 2 | module VagrantPlugins 3 | module QEMU 4 | module Cap 5 | autoload :Disk, "vagrant-qemu/cap/disk" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/cap/disk.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module QEMU 5 | module Cap 6 | module Disk 7 | @@logger = Log4r::Logger.new("vagrant_qemu::cap::disk") 8 | 9 | DEFAULT_DISK_EXT_LIST = ["qcow2", "iso"].map(&:freeze).freeze 10 | DEFAULT_DISK_EXT = "qcow2".freeze 11 | 12 | # @param [Vagrant::Machine] machine 13 | # @return [String] 14 | def self.set_default_disk_ext(machine) 15 | DEFAULT_DISK_EXT 16 | end 17 | 18 | # @param [Vagrant::Machine] machine 19 | # @return [Array] 20 | def self.default_disk_exts(machine) 21 | DEFAULT_DISK_EXT_LIST 22 | end 23 | 24 | # @param [Vagrant::Machine] machine 25 | # @param [String] disk_ext 26 | # @return [Bool] 27 | def self.validate_disk_ext(machine, disk_ext) 28 | DEFAULT_DISK_EXT_LIST.include?(disk_ext) 29 | end 30 | 31 | # @param [Vagrant::Machine] machine 32 | # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks 33 | # @return [Hash] configured_disks - A hash of all the current configured disks 34 | def self.configure_disks(machine, defined_disks) 35 | return {} if defined_disks.empty? 36 | 37 | configured_disks = {disk: [], floppy: [], dvd: []} 38 | defined_disks.each do |disk| 39 | @@logger.info("Disk: #{disk.to_yaml}") 40 | case disk.type 41 | when :disk 42 | disk_data = setup_disk(machine, disk) 43 | if !disk_data.empty? 44 | configured_disks[:disk] << disk_data 45 | machine.provider.driver.attach_disk(disk_data) 46 | end 47 | when :floppy 48 | machine.ui.info(I18n.t("vagrant_qemu.errors.floppy_unsupported")) 49 | when :dvd 50 | disk_data = setup_dvd(machine, disk) 51 | if !disk_data.empty? 52 | configured_disks[:dvd] << disk_data 53 | machine.provider.driver.attach_dvd(disk_data) 54 | end 55 | else 56 | @@logger.info("unsupported disk type: #{disk.type}") 57 | end 58 | end 59 | 60 | configured_disks 61 | end 62 | 63 | # @param [Vagrant::Machine] machine 64 | # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks 65 | # @param [Hash] disk_meta - A hash of all the previously defined disks 66 | # from the last configure_disk action 67 | # @return [nil] 68 | def self.cleanup_disks(machine, defined_disks, disk_meta) 69 | return if disk_meta.values.flatten.empty? 70 | end 71 | 72 | protected 73 | 74 | # Sets up all disk configs of type `:disk` 75 | # 76 | # @param [Vagrant::Machine] machine - the current machine 77 | # @param [Config::Disk] disk - the current disk to configure 78 | # @return [Hash] - disk_metadata 79 | def self.setup_disk(machine, disk) 80 | disk_dir = machine.provider.driver.disk_dir 81 | disk_path = disk_dir.join("#{disk.name}.#{disk.disk_ext}") 82 | args = ["create", "-f", "qcow2"] 83 | 84 | disk_provider_config = disk.provider_config[:qemu] if disk.provider_config 85 | args.push(disk_path.to_s) 86 | args.push("#{disk.size}") 87 | machine.provider.driver.execute("qemu-img", *args) 88 | 89 | {UUID: disk.id, Name: disk.name, Path: disk_path.to_s, primary: !!disk.primary} 90 | end 91 | 92 | # Sets up all disk configs of type `:dvd` 93 | # 94 | # @param [Vagrant::Machine] machine - the current machine 95 | # @param [Config::Disk] disk - the current disk to configure 96 | # @return [Hash] - disk_metadata 97 | def self.setup_dvd(machine, disk) 98 | {UUID: disk.id, Name: disk.name, Path: disk.file, primary: !!disk.primary} 99 | end 100 | 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/config.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module QEMU 5 | class Config < Vagrant.plugin("2", :config) 6 | attr_accessor :ssh_host 7 | attr_accessor :ssh_port 8 | attr_accessor :ssh_auto_correct 9 | attr_accessor :arch 10 | attr_accessor :machine 11 | attr_accessor :cpu 12 | attr_accessor :smp 13 | attr_accessor :memory 14 | attr_accessor :net_device 15 | attr_accessor :drive_interface 16 | attr_accessor :image_path 17 | attr_accessor :qemu_bin 18 | attr_accessor :qemu_dir 19 | attr_accessor :disk_resize 20 | attr_accessor :extra_qemu_args 21 | attr_accessor :extra_netdev_args 22 | attr_accessor :extra_drive_args 23 | attr_accessor :control_port 24 | attr_accessor :debug_port 25 | attr_accessor :no_daemonize 26 | attr_accessor :firmware_format 27 | attr_accessor :other_default 28 | attr_accessor :extra_image_opts 29 | 30 | def initialize 31 | @ssh_host = UNSET_VALUE 32 | @ssh_port = UNSET_VALUE 33 | @ssh_auto_correct = UNSET_VALUE 34 | @arch = UNSET_VALUE 35 | @machine = UNSET_VALUE 36 | @cpu = UNSET_VALUE 37 | @smp = UNSET_VALUE 38 | @memory = UNSET_VALUE 39 | @net_device = UNSET_VALUE 40 | @drive_interface = UNSET_VALUE 41 | @image_path = UNSET_VALUE 42 | @qemu_bin = UNSET_VALUE 43 | @qemu_dir = UNSET_VALUE 44 | @disk_resize = UNSET_VALUE 45 | @extra_qemu_args = UNSET_VALUE 46 | @extra_netdev_args = UNSET_VALUE 47 | @extra_drive_args = UNSET_VALUE 48 | @control_port = UNSET_VALUE 49 | @debug_port = UNSET_VALUE 50 | @no_daemonize = UNSET_VALUE 51 | @firmware_format = UNSET_VALUE 52 | @other_default = UNSET_VALUE 53 | @extra_image_opts = UNSET_VALUE 54 | end 55 | 56 | #------------------------------------------------------------------- 57 | # Internal methods. 58 | #------------------------------------------------------------------- 59 | 60 | def merge(other) 61 | super.tap do |result| 62 | end 63 | end 64 | 65 | def finalize! 66 | @ssh_host = "127.0.0.1" if @ssh_host == UNSET_VALUE 67 | @ssh_port = 50022 if @ssh_port == UNSET_VALUE 68 | @ssh_auto_correct = false if @ssh_auto_correct == UNSET_VALUE 69 | @arch = "aarch64" if @arch == UNSET_VALUE 70 | @machine = "virt,accel=hvf,highmem=on" if @machine == UNSET_VALUE 71 | @cpu = "host" if @cpu == UNSET_VALUE 72 | @smp = "2" if @smp == UNSET_VALUE 73 | @memory = "4G" if @memory == UNSET_VALUE 74 | @net_device = "virtio-net-device" if @net_device == UNSET_VALUE 75 | @drive_interface = "virtio" if @drive_interface == UNSET_VALUE 76 | @image_path = nil if @image_path == UNSET_VALUE 77 | @qemu_bin = nil if @qemu_bin == UNSET_VALUE 78 | @qemu_dir = "/opt/homebrew/share/qemu" if @qemu_dir == UNSET_VALUE 79 | @disk_resize = nil if @disk_resize == UNSET_VALUE 80 | @extra_qemu_args = [] if @extra_qemu_args == UNSET_VALUE 81 | @extra_netdev_args = nil if @extra_netdev_args == UNSET_VALUE 82 | @extra_drive_args = nil if @extra_drive_args == UNSET_VALUE 83 | @control_port = nil if @control_port == UNSET_VALUE 84 | @debug_port = nil if @debug_port == UNSET_VALUE 85 | @no_daemonize = false if @no_daemonize == UNSET_VALUE 86 | @firmware_format = "raw" if @firmware_format == UNSET_VALUE 87 | @other_default = %W(-parallel null -monitor none -display none -vga none) if @other_default == UNSET_VALUE 88 | @extra_image_opts = nil if @extra_image_opts == UNSET_VALUE 89 | 90 | # TODO better error msg 91 | @ssh_port = Integer(@ssh_port) 92 | end 93 | 94 | def validate(machine) 95 | # errors = _detected_errors 96 | errors = [] 97 | { "QEMU Provider" => errors } 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/driver.rb: -------------------------------------------------------------------------------- 1 | require 'log4r' 2 | require 'childprocess' 3 | require 'securerandom' 4 | require 'yaml' 5 | 6 | require "vagrant/util/busy" 7 | require 'vagrant/util/io' 8 | require "vagrant/util/safe_chdir" 9 | require "vagrant/util/subprocess" 10 | 11 | require_relative "plugin" 12 | 13 | module VagrantPlugins 14 | module QEMU 15 | class Driver 16 | # @return [String] VM ID 17 | attr_reader :vm_id 18 | attr_reader :data_dir 19 | attr_reader :tmp_dir 20 | attr_reader :attached_drives 21 | 22 | def initialize(id, dir, tmp) 23 | @vm_id = id 24 | @data_dir = dir 25 | @tmp_dir = tmp.join("vagrant-qemu") 26 | @attached_drives = {disk: [], floppy: [], dvd: []} 27 | @logger = Log4r::Logger.new("vagrant_qemu::driver") 28 | end 29 | 30 | def get_current_state 31 | case 32 | when running? 33 | :running 34 | when created? 35 | :stopped 36 | else 37 | :not_created 38 | end 39 | end 40 | 41 | def delete 42 | if created? 43 | id_dir = @data_dir.join(@vm_id) 44 | FileUtils.rm_rf(id_dir) 45 | id_tmp_dir = @tmp_dir.join(@vm_id) 46 | FileUtils.rm_rf(id_tmp_dir) 47 | end 48 | end 49 | 50 | def start(options) 51 | if !running? 52 | id_dir = @data_dir.join(@vm_id) 53 | 54 | image_path = Array.new 55 | image_count = id_dir.glob("linked-box*.img").count 56 | for i in 0..image_count-1 do 57 | suffix_index = i > 0 ? "-#{i}" : '' 58 | image_path.append(id_dir.join("linked-box#{suffix_index}.img").to_s) 59 | end 60 | 61 | id_tmp_dir = @tmp_dir.join(@vm_id) 62 | FileUtils.mkdir_p(id_tmp_dir) 63 | 64 | # dump options 65 | options_file = id_tmp_dir.join("options.yml") 66 | File.write(options_file, options.to_yaml) 67 | 68 | control_socket = "" 69 | if !options[:control_port].nil? 70 | control_socket = "port=#{options[:control_port]},host=localhost,ipv4=on" 71 | else 72 | unix_socket_path = id_tmp_dir.join("qemu_socket").to_s 73 | control_socket = "path=#{unix_socket_path}" 74 | end 75 | 76 | debug_socket = "" 77 | if !options[:debug_port].nil? 78 | debug_socket = "port=#{options[:debug_port]},host=localhost,ipv4=on" 79 | else 80 | unix_socket_serial_path = id_tmp_dir.join("qemu_socket_serial").to_s 81 | debug_socket = "path=#{unix_socket_serial_path}" 82 | end 83 | 84 | cmd = [] 85 | if options[:qemu_bin].nil? 86 | cmd += %W(qemu-system-#{options[:arch]}) 87 | else 88 | if options[:qemu_bin].kind_of?(Array) 89 | cmd += options[:qemu_bin] 90 | else 91 | cmd += %W(#{options[:qemu_bin]}) 92 | end 93 | end 94 | 95 | # basic 96 | cmd += %W(-machine #{options[:machine]}) if !options[:machine].nil? 97 | cmd += %W(-cpu #{options[:cpu]}) if !options[:cpu].nil? 98 | cmd += %W(-smp #{options[:smp]}) if !options[:smp].nil? 99 | cmd += %W(-m #{options[:memory]}) if !options[:memory].nil? 100 | 101 | # network 102 | if !options[:net_device].nil? 103 | # net device 104 | cmd += %W(-device #{options[:net_device]},netdev=net0) 105 | 106 | # ports 107 | hostfwd = "hostfwd=tcp::#{options[:ssh_port]}-:22" 108 | options[:ports].each do |v| 109 | hostfwd += ",hostfwd=#{v}" 110 | end 111 | extra_netdev = "" 112 | if !options[:extra_netdev_args].nil? 113 | extra_netdev = ",#{options[:extra_netdev_args]}" 114 | end 115 | cmd += %W(-netdev user,id=net0,#{hostfwd}#{extra_netdev}) 116 | end 117 | 118 | # drive 119 | diskid = 0 120 | extra_drive_args = "" 121 | if !options[:extra_drive_args].nil? 122 | extra_drive_args = ",#{options[:extra_drive_args]}" 123 | end 124 | 125 | if !options[:drive_interface].nil? 126 | image_path.each do |img| 127 | cmd += %W(-drive if=#{options[:drive_interface]},id=disk#{diskid},format=qcow2,file=#{img}#{extra_drive_args}) 128 | diskid += 1 129 | end 130 | end 131 | if options[:arch] == "aarch64" && !options[:firmware_format].nil? 132 | fm1_path = id_dir.join("edk2-aarch64-code.fd").to_s 133 | fm2_path = id_dir.join("edk2-arm-vars.fd").to_s 134 | cmd += %W(-drive if=pflash,format=#{options[:firmware_format]},file=#{fm1_path},readonly=on) 135 | cmd += %W(-drive if=pflash,format=#{options[:firmware_format]},file=#{fm2_path}) 136 | end 137 | 138 | dvd_index = 1 139 | @attached_drives[:dvd].each do |disk| 140 | cmd += %W(-drive file=#{disk[:Path]},index=#{dvd_index},media=cdrom) 141 | dvd_index += 1 142 | end 143 | if !options[:drive_interface].nil? 144 | @attached_drives[:disk].each do |disk| 145 | cmd += %W(-drive if=#{options[:drive_interface]},id=disk#{diskid},format=qcow2,file=#{disk[:Path]}#{extra_drive_args}) 146 | diskid += 1 147 | end 148 | end 149 | 150 | # control 151 | pid_file = id_tmp_dir.join("qemu.pid").to_s 152 | cmd += %W(-chardev socket,id=mon0,#{control_socket},server=on,wait=off) 153 | cmd += %W(-mon chardev=mon0,mode=readline) 154 | cmd += %W(-chardev socket,id=ser0,#{debug_socket},server=on,wait=off) 155 | cmd += %W(-serial chardev:ser0) 156 | cmd += %W(-pidfile #{pid_file}) 157 | if !options[:no_daemonize] 158 | cmd += %W(-daemonize) 159 | end 160 | 161 | # other default 162 | cmd += options[:other_default] 163 | 164 | # user-defined 165 | cmd += options[:extra_qemu_args] 166 | 167 | opts = {:detach => options[:no_daemonize]} 168 | execute(*cmd, **opts) 169 | end 170 | end 171 | 172 | def stop(options) 173 | if running? 174 | if !options[:control_port].nil? 175 | Socket.tcp("localhost", options[:control_port], connect_timeout: 5) do |sock| 176 | sock.print "system_powerdown\n" 177 | sock.close_write 178 | sock.read rescue nil 179 | end 180 | else 181 | id_tmp_dir = @tmp_dir.join(@vm_id) 182 | unix_socket_path = id_tmp_dir.join("qemu_socket").to_s 183 | Socket.unix(unix_socket_path) do |sock| 184 | sock.print "system_powerdown\n" 185 | sock.close_write 186 | sock.read rescue nil 187 | end 188 | end 189 | end 190 | end 191 | 192 | def get_ssh_port(ssh_port) 193 | id_tmp_dir = @tmp_dir.join(@vm_id) 194 | options_file = id_tmp_dir.join("options.yml") 195 | 196 | if options_file.file? 197 | options = YAML.load_file(options_file) rescue nil 198 | ssh_port = options[:ssh_port] if !options.nil? && options.key?(:ssh_port) 199 | end 200 | 201 | ssh_port 202 | end 203 | 204 | def import(options) 205 | new_id = "vq_" + SecureRandom.urlsafe_base64(8) 206 | 207 | # Make dir 208 | id_dir = @data_dir.join(new_id) 209 | FileUtils.mkdir_p(id_dir) 210 | id_tmp_dir = @tmp_dir.join(new_id) 211 | FileUtils.mkdir_p(id_tmp_dir) 212 | 213 | # Prepare firmware 214 | if options[:arch] == "aarch64" && !options[:firmware_format].nil? 215 | execute("cp", options[:qemu_dir].join("edk2-aarch64-code.fd").to_s, id_dir.join("edk2-aarch64-code.fd").to_s) 216 | execute("cp", options[:qemu_dir].join("edk2-arm-vars.fd").to_s, id_dir.join("edk2-arm-vars.fd").to_s) 217 | execute("chmod", "644", id_dir.join("edk2-arm-vars.fd").to_s) 218 | end 219 | 220 | # Create image 221 | options[:image_path].each_with_index do |img, i| 222 | suffix_index = i > 0 ? "-#{i}" : '' 223 | 224 | linked_image = id_dir.join("linked-box#{suffix_index}.img").to_s 225 | args = ["create", "-f", "qcow2", "-F", "qcow2", "-b", img.to_s] 226 | 227 | if !options[:extra_image_opts].nil? 228 | options[:extra_image_opts].each do |opt| 229 | args.push("-o") 230 | args.push(opt) 231 | end 232 | end 233 | 234 | args.push(linked_image) 235 | 236 | if i == 0 237 | if !options[:disk_resize].nil? 238 | args.push(options[:disk_resize]) 239 | end 240 | end 241 | 242 | execute("qemu-img", *args) 243 | end 244 | 245 | server = { 246 | :id => new_id, 247 | } 248 | end 249 | 250 | def created? 251 | result = @data_dir.join(@vm_id).directory? 252 | end 253 | 254 | def running? 255 | pid_file = @tmp_dir.join(@vm_id).join("qemu.pid") 256 | return false if !pid_file.file? 257 | 258 | begin 259 | Process.kill(0, File.read(pid_file).to_i) 260 | true 261 | rescue Errno::ESRCH 262 | false 263 | end 264 | end 265 | 266 | def execute(*cmd, **opts, &block) 267 | result = nil 268 | 269 | if opts && opts[:detach] 270 | # give it some time to startup 271 | timeout = 5 272 | 273 | # edit version of "Subprocess.execute" for detach 274 | workdir = Dir.pwd 275 | process = ChildProcess.build(*cmd) 276 | 277 | stdout, stdout_writer = ::IO.pipe 278 | stderr, stderr_writer = ::IO.pipe 279 | process.io.stdout = stdout_writer 280 | process.io.stderr = stderr_writer 281 | 282 | process.leader = true 283 | process.detach = true 284 | 285 | ::Vagrant::Util::SafeChdir.safe_chdir(workdir) do 286 | process.start 287 | end 288 | 289 | if RUBY_PLATFORM != "java" 290 | stdout_writer.close 291 | stderr_writer.close 292 | end 293 | 294 | io_data = { stdout: "", stderr: "" } 295 | start_time = Time.now.to_i 296 | open_readers = [stdout, stderr] 297 | 298 | while true 299 | results = ::IO.select(open_readers, nil, nil, 0.1) 300 | results ||= [] 301 | readers = results[0] 302 | 303 | # Check if we have exceeded our timeout 304 | return if (Time.now.to_i - start_time) > timeout 305 | 306 | if readers && !readers.empty? 307 | readers.each do |r| 308 | data = ::Vagrant::Util::IO.read_until_block(r) 309 | next if data.empty? 310 | 311 | io_name = r == stdout ? :stdout : :stderr 312 | io_data[io_name] += data 313 | end 314 | end 315 | 316 | break if process.exited? 317 | end 318 | 319 | if RUBY_PLATFORM == "java" 320 | stdout_writer.close 321 | stderr_writer.close 322 | end 323 | 324 | result = ::Vagrant::Util::Subprocess::Result.new(process.exit_code, io_data[:stdout], io_data[:stderr]) 325 | else 326 | # Append in the options for subprocess 327 | cmd << { notify: [:stdout, :stderr, :stdin] } 328 | 329 | interrupted = false 330 | int_callback = ->{ interrupted = true } 331 | result = ::Vagrant::Util::Busy.busy(int_callback) do 332 | ::Vagrant::Util::Subprocess.execute(*cmd, &block) 333 | end 334 | end 335 | 336 | result.stderr.gsub!("\r\n", "\n") 337 | result.stdout.gsub!("\r\n", "\n") 338 | 339 | if result.exit_code != 0 && !interrupted 340 | raise Errors::ExecuteError, 341 | command: cmd.inspect, 342 | stderr: result.stderr, 343 | stdout: result.stdout 344 | end 345 | 346 | if opts 347 | if opts[:with_stderr] 348 | return result.stdout + " " + result.stderr 349 | else 350 | return result.stdout 351 | end 352 | end 353 | end 354 | 355 | def attach_dvd(disk) 356 | @attached_drives[:dvd] << disk 357 | end 358 | 359 | def attach_disk(disk) 360 | @attached_drives[:disk] << disk 361 | end 362 | 363 | def disk_dir 364 | @data_dir.join(@vm_id) 365 | end 366 | end 367 | end 368 | end 369 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/errors.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module QEMU 5 | module Errors 6 | class VagrantQEMUError < Vagrant::Errors::VagrantError 7 | error_namespace("vagrant_qemu.errors") 8 | end 9 | 10 | class RsyncError < VagrantQEMUError 11 | error_key(:rsync_error) 12 | end 13 | 14 | class MkdirError < VagrantQEMUError 15 | error_key(:mkdir_error) 16 | end 17 | 18 | class NotSupportedError < VagrantQEMUError 19 | error_key(:not_supported) 20 | end 21 | 22 | class BoxInvalid < VagrantQEMUError 23 | error_key(:box_invalid) 24 | end 25 | 26 | class ExecuteError < VagrantQEMUError 27 | error_key(:execute_error) 28 | end 29 | 30 | class ConfigError < VagrantQEMUError 31 | error_key(:config_error) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/plugin.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "vagrant" 3 | rescue LoadError 4 | raise "The Vagrant QEMU plugin must be run within Vagrant." 5 | end 6 | 7 | # This is a sanity check to make sure no one is attempting to install 8 | # this into an early Vagrant version. 9 | if Vagrant::VERSION < "1.2.0" 10 | raise "The Vagrant QEMU plugin is only compatible with Vagrant 1.2+" 11 | end 12 | 13 | module VagrantPlugins 14 | module QEMU 15 | class Plugin < Vagrant.plugin("2") 16 | name "QEMU" 17 | description <<-DESC 18 | This plugin installs a provider that allows Vagrant to manage 19 | machines in QEMU. 20 | DESC 21 | 22 | config(:qemu, :provider) do 23 | require_relative "config" 24 | Config 25 | end 26 | 27 | provider_capability(:qemu, :set_default_disk_ext) do 28 | require File.expand_path("../cap/disk", __FILE__) 29 | Cap::Disk 30 | end 31 | 32 | provider_capability(:qemu, :default_disk_exts) do 33 | require File.expand_path("../cap/disk", __FILE__) 34 | Cap::Disk 35 | end 36 | 37 | provider_capability(:qemu, :configure_disks) do 38 | require File.expand_path("../cap/disk", __FILE__) 39 | Cap::Disk 40 | end 41 | 42 | provider_capability(:qemu, :cleanup_disks) do 43 | require File.expand_path("../cap/disk", __FILE__) 44 | Cap::Disk 45 | end 46 | 47 | provider_capability(:qemu, :validate_disk_ext) do 48 | require File.expand_path("../cap/disk", __FILE__) 49 | Cap::Disk 50 | end 51 | 52 | provider(:qemu, box_format: "libvirt", box_optional: true, parallel: true) do 53 | # Setup logging and i18n 54 | setup_logging 55 | setup_i18n 56 | 57 | # Return the provider 58 | require_relative "provider" 59 | Provider 60 | end 61 | 62 | # This initializes the internationalization strings. 63 | def self.setup_i18n 64 | I18n.load_path << File.expand_path("locales/en.yml", QEMU.source_root) 65 | I18n.reload! 66 | end 67 | 68 | # This sets up our log level to be whatever VAGRANT_LOG is. 69 | def self.setup_logging 70 | require "log4r" 71 | 72 | level = nil 73 | begin 74 | level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) 75 | rescue NameError 76 | # This means that the logging constant wasn't found, 77 | # which is fine. We just keep `level` as `nil`. But 78 | # we tell the user. 79 | level = nil 80 | end 81 | 82 | # Some constants, such as "true" resolve to booleans, so the 83 | # above error checking doesn't catch it. This will check to make 84 | # sure that the log level is an integer, as Log4r requires. 85 | level = nil if !level.is_a?(Integer) 86 | 87 | # Set the logging level on all "vagrant" namespaced 88 | # logs as long as we have a valid level. 89 | if level 90 | logger = Log4r::Logger.new("vagrant_qemu") 91 | logger.outputters = Log4r::Outputter.stderr 92 | logger.level = level 93 | logger = nil 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/provider.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | require "vagrant" 3 | 4 | require_relative "driver" 5 | 6 | module VagrantPlugins 7 | module QEMU 8 | class Provider < Vagrant.plugin("2", :provider) 9 | attr_reader :driver 10 | 11 | def initialize(machine) 12 | @machine = machine 13 | 14 | # TODO support NFS 15 | @machine.config.nfs.functional = false 16 | 17 | # This method will load in our driver, so we call it now to 18 | # initialize it. 19 | machine_id_changed 20 | end 21 | 22 | def action(name) 23 | # Attempt to get the action method from the Action class if it 24 | # exists, otherwise return nil to show that we don't support the 25 | # given action. 26 | action_method = "action_#{name}" 27 | return Action.send(action_method) if Action.respond_to?(action_method) 28 | nil 29 | end 30 | 31 | def machine_id_changed 32 | @driver = Driver.new(@machine.id, @machine.data_dir, @machine.env.tmp_path) 33 | end 34 | 35 | def ssh_info 36 | # If the VM is not running that we can't possibly SSH into it 37 | return nil if state.id != :running 38 | 39 | return { 40 | host: @machine.provider_config.ssh_host, 41 | port: @machine.provider_config.ssh_port 42 | } 43 | end 44 | 45 | def state 46 | state_id = nil 47 | state_id = :not_created if !@machine.id 48 | 49 | if !state_id 50 | # Run a custom action we define called "read_state" which does 51 | # what it says. It puts the state in the `:machine_state_id` 52 | # key in the environment. 53 | env = @machine.action(:read_state) 54 | state_id = env[:machine_state_id] 55 | end 56 | 57 | # Get the short and long description 58 | short = state_id.to_s 59 | long = "" 60 | 61 | # If we're not created, then specify the special ID flag 62 | if state_id == :not_created 63 | state_id = Vagrant::MachineState::NOT_CREATED_ID 64 | end 65 | 66 | # Return the MachineState object 67 | Vagrant::MachineState.new(state_id, short, long) 68 | end 69 | 70 | def to_s 71 | id = @machine.id.nil? ? "new" : @machine.id 72 | "QEMU (#{id})" 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/util/timer.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | module Util 4 | class Timer 5 | # A basic utility method that times the execution of the given 6 | # block and returns it. 7 | def self.time 8 | start_time = Time.now.to_f 9 | yield 10 | end_time = Time.now.to_f 11 | 12 | end_time - start_time 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-qemu/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module QEMU 3 | VERSION = '0.3.12' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_qemu: 3 | already_status: |- 4 | The machine is already %{status}. 5 | not_created: |- 6 | Instance is not created. Please run `vagrant up` first. 7 | rsync_not_found_warning: |- 8 | Warning! Folder sync disabled because the rsync binary is missing in the %{side}. 9 | Make sure rsync is installed and the binary can be found in the PATH. 10 | rsync_folder: |- 11 | Rsyncing folder: %{hostpath} => %{guestpath} 12 | starting: |- 13 | Starting the instance... 14 | stopping: |- 15 | Stopping the instance... 16 | destroying: |- 17 | Destroying the instance... 18 | warn_networks: |- 19 | Warning! The QEMU provider doesn't support any of the Vagrant 20 | high-level network configurations (`config.vm.network`). They 21 | will be silently ignored. 22 | will_not_destroy: |- 23 | The instance '%{name}' will not be destroyed, since the confirmation 24 | was declined. 25 | 26 | errors: 27 | not_supported: |- 28 | Function not supported. 29 | rsync_error: |- 30 | There was an error when attempting to rsync a shared folder. 31 | Please inspect the error message below for more info. 32 | 33 | Host path: %{hostpath} 34 | Guest path: %{guestpath} 35 | Error: %{stderr} 36 | mkdir_error: |- 37 | There was an error when attempting to create a shared host folder. 38 | Please inspect the error message below for more info. 39 | 40 | Host path: %{hostpath} 41 | Error: %{err} 42 | box_invalid: |- 43 | The box you're using with the QEMU provider ('%{name}') 44 | is invalid. 45 | 46 | Error: %{err} 47 | execute_error: |- 48 | A command executed by Vagrant didn't complete successfully! 49 | The command run along with the output from the command is shown 50 | below. 51 | 52 | Command: %{command} 53 | 54 | Stderr: %{stderr} 55 | 56 | Stdout: %{stdout} 57 | config_error: |- 58 | Invalid config. 59 | 60 | Error: %{err} 61 | floppy_unsupported: |- 62 | Floppy disks not supported 63 | -------------------------------------------------------------------------------- /vagrant-qemu.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../lib", __FILE__) 2 | require "vagrant-qemu/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "vagrant-qemu" 6 | s.version = VagrantPlugins::QEMU::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.license = "MIT" 9 | s.authors = "ppggff" 10 | s.email = "pgf00a@gmail.com" 11 | s.homepage = "https://github.com/ppggff/vagrant-qemu" 12 | s.summary = "Enables Vagrant to manage machines with QEMU." 13 | s.description = "Enables Vagrant to manage machines with QEMU." 14 | 15 | s.required_rubygems_version = ">= 1.3.6" 16 | s.rubyforge_project = "vagrant-qemu" 17 | 18 | # The following block of code determines the files that should be included 19 | # in the gem. It does this by reading all the files in the directory where 20 | # this gemspec is, and parsing out the ignored files from the gitignore. 21 | # Note that the entire gitignore(5) syntax is not supported, specifically 22 | # the "!" syntax, but it should mostly work correctly. 23 | root_path = File.dirname(__FILE__) 24 | all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") } 25 | all_files.reject! { |file| [".", ".."].include?(File.basename(file)) } 26 | gitignore_path = File.join(root_path, ".gitignore") 27 | gitignore = File.readlines(gitignore_path) 28 | gitignore.map! { |line| line.chomp.strip } 29 | gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ } 30 | 31 | unignored_files = all_files.reject do |file| 32 | # Ignore any directories, the gemspec only cares about files 33 | next true if File.directory?(file) 34 | 35 | # Ignore any paths that match anything in the gitignore. We do 36 | # two tests here: 37 | # 38 | # - First, test to see if the entire path matches the gitignore. 39 | # - Second, match if the basename does, this makes it so that things 40 | # like '.DS_Store' will match sub-directories too (same behavior 41 | # as git). 42 | # 43 | gitignore.any? do |ignore| 44 | File.fnmatch(ignore, file, File::FNM_PATHNAME|File::FNM_DOTMATCH) || 45 | File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME) 46 | end 47 | end 48 | 49 | s.files = unignored_files 50 | s.executables = unignored_files.map { |f| f[/^bin\/(.*)/, 1] }.compact 51 | s.require_path = 'lib' 52 | end 53 | --------------------------------------------------------------------------------