vagrant-qemu 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 56d15ffaaea610e07a3d494ef9935dc75469989a5c2b586106fcd9b0c75e41c4
4
+ data.tar.gz: d8ce98156eeff8fe8692ece6c0254db233bfcc3ea136dbd767b9acdd3f858f6d
5
+ SHA512:
6
+ metadata.gz: 7845ce71e9f0403ff35ebe4def750fef772a7736fb225262a71005fb1a944792b2bcc2ccec7f789b11b82a2b243f5b65bb833967155144bb7421ccb09d7730e3
7
+ data.tar.gz: 82272e1a35fa6ee973468040784d01d33a1d3354da89a784119c44350ace75161c9974d1de5f3ebd63247abe673232044f37b7ce203d3a723016797a1e78d7fd
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
+
15
+ # Vagrant
16
+ .vagrant
17
+ Vagrantfile
18
+ !example_box/Vagrantfile
19
+
20
+ # RVM files for gemset/ruby setting
21
+ .ruby-*
22
+ .rvmrc
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
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.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
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
data/LICENSE ADDED
@@ -0,0 +1,8 @@
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.
data/README.md ADDED
@@ -0,0 +1,128 @@
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 aarch64 image only**
7
+
8
+ ## Features
9
+
10
+ * Import from a Libvirt vagrant box or qcow2 image
11
+ * Start VM without GUI
12
+ * SSH into VM
13
+ * Provision the instances with any built-in Vagrant provisioner
14
+ * Synced folder support via SMB
15
+ * Basic operation: up, ssh, halt, destroy
16
+
17
+ ## Usage
18
+
19
+ Make sure QEMU is installed, if not:
20
+
21
+ ```
22
+ brew install qemu
23
+ ```
24
+
25
+ Install plugin:
26
+
27
+ ```
28
+ vagrant plugin install vagrant-qemu-x.x.x.gem
29
+ ```
30
+
31
+ Prepare a `Vagrantfile`, and start:
32
+
33
+ ```
34
+ vagrant up --provider qemu
35
+ ```
36
+
37
+ Notes:
38
+ * may need password to setup SMB on Mac,
39
+ see [vagrant doc](https://www.vagrantup.com/docs/synced-folders/smb) for details
40
+ * need username/password to access shared folder
41
+
42
+ ## Box format
43
+
44
+ Same as [vagrant-libvirt version-1](https://github.com/vagrant-libvirt/vagrant-libvirt#version-1):
45
+
46
+ * qcow2 image file named `box.img`
47
+ * `metadata.json` file describing box image (provider, virtual_size, format)
48
+ * `Vagrantfile` that does default settings
49
+
50
+ ## Configuration
51
+
52
+ This provider exposes a few provider-specific configuration options:
53
+
54
+ * `ssh_port` - The SSH port number used to access VM (IP is 127.0.0.1),
55
+ default: `50022`
56
+ * `arch` - The architecture of VM, default: `aarch64`
57
+ * `machine` - The machine type of VM, default: `virt,accel=hvf,highmem=off`
58
+ * `cpu` - The cpu model of VM, default: `cortex-a72`
59
+ * `smp` - The smp setting (Simulate an SMP system with n CPUs) of VM, default: `2`
60
+ * `memory` - The memory setting of VM, default: `4G`
61
+ * `image_path` - The path to qcow2 image for box-less VM, default is nil value
62
+ * `qemu_dir` - The path to QEMU's install dir, default: `/opt/homebrew/share/qemu`
63
+
64
+ These can be set like typical provider-specific configuration:
65
+
66
+ ```
67
+ # Basic Vagrant config (API version 2)
68
+ Vagrant.configure(2) do |config|
69
+ # ... other stuff
70
+
71
+ config.vm.provider "qemu" do |qe|
72
+ qe.memory = "8G"
73
+ end
74
+ end
75
+ ```
76
+
77
+ ## Example
78
+
79
+ 1. With a local box
80
+
81
+ ```
82
+ # Basic Vagrant config (API version 2)
83
+ Vagrant.configure(2) do |config|
84
+ config.vm.box = "test-box"
85
+ config.vm.box_url = "file:///Users/xxx/test.box"
86
+ config.vm.box_check_update = false
87
+ end
88
+ ```
89
+
90
+ 2. With a local qcow2
91
+
92
+ ```
93
+ # Basic Vagrant config (API version 2)
94
+ Vagrant.configure(2) do |config|
95
+ config.vm.provider "qemu" do |qe, override|
96
+ override.ssh.username = "xxx"
97
+ override.ssh.password = "vagrant"
98
+
99
+ qe.image_path = "/Users/xxx/test.qcow2"
100
+ end
101
+ end
102
+ ```
103
+
104
+ ## Build
105
+
106
+ To build the `vagrant-qemu` plugin, clone this repository out, and use
107
+ [Bundler](http://gembundler.com) to get the dependencies:
108
+
109
+ ```
110
+ bundle
111
+ ```
112
+
113
+ Once you have the dependencies, build with `rake`:
114
+
115
+ ```
116
+ bundle exec rake build
117
+ ```
118
+
119
+ ## TODO
120
+
121
+ * Support example image
122
+ * Support NFS shared folder
123
+ * Support package VM to box
124
+ * Test on more architectures
125
+ * More configures
126
+ * Better error messages
127
+ * Network
128
+ * GUI mode
data/Rakefile ADDED
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,54 @@
1
+ require "log4r"
2
+ require "pathname"
3
+
4
+ module VagrantPlugins
5
+ module QEMU
6
+ module Action
7
+ class Import
8
+
9
+ def initialize(app, env)
10
+ @app = app
11
+ @logger = Log4r::Logger.new("vagrant_qemu::action::import")
12
+ end
13
+
14
+ def call(env)
15
+ image_path = nil
16
+ if env[:machine].provider_config.image_path
17
+ image_path = Pathname.new(env[:machine].provider_config.image_path)
18
+ elsif env[:machine].box
19
+ image_path = env[:machine].box.directory.join("box.img")
20
+ end
21
+
22
+ if !image_path || !image_path.file?
23
+ @logger.error("Invalid box image path: #{image_path}")
24
+ raise Errors::BoxInvalid, name: env[:machine].name
25
+ else
26
+ @logger.info("Found box image path: #{image_path}")
27
+ end
28
+
29
+ qemu_dir = Pathname.new(env[:machine].provider_config.qemu_dir)
30
+ if !qemu_dir.directory?
31
+ @logger.error("Invalid qemu dir: #{qemu_dir}")
32
+ raise Errors::BoxInvalid, name: env[:machine].name
33
+ else
34
+ @logger.info("Found qemu dir: #{qemu_dir}")
35
+ end
36
+
37
+ env[:ui].output("Importing a QEMU instance")
38
+
39
+ options = {
40
+ :image_path => image_path,
41
+ :qemu_dir => qemu_dir,
42
+ }
43
+
44
+ env[:ui].detail("Creating and registering the VM...")
45
+ server = env[:machine].provider.driver.import(options)
46
+
47
+ env[:ui].detail("Successfully imported VM")
48
+ env[:machine].id = server[:id]
49
+ @app.call(env)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,31 @@
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
+ @app.call(env)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
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
+ options = {
15
+ :ssh_port => env[:machine].provider_config.ssh_port,
16
+ :arch => env[:machine].provider_config.arch,
17
+ :machine => env[:machine].provider_config.machine,
18
+ :cpu => env[:machine].provider_config.cpu,
19
+ :smp => env[:machine].provider_config.smp,
20
+ :memory => env[:machine].provider_config.memory,
21
+ }
22
+
23
+ env[:ui].output(I18n.t("vagrant_qemu.starting"))
24
+ env[:machine].provider.driver.start(options)
25
+ @app.call(env)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
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
+ env[:ui].info(I18n.t("vagrant_qemu.stopping"))
12
+ env[:machine].provider.driver.stop
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,174 @@
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 Provision
119
+ b1.use SyncedFolderCleanup
120
+ b1.use SyncedFolders
121
+ b1.use WarnNetworks
122
+ b1.use SetHostname
123
+ b1.use StartInstance
124
+ b1.use WaitForCommunicator, [:running]
125
+ end
126
+ end
127
+ end
128
+
129
+ # This action is called to bring the box up from nothing.
130
+ def self.action_up
131
+ Vagrant::Action::Builder.new.tap do |b|
132
+ b.use HandleBox
133
+ b.use ConfigValidate
134
+ b.use BoxCheckOutdated
135
+ b.use Call, IsState, :not_created do |env1, b1|
136
+ if env1[:result]
137
+ b1.use Import
138
+ end
139
+
140
+ b1.use action_start
141
+ end
142
+ end
143
+ end
144
+
145
+ def self.action_reload
146
+ Vagrant::Action::Builder.new.tap do |b|
147
+ b.use ConfigValidate
148
+ b.use Call, IsState, :not_created do |env, b2|
149
+ if env[:result]
150
+ b2.use MessageNotCreated
151
+ next
152
+ end
153
+
154
+ b2.use action_halt
155
+ b2.use action_start
156
+ end
157
+ end
158
+ end
159
+
160
+ # The autoload farm
161
+ action_root = Pathname.new(File.expand_path("../action", __FILE__))
162
+ autoload :MessageAlreadyCreated, action_root.join("message_already_created")
163
+ autoload :MessageNotCreated, action_root.join("message_not_created")
164
+ autoload :MessageWillNotDestroy, action_root.join("message_will_not_destroy")
165
+ autoload :ReadState, action_root.join("read_state")
166
+ autoload :Import, action_root.join("import")
167
+ autoload :StartInstance, action_root.join("start_instance")
168
+ autoload :StopInstance, action_root.join("stop_instance")
169
+ autoload :Destroy, action_root.join("destroy")
170
+ autoload :TimedProvision, action_root.join("timed_provision") # some plugins now expect this action to exist
171
+ autoload :WarnNetworks, action_root.join("warn_networks")
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,53 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module QEMU
5
+ class Config < Vagrant.plugin("2", :config)
6
+ attr_accessor :ssh_port
7
+ attr_accessor :arch
8
+ attr_accessor :machine
9
+ attr_accessor :cpu
10
+ attr_accessor :smp
11
+ attr_accessor :memory
12
+ attr_accessor :image_path
13
+ attr_accessor :qemu_dir
14
+
15
+ def initialize
16
+ @ssh_port = UNSET_VALUE
17
+ @arch = UNSET_VALUE
18
+ @machine = UNSET_VALUE
19
+ @cpu = UNSET_VALUE
20
+ @smp = UNSET_VALUE
21
+ @memory = UNSET_VALUE
22
+ @image_path = UNSET_VALUE
23
+ @qemu_dir = UNSET_VALUE
24
+ end
25
+
26
+ #-------------------------------------------------------------------
27
+ # Internal methods.
28
+ #-------------------------------------------------------------------
29
+
30
+ def merge(other)
31
+ super.tap do |result|
32
+ end
33
+ end
34
+
35
+ def finalize!
36
+ @ssh_port = 50022 if @ssh_port == UNSET_VALUE
37
+ @arch = "aarch64" if @arch == UNSET_VALUE
38
+ @machine = "virt,accel=hvf,highmem=off" if @machine == UNSET_VALUE
39
+ @cpu = "cortex-a72" if @cpu == UNSET_VALUE
40
+ @smp = "2" if @smp == UNSET_VALUE
41
+ @memory = "4G" if @memory == UNSET_VALUE
42
+ @image_path = nil if @image_path == UNSET_VALUE
43
+ @qemu_dir = "/opt/homebrew/share/qemu" if @qemu_dir == UNSET_VALUE
44
+ end
45
+
46
+ def validate(machine)
47
+ # errors = _detected_errors
48
+ errors = []
49
+ { "QEMU Provider" => errors }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,158 @@
1
+ require 'securerandom'
2
+
3
+ require "vagrant/util/busy"
4
+ require "vagrant/util/subprocess"
5
+
6
+ require_relative "plugin"
7
+
8
+ module VagrantPlugins
9
+ module QEMU
10
+ class Driver
11
+ # @return [String] VM ID
12
+ attr_reader :vm_id
13
+ attr_reader :data_dir
14
+
15
+ def initialize(id, dir)
16
+ @vm_id = id
17
+ @data_dir = dir
18
+ end
19
+
20
+ def get_current_state
21
+ case
22
+ when running?
23
+ :running
24
+ when created?
25
+ :stopped
26
+ else
27
+ :not_created
28
+ end
29
+ end
30
+
31
+ def delete
32
+ if created?
33
+ id_dir = @data_dir.join(@vm_id)
34
+ FileUtils.rm_rf(id_dir)
35
+ end
36
+ end
37
+
38
+ def start(options)
39
+ if !running?
40
+ id_dir = @data_dir.join(@vm_id)
41
+ image_path = id_dir.join("linked-box.img").to_s
42
+ unix_socket_path = id_dir.join("qemu_socket").to_s
43
+ pid_file = id_dir.join("qemu.pid").to_s
44
+
45
+ cmd = []
46
+ cmd += %W(qemu-system-#{options[:arch]})
47
+
48
+ # basic
49
+ cmd += %W(-machine #{options[:machine]})
50
+ cmd += %W(-cpu #{options[:cpu]})
51
+ cmd += %W(-smp #{options[:smp]})
52
+ cmd += %W(-m #{options[:memory]})
53
+ cmd += %W(-device virtio-net-device,netdev=net0)
54
+ cmd += %W(-netdev user,id=net0,hostfwd=tcp::#{options[:ssh_port]}-:22)
55
+
56
+ # drive
57
+ cmd += %W(-drive if=virtio,format=qcow2,file=#{image_path})
58
+ if options[:arch] == "aarch64"
59
+ fm1_path = id_dir.join("edk2-aarch64-code.fd").to_s
60
+ fm2_path = id_dir.join("edk2-arm-vars.fd").to_s
61
+ cmd += %W(-drive if=pflash,format=raw,file=#{fm1_path},readonly=on)
62
+ cmd += %W(-drive if=pflash,format=raw,file=#{fm2_path})
63
+ end
64
+
65
+ # control
66
+ cmd += %W(-chardev socket,id=mon0,path=#{unix_socket_path},server=on,wait=off)
67
+ cmd += %W(-mon chardev=mon0,mode=readline)
68
+ cmd += %W(-pidfile #{pid_file})
69
+ cmd += %W(-serial null -parallel null -monitor none -display none -vga none)
70
+ cmd += %W(-daemonize)
71
+
72
+ execute(*cmd)
73
+ end
74
+ end
75
+
76
+ def stop
77
+ if running?
78
+ id_dir = @data_dir.join(@vm_id)
79
+ unix_socket_path = id_dir.join("qemu_socket").to_s
80
+ sent = false
81
+ execute("nc", "-w", "5", "-U", unix_socket_path) do |type, data|
82
+ case type
83
+ when :stdin
84
+ if !sent
85
+ data.write("system_powerdown\n")
86
+ sent = true
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def import(options)
94
+ new_id = SecureRandom.urlsafe_base64(8)
95
+
96
+ # Make dir
97
+ id_dir = @data_dir.join(new_id)
98
+ FileUtils.mkdir_p(id_dir)
99
+
100
+ # Prepare firmware
101
+ execute("cp", options[:qemu_dir].join("edk2-aarch64-code.fd").to_s, id_dir.join("edk2-aarch64-code.fd").to_s)
102
+ execute("cp", options[:qemu_dir].join("edk2-arm-vars.fd").to_s, id_dir.join("edk2-arm-vars.fd").to_s)
103
+
104
+ # Create image
105
+ execute("qemu-img", "create", "-f", "qcow2", "-F", "qcow2", "-b", options[:image_path].to_s, id_dir.join("linked-box.img").to_s)
106
+
107
+ server = {
108
+ :id => new_id,
109
+ }
110
+ end
111
+
112
+ def created?
113
+ result = @data_dir.join(@vm_id).directory?
114
+ end
115
+
116
+ def running?
117
+ pid_file = @data_dir.join(@vm_id).join("qemu.pid")
118
+ return false if !pid_file.file?
119
+
120
+ begin
121
+ Process.getpgid(File.read(pid_file).to_i)
122
+ true
123
+ rescue Errno::ESRCH
124
+ false
125
+ end
126
+ end
127
+
128
+ def execute(*cmd, **opts, &block)
129
+ # Append in the options for subprocess
130
+ cmd << { notify: [:stdout, :stderr, :stdin] }
131
+
132
+ interrupted = false
133
+ int_callback = ->{ interrupted = true }
134
+ result = ::Vagrant::Util::Busy.busy(int_callback) do
135
+ ::Vagrant::Util::Subprocess.execute(*cmd, &block)
136
+ end
137
+
138
+ result.stderr.gsub!("\r\n", "\n")
139
+ result.stdout.gsub!("\r\n", "\n")
140
+
141
+ if result.exit_code != 0 && !interrupted
142
+ raise Errors::ExecuteError,
143
+ command: cmd.inspect,
144
+ stderr: result.stderr,
145
+ stdout: result.stdout
146
+ end
147
+
148
+ if opts
149
+ if opts[:with_stderr]
150
+ return result.stdout + " " + result.stderr
151
+ else
152
+ return result.stdout
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,31 @@
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
+ end
30
+ end
31
+ end
@@ -0,0 +1,73 @@
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(:qemu, box_format: "libvirt", box_optional: true, parallel: true) do
28
+ # Setup logging and i18n
29
+ setup_logging
30
+ setup_i18n
31
+
32
+ # Return the provider
33
+ require_relative "provider"
34
+ Provider
35
+ end
36
+
37
+ # This initializes the internationalization strings.
38
+ def self.setup_i18n
39
+ I18n.load_path << File.expand_path("locales/en.yml", QEMU.source_root)
40
+ I18n.reload!
41
+ end
42
+
43
+ # This sets up our log level to be whatever VAGRANT_LOG is.
44
+ def self.setup_logging
45
+ require "log4r"
46
+
47
+ level = nil
48
+ begin
49
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
50
+ rescue NameError
51
+ # This means that the logging constant wasn't found,
52
+ # which is fine. We just keep `level` as `nil`. But
53
+ # we tell the user.
54
+ level = nil
55
+ end
56
+
57
+ # Some constants, such as "true" resolve to booleans, so the
58
+ # above error checking doesn't catch it. This will check to make
59
+ # sure that the log level is an integer, as Log4r requires.
60
+ level = nil if !level.is_a?(Integer)
61
+
62
+ # Set the logging level on all "vagrant" namespaced
63
+ # logs as long as we have a valid level.
64
+ if level
65
+ logger = Log4r::Logger.new("vagrant_qemu")
66
+ logger.outputters = Log4r::Outputter.stderr
67
+ logger.level = level
68
+ logger = nil
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,76 @@
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)
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: "127.0.0.1",
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
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module QEMU
3
+ VERSION = '0.1.2'
4
+ end
5
+ end
@@ -0,0 +1,18 @@
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 :Errors, lib_path.join("errors")
10
+
11
+ # This returns the path to the source of this plugin.
12
+ #
13
+ # @return [Pathname]
14
+ def self.source_root
15
+ @source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
16
+ end
17
+ end
18
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,54 @@
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
+ execute_error: |-
46
+ A command executed by Vagrant didn't complete successfully!
47
+ The command run along with the output from the command is shown
48
+ below.
49
+
50
+ Command: %{command}
51
+
52
+ Stderr: %{stderr}
53
+
54
+ Stdout: %{stdout}
@@ -0,0 +1,52 @@
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.com"
11
+ s.homepage = ""
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) ||
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
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-qemu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - ppggff
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Enables Vagrant to manage machines with QEMU.
14
+ email: pgf00a@gmail.com.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".gitignore"
20
+ - CHANGELOG.md
21
+ - Gemfile
22
+ - LICENSE
23
+ - README.md
24
+ - Rakefile
25
+ - lib/vagrant-qemu.rb
26
+ - lib/vagrant-qemu/action.rb
27
+ - lib/vagrant-qemu/action/destroy.rb
28
+ - lib/vagrant-qemu/action/import.rb
29
+ - lib/vagrant-qemu/action/message_already_created.rb
30
+ - lib/vagrant-qemu/action/message_not_created.rb
31
+ - lib/vagrant-qemu/action/message_will_not_destroy.rb
32
+ - lib/vagrant-qemu/action/read_state.rb
33
+ - lib/vagrant-qemu/action/start_instance.rb
34
+ - lib/vagrant-qemu/action/stop_instance.rb
35
+ - lib/vagrant-qemu/action/warn_networks.rb
36
+ - lib/vagrant-qemu/config.rb
37
+ - lib/vagrant-qemu/driver.rb
38
+ - lib/vagrant-qemu/errors.rb
39
+ - lib/vagrant-qemu/plugin.rb
40
+ - lib/vagrant-qemu/provider.rb
41
+ - lib/vagrant-qemu/util/timer.rb
42
+ - lib/vagrant-qemu/version.rb
43
+ - locales/en.yml
44
+ - vagrant-qemu.gemspec
45
+ homepage: ''
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.6
63
+ requirements: []
64
+ rubygems_version: 3.0.3
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Enables Vagrant to manage machines with QEMU.
68
+ test_files: []