vagrant_utm 0.0.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +12 -0
- data/docs/.gitignore +15 -0
- data/docs/Gemfile +10 -0
- data/docs/Gemfile.lock +276 -0
- data/docs/README.md +174 -0
- data/docs/_config.yml +76 -0
- data/docs/_includes/footer_custom.html +3 -0
- data/docs/_sass/gallery.scss +64 -0
- data/docs/_virtual_machines/archlinux-arm.md +13 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/logo.png +0 -0
- data/docs/assets/images/screens/archlinux-logo.png +0 -0
- data/docs/assets/images/screens/debian-11-xfce-arm64.png +0 -0
- data/docs/boxes/creating_utm_box.md +70 -0
- data/docs/boxes/index.md +6 -0
- data/docs/boxes/utm_box_gallery.md +117 -0
- data/docs/commands.md +156 -0
- data/docs/configuration.md +51 -0
- data/docs/features/index.md +5 -0
- data/docs/features/synced_folders.md +28 -0
- data/docs/index.md +103 -0
- data/docs/internals/actions.md +20 -0
- data/docs/internals/index.md +5 -0
- data/docs/internals/status.md +25 -0
- data/docs/internals/utm_api.md +31 -0
- data/docs/known_issues.md +24 -0
- data/lib/vagrant_utm/action/boot.rb +22 -0
- data/lib/vagrant_utm/action/boot_disposable.rb +22 -0
- data/lib/vagrant_utm/action/check_accessible.rb +33 -0
- data/lib/vagrant_utm/action/check_created.rb +24 -0
- data/lib/vagrant_utm/action/check_guest_additions.rb +37 -0
- data/lib/vagrant_utm/action/check_qemu_img.rb +21 -0
- data/lib/vagrant_utm/action/check_running.rb +25 -0
- data/lib/vagrant_utm/action/check_utm.rb +30 -0
- data/lib/vagrant_utm/action/clear_forwarded_ports.rb +26 -0
- data/lib/vagrant_utm/action/created.rb +26 -0
- data/lib/vagrant_utm/action/customize.rb +50 -0
- data/lib/vagrant_utm/action/destroy.rb +25 -0
- data/lib/vagrant_utm/action/download_confirm.rb +19 -0
- data/lib/vagrant_utm/action/export.rb +22 -0
- data/lib/vagrant_utm/action/forced_halt.rb +28 -0
- data/lib/vagrant_utm/action/forward_ports.rb +90 -0
- data/lib/vagrant_utm/action/import.rb +63 -0
- data/lib/vagrant_utm/action/is_paused.rb +26 -0
- data/lib/vagrant_utm/action/is_running.rb +26 -0
- data/lib/vagrant_utm/action/is_stopped.rb +26 -0
- data/lib/vagrant_utm/action/message_already_running.rb +22 -0
- data/lib/vagrant_utm/action/message_not_created.rb +22 -0
- data/lib/vagrant_utm/action/message_not_running.rb +22 -0
- data/lib/vagrant_utm/action/message_not_stopped.rb +22 -0
- data/lib/vagrant_utm/action/message_will_not_create.rb +23 -0
- data/lib/vagrant_utm/action/message_will_not_destroy.rb +23 -0
- data/lib/vagrant_utm/action/prepare_forwarded_port_collision_params.rb +43 -0
- data/lib/vagrant_utm/action/resume.rb +24 -0
- data/lib/vagrant_utm/action/set_id.rb +20 -0
- data/lib/vagrant_utm/action/set_name.rb +62 -0
- data/lib/vagrant_utm/action/snapshot_delete.rb +34 -0
- data/lib/vagrant_utm/action/snapshot_restore.rb +28 -0
- data/lib/vagrant_utm/action/snapshot_save.rb +34 -0
- data/lib/vagrant_utm/action/suspend.rb +23 -0
- data/lib/vagrant_utm/action/wait_for_running.rb +28 -0
- data/lib/vagrant_utm/action.rb +413 -0
- data/lib/vagrant_utm/cap.rb +40 -0
- data/lib/vagrant_utm/config.rb +141 -0
- data/lib/vagrant_utm/disposable.rb +16 -0
- data/lib/vagrant_utm/driver/base.rb +358 -0
- data/lib/vagrant_utm/driver/meta.rb +132 -0
- data/lib/vagrant_utm/driver/version_4_5.rb +307 -0
- data/lib/vagrant_utm/errors.rb +77 -0
- data/lib/vagrant_utm/model/forwarded_port.rb +75 -0
- data/lib/vagrant_utm/model/list_result.rb +77 -0
- data/lib/vagrant_utm/plugin.rb +65 -0
- data/lib/vagrant_utm/provider.rb +139 -0
- data/lib/vagrant_utm/scripts/add_port_forwards.applescript +72 -0
- data/lib/vagrant_utm/scripts/clear_port_forwards.applescript +56 -0
- data/lib/vagrant_utm/scripts/customize_vm.applescript +59 -0
- data/lib/vagrant_utm/scripts/downloadVM.sh +1 -0
- data/lib/vagrant_utm/scripts/list_vm.js +32 -0
- data/lib/vagrant_utm/scripts/open_with_utm.js +30 -0
- data/lib/vagrant_utm/scripts/read_forwarded_ports.applescript +27 -0
- data/lib/vagrant_utm/scripts/read_guest_ip.applescript +9 -0
- data/lib/vagrant_utm/scripts/read_network_interfaces.applescript +12 -0
- data/lib/vagrant_utm/util/compile_forwarded_ports.rb +43 -0
- data/lib/vagrant_utm/version.rb +9 -0
- data/lib/vagrant_utm.rb +40 -0
- data/locales/en.yml +154 -0
- data/sig/vagrant_utm.rbs +4 -0
- data/vagrantfile_examples/Vagrantfile +27 -0
- metadata +140 -0
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "log4r"
|
4
|
+
require "etc"
|
5
|
+
require "vagrant/util/platform"
|
6
|
+
|
7
|
+
require File.expand_path("base", __dir__)
|
8
|
+
|
9
|
+
module VagrantPlugins
|
10
|
+
module Utm
|
11
|
+
module Driver
|
12
|
+
# Driver for UTM 4.5.x
|
13
|
+
class Version_4_5 < Base # rubocop:disable Naming/ClassAndModuleCamelCase,Metrics/ClassLength
|
14
|
+
def initialize(uuid)
|
15
|
+
super()
|
16
|
+
|
17
|
+
@logger = Log4r::Logger.new("vagrant::provider::utm_4_5")
|
18
|
+
@uuid = uuid
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_forwarded_ports
|
22
|
+
args = []
|
23
|
+
read_forwarded_ports(@uuid).each do |nic, name, _, _|
|
24
|
+
args.concat(["--index", nic.to_s, name])
|
25
|
+
end
|
26
|
+
|
27
|
+
command = ["clear_port_forwards.applescript", @uuid] + args
|
28
|
+
execute_osa_script(command) unless args.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_qemu_guest_agent
|
32
|
+
# Check if the qemu-guest-agent is installed and running
|
33
|
+
# Ideally do: utmctl exec id --cmd systemctl is-active qemu-guest-agent
|
34
|
+
# But this is not returning anything, so we just do any utmctl exec command
|
35
|
+
# Here we check if the user is root
|
36
|
+
output = execute("exec", @uuid, "--cmd", "whoami")
|
37
|
+
# check if output contains 'root'
|
38
|
+
output.include?("root")
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_snapshot(machine_id, snapshot_name)
|
42
|
+
list_result = list
|
43
|
+
machine_name = list_result.find(uuid: machine_id).name
|
44
|
+
machine_file = get_vm_file(machine_name)
|
45
|
+
execute_shell("qemu-img", "snapshot", "-c", snapshot_name, machine_file)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_snapshot(machine_id, snapshot_name)
|
49
|
+
list_result = list
|
50
|
+
machine_name = list_result.find(uuid: machine_id).name
|
51
|
+
machine_file = get_vm_file(machine_name)
|
52
|
+
execute_shell("qemu-img", "snapshot", "-d", snapshot_name, machine_file)
|
53
|
+
end
|
54
|
+
|
55
|
+
def list_snapshots(machine_id) # rubocop:disable Metrics/AbcSize
|
56
|
+
list_result = list
|
57
|
+
machine_name = list_result.find(uuid: machine_id).name
|
58
|
+
machine_file = get_vm_file(machine_name)
|
59
|
+
output = execute_shell("qemu-img", "snapshot", "-l", machine_file)
|
60
|
+
|
61
|
+
return [] if output.nil? || output.strip.empty?
|
62
|
+
|
63
|
+
@logger.debug("list_snapshots_here: #{output}")
|
64
|
+
|
65
|
+
result = []
|
66
|
+
output.split("\n").map do |line|
|
67
|
+
result << ::Regexp.last_match(1).to_s if line =~ /^\d+\s+(\w+)/
|
68
|
+
end
|
69
|
+
|
70
|
+
result.sort
|
71
|
+
end
|
72
|
+
|
73
|
+
def restore_snapshot(machine_id, snapshot_name)
|
74
|
+
list_result = list
|
75
|
+
machine_name = list_result.find(uuid: machine_id).name
|
76
|
+
machine_file = get_vm_file(machine_name)
|
77
|
+
execute_shell("qemu-img", "snapshot", "-a", snapshot_name, machine_file)
|
78
|
+
end
|
79
|
+
|
80
|
+
def forward_ports(ports) # rubocop:disable Metrics/CyclomaticComplexity
|
81
|
+
args = []
|
82
|
+
ports.each do |options|
|
83
|
+
# Convert to UTM protcol enum
|
84
|
+
protocol_code = case options[:protocol]
|
85
|
+
when "tcp"
|
86
|
+
"TcPp"
|
87
|
+
when "udp"
|
88
|
+
"UdPp"
|
89
|
+
else
|
90
|
+
raise Errors::ForwardedPortInvalidProtocol
|
91
|
+
end
|
92
|
+
|
93
|
+
pf_builder = [
|
94
|
+
# options[:name], # Name is not supported in UTM
|
95
|
+
protocol_code || "TcPp", # Default to TCP
|
96
|
+
options[:guestip] || "",
|
97
|
+
options[:guestport],
|
98
|
+
options[:hostip] || "",
|
99
|
+
options[:hostport]
|
100
|
+
]
|
101
|
+
|
102
|
+
args.concat(["--index", options[:adapter].to_s,
|
103
|
+
pf_builder.join(",")])
|
104
|
+
end
|
105
|
+
|
106
|
+
command = ["add_port_forwards.applescript", @uuid] + args
|
107
|
+
execute_osa_script(command) unless args.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_vm_file(vm_name)
|
111
|
+
# Get the current username
|
112
|
+
username = ENV["USER"] || Etc.getlogin
|
113
|
+
|
114
|
+
data_path = "/Users/#{username}/Library/Containers/com.utmapp.UTM/Data/Documents/#{vm_name}.utm/Data"
|
115
|
+
# Define the regex for UUID pattern
|
116
|
+
pattern = /^\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\b.qcow2$/
|
117
|
+
|
118
|
+
# List the files in the directory and filter by regex
|
119
|
+
matching_files = Dir.entries(data_path).select do |file|
|
120
|
+
file.match?(pattern)
|
121
|
+
end
|
122
|
+
|
123
|
+
if matching_files.length == 1
|
124
|
+
# If there is exactly one matching file, return full path to file
|
125
|
+
"#{data_path}/#{matching_files[0]}"
|
126
|
+
elsif matching_files.length > 1
|
127
|
+
# If there are multiple matching files, raise an error
|
128
|
+
raise Errors::SnapShotMultipleVMFiles, directory: data_path, files: matching_files
|
129
|
+
else
|
130
|
+
# If there are no matching files, raise an error
|
131
|
+
raise Errors::SnapShotVMFileNotFound, directory: data_path
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Check if the VM with the given UUID exists.
|
136
|
+
def vm_exists?(uuid)
|
137
|
+
list_result = list
|
138
|
+
list_result.any?(uuid)
|
139
|
+
end
|
140
|
+
|
141
|
+
def read_forwarded_ports(uuid = nil)
|
142
|
+
uuid ||= @uuid
|
143
|
+
|
144
|
+
@logger.debug("read_forward_ports: uuid=#{uuid}")
|
145
|
+
|
146
|
+
# If we care about active VMs only, then we check the state
|
147
|
+
# to verify the VM is running.
|
148
|
+
# This is taken care by the caller , read used ports
|
149
|
+
# return [] if active_only && check_state != :started
|
150
|
+
|
151
|
+
# Get the forwarded ports from emulated Network interface
|
152
|
+
# Format: [nicIndex, name, hostPort, guestPort]
|
153
|
+
# We use hostPort as the name, since UTM does not support name
|
154
|
+
# Because hostport is and should be unique
|
155
|
+
results = []
|
156
|
+
command = ["read_forwarded_ports.applescript", uuid]
|
157
|
+
info = execute_osa_script(command)
|
158
|
+
info.split("\n").each do |line|
|
159
|
+
# Parse info, Forwarding(nicIndex)(ruleIndex)="Protocol,GuestIP,GuestPort,HostIP,HostPort"
|
160
|
+
next unless (matcher = /^Forwarding\((\d+)\)\((\d+)\)="(.+?),.*?,(.+?),.*?,(.+?)"$/.match(line))
|
161
|
+
|
162
|
+
# nicIndex name( our hostPort) hostport guestport
|
163
|
+
result = [matcher[1].to_i, matcher[5], matcher[5].to_i, matcher[4].to_i]
|
164
|
+
@logger.debug(" - #{result.inspect}")
|
165
|
+
results << result
|
166
|
+
end
|
167
|
+
|
168
|
+
results
|
169
|
+
end
|
170
|
+
|
171
|
+
def read_guest_ip
|
172
|
+
command = ["read_guest_ip.applescript", @uuid]
|
173
|
+
output = execute_osa_script(command)
|
174
|
+
output.strip
|
175
|
+
end
|
176
|
+
|
177
|
+
def read_network_interfaces
|
178
|
+
nics = {}
|
179
|
+
command = ["read_network_interfaces.applescript", @uuid]
|
180
|
+
info = execute_osa_script(command)
|
181
|
+
info.split("\n").each do |line|
|
182
|
+
next unless (matcher = /^nic(\d+),(.+?)$/.match(line))
|
183
|
+
|
184
|
+
adapter = matcher[1].to_i
|
185
|
+
type = matcher[2].to_sym
|
186
|
+
nics[adapter] ||= {}
|
187
|
+
nics[adapter][:type] = type
|
188
|
+
end
|
189
|
+
|
190
|
+
nics
|
191
|
+
end
|
192
|
+
|
193
|
+
def ssh_port(expected_port)
|
194
|
+
@logger.debug("Searching for SSH port: #{expected_port.inspect}")
|
195
|
+
|
196
|
+
# Look for the forwarded port only by comparing the guest port
|
197
|
+
read_forwarded_ports.each do |_, _, hostport, guestport|
|
198
|
+
return hostport if guestport == expected_port
|
199
|
+
end
|
200
|
+
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
|
204
|
+
# virtualbox plugin style
|
205
|
+
def read_state
|
206
|
+
output = execute("status", @uuid)
|
207
|
+
output.strip.to_sym
|
208
|
+
end
|
209
|
+
|
210
|
+
# We handle the active only case here
|
211
|
+
# So we can avoid calling the utmctl status command
|
212
|
+
# for each VM
|
213
|
+
def read_used_ports(active_only = true) # rubocop:disable Style/OptionalBooleanParameter
|
214
|
+
@logger.debug("read_used_ports: active_only=#{active_only}")
|
215
|
+
ports = []
|
216
|
+
list.machines.each do |machine|
|
217
|
+
# Ignore our own used ports
|
218
|
+
next if machine.uuid == @uuid
|
219
|
+
# Ignore inactive VMs if we only care about active VMs
|
220
|
+
next if active_only && machine.state != :started
|
221
|
+
|
222
|
+
read_forwarded_ports(machine.uuid).each do |_, _, hostport, _|
|
223
|
+
ports << hostport
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
ports
|
228
|
+
end
|
229
|
+
|
230
|
+
def set_name(name) # rubocop:disable Naming/AccessorMethodName
|
231
|
+
command = ["customize_vm.applescript", @uuid, "--name", name.to_s]
|
232
|
+
execute_osa_script(command)
|
233
|
+
end
|
234
|
+
|
235
|
+
def delete
|
236
|
+
execute("delete", @uuid)
|
237
|
+
end
|
238
|
+
|
239
|
+
def start
|
240
|
+
execute("start", @uuid)
|
241
|
+
end
|
242
|
+
|
243
|
+
def start_disposable
|
244
|
+
execute("start", @uuid, "--disposable")
|
245
|
+
end
|
246
|
+
|
247
|
+
def halt
|
248
|
+
execute("stop", @uuid)
|
249
|
+
end
|
250
|
+
|
251
|
+
def suspend
|
252
|
+
execute("suspend", @uuid)
|
253
|
+
end
|
254
|
+
|
255
|
+
def execute_osa_script(command)
|
256
|
+
script_path = @script_path.join(command[0])
|
257
|
+
cmd = ["osascript", script_path.to_s] + command[1..]
|
258
|
+
execute_shell(*cmd)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Execute the 'list' command and returns the list of machines.
|
262
|
+
# @return [ListResult] The list of machines.
|
263
|
+
def list
|
264
|
+
script_path = @script_path.join("list_vm.js")
|
265
|
+
cmd = ["osascript", script_path.to_s]
|
266
|
+
result = execute_shell(*cmd)
|
267
|
+
data = JSON.parse(result)
|
268
|
+
Model::ListResult.new(data)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Execute the 'utm://downloadVM?url='
|
272
|
+
# See https://docs.getutm.app/advanced/remote-control/
|
273
|
+
# @param utm_file_url [String] The url to the UTM file.
|
274
|
+
# @return [uuid] The UUID of the imported machine.
|
275
|
+
def import(utm_file_url)
|
276
|
+
script_path = @script_path.join("downloadVM.sh")
|
277
|
+
cmd = [script_path.to_s, utm_file_url]
|
278
|
+
execute_shell(*cmd)
|
279
|
+
# wait for the VM to be imported
|
280
|
+
# TODO: UTM API to give the progress of the import
|
281
|
+
# along with the UUID of the imported VM
|
282
|
+
# sleep(60)
|
283
|
+
# Get the UUID of the imported VM
|
284
|
+
# HACK: Currently we do not know the UUID of the imported VM
|
285
|
+
# So, we just get the UUID of the last VM in the list
|
286
|
+
# which is the last imported VM (unless UTM changes the order)
|
287
|
+
# TODO: Use UTM API to get the UUID of the imported VM
|
288
|
+
# last_uuid
|
289
|
+
end
|
290
|
+
|
291
|
+
# Return UUID of the last VM in the list.
|
292
|
+
# @return [uuid] The UUID of the VM.
|
293
|
+
def last_uuid
|
294
|
+
list_result = list
|
295
|
+
list_result.last.uuid
|
296
|
+
end
|
297
|
+
|
298
|
+
def verify!
|
299
|
+
# Verify proper functionality of UTM
|
300
|
+
# add any command that should be checked
|
301
|
+
# we now only check if the 'utmctl' command is available
|
302
|
+
execute("--list")
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module Utm
|
5
|
+
module Errors
|
6
|
+
# Base class for all errors raised by the UTM provider.
|
7
|
+
class UtmError < Vagrant::Errors::VagrantError
|
8
|
+
error_namespace("vagrant_utm.errors")
|
9
|
+
end
|
10
|
+
|
11
|
+
# This error is raised if the platform is not MacOS.
|
12
|
+
class MacOSRequired < UtmError
|
13
|
+
error_key(:macos_required)
|
14
|
+
end
|
15
|
+
|
16
|
+
# This error is raised if the UTM is not found.
|
17
|
+
class UtmNotDetected < UtmError
|
18
|
+
error_key(:utm_not_detected)
|
19
|
+
end
|
20
|
+
|
21
|
+
# This error is raised if the UTM version is not supported.
|
22
|
+
class UtmInvalidVersion < UtmError
|
23
|
+
error_key(:utm_invalid_version)
|
24
|
+
end
|
25
|
+
|
26
|
+
# This error is raised if the utmctl is not found.
|
27
|
+
class UtmctlNotFoundError < UtmError
|
28
|
+
error_key(:utmctl_not_found)
|
29
|
+
end
|
30
|
+
|
31
|
+
# This error is raised if the utmctl command fails.
|
32
|
+
class UtmctlError < UtmError
|
33
|
+
error_key(:utmctl_error)
|
34
|
+
end
|
35
|
+
|
36
|
+
# This error is raised if the utm or other binary like osa fail to launch.
|
37
|
+
class UtmLaunchError < UtmError
|
38
|
+
error_key(:utm_launch_error)
|
39
|
+
end
|
40
|
+
|
41
|
+
# This error is raised if a shell/osascript command fails.
|
42
|
+
class CommandError < UtmError
|
43
|
+
error_key(:command_error)
|
44
|
+
end
|
45
|
+
|
46
|
+
# This error is raised if UTM file was failed to import.
|
47
|
+
class UtmImportFailed < UtmError
|
48
|
+
error_key(:utm_import_failed)
|
49
|
+
end
|
50
|
+
|
51
|
+
# This error is raised if invalid protocol is used in forwarded ports.
|
52
|
+
class ForwardedPortInvalidProtocol < UtmError
|
53
|
+
error_key(:forwarded_port_invalid_protocol)
|
54
|
+
end
|
55
|
+
|
56
|
+
# This error is raised if the qemu-img is not detected.
|
57
|
+
class QemuImgRequired < UtmError
|
58
|
+
error_key(:qemu_img_required)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This error is raised if the snapshot command failed.
|
62
|
+
class SnapShotCommandFailed < UtmError
|
63
|
+
error_key(:snapshot_command_failed)
|
64
|
+
end
|
65
|
+
|
66
|
+
# This error is raised if multiple VM files are found during snapshot.
|
67
|
+
class SnapShotMultipleVMFiles < UtmError
|
68
|
+
error_key(:snapshot_multiple_vm_files)
|
69
|
+
end
|
70
|
+
|
71
|
+
# This error is raised if the VM is not found.
|
72
|
+
class SnapShotVMFileNotFound < UtmError
|
73
|
+
error_key(:snapshot_vm_file_not_found)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) HashiCorp, Inc.
|
4
|
+
# SPDX-License-Identifier: BUSL-1.1
|
5
|
+
|
6
|
+
module VagrantPlugins
|
7
|
+
module Utm
|
8
|
+
module Model
|
9
|
+
# Represents a single forwarded port for UTM. This has various
|
10
|
+
# helpers and defaults for a forwarded port.
|
11
|
+
class ForwardedPort
|
12
|
+
# The 'Emulated VLAN' adapter on which to attach the forwarded port.
|
13
|
+
#
|
14
|
+
# @return [Integer]
|
15
|
+
attr_reader :adapter
|
16
|
+
|
17
|
+
# If true, this port should be auto-corrected.
|
18
|
+
# TODO: This is not implemented yet.
|
19
|
+
# @return [Boolean]
|
20
|
+
attr_reader :auto_correct
|
21
|
+
|
22
|
+
# The unique ID for the forwarded port.
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
attr_reader :id
|
26
|
+
|
27
|
+
# The protocol to forward.
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
attr_reader :protocol
|
31
|
+
|
32
|
+
# The IP that the forwarded port will connect to on the guest machine.
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
attr_reader :guest_ip
|
36
|
+
|
37
|
+
# The port on the guest to be exposed on the host.
|
38
|
+
#
|
39
|
+
# @return [Integer]
|
40
|
+
attr_reader :guest_port
|
41
|
+
|
42
|
+
# The IP that the forwarded port will bind to on the host machine.
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
attr_reader :host_ip
|
46
|
+
|
47
|
+
# The port on the host used to access the port on the guest.
|
48
|
+
#
|
49
|
+
# @return [Integer]
|
50
|
+
attr_reader :host_port
|
51
|
+
|
52
|
+
def initialize(id, host_port, guest_port, options)
|
53
|
+
@id = id
|
54
|
+
@guest_port = guest_port
|
55
|
+
@host_port = host_port
|
56
|
+
|
57
|
+
options ||= {}
|
58
|
+
@auto_correct = false
|
59
|
+
@auto_correct = options[:auto_correct] if options.key?(:auto_correct)
|
60
|
+
@adapter = (options[:adapter] || 1).to_i # if adapter is not set, use 1. index 0 is the default adapter
|
61
|
+
@guest_ip = options[:guest_ip] || nil
|
62
|
+
@host_ip = options[:host_ip] || nil
|
63
|
+
@protocol = options[:protocol] || "tcp" # default to TCP
|
64
|
+
end
|
65
|
+
|
66
|
+
# This corrects the host port and changes it to the given new port.
|
67
|
+
# TODO: This is not implemented yet.
|
68
|
+
# @param [Integer] new_port The new port
|
69
|
+
def correct_host_port(new_port)
|
70
|
+
@host_port = new_port
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module Utm
|
5
|
+
module Model
|
6
|
+
# Represents the result of a 'utmctl list' operation.
|
7
|
+
class ListResult
|
8
|
+
# @return [Array<ListResultItem>] The list of machines.
|
9
|
+
attr_accessor :machines
|
10
|
+
|
11
|
+
# Initialize the result from raw data.
|
12
|
+
# @param [Array<Hash>] data The raw data.
|
13
|
+
def initialize(data)
|
14
|
+
@machines = []
|
15
|
+
data.each do |machine|
|
16
|
+
item = ListResultItem.new(machine)
|
17
|
+
@machines << item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks if a machine with the given UUID exists.
|
22
|
+
# @param uuid [String] The UUID of the machine.
|
23
|
+
# @return [Boolean]
|
24
|
+
def any?(uuid)
|
25
|
+
@machines.any? { |i| i.uuid == uuid }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Finds a machine with the given name or uuid.
|
29
|
+
# @param [String] name The name of the machine.
|
30
|
+
# @return [ListResultItem]
|
31
|
+
def find(name: nil, uuid: nil)
|
32
|
+
if name
|
33
|
+
@machines.find { |i| i.name == name }
|
34
|
+
elsif uuid
|
35
|
+
@machines.find { |i| i.uuid == uuid }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the last machine in the list.
|
40
|
+
# @return [ListResultItem]
|
41
|
+
def last
|
42
|
+
@machines.last
|
43
|
+
end
|
44
|
+
|
45
|
+
# Represents an item in the list result.
|
46
|
+
class ListResultItem
|
47
|
+
# @return [String] The UUID of the machine.
|
48
|
+
attr_accessor :uuid
|
49
|
+
# @return [String] The name of the machine.
|
50
|
+
attr_accessor :name
|
51
|
+
# @return [String] The state of the machine.
|
52
|
+
attr_accessor :state
|
53
|
+
|
54
|
+
# Initialize the result from raw data.
|
55
|
+
# @param [Hash] data The raw data.
|
56
|
+
def initialize(data)
|
57
|
+
@uuid = data["UUID"]
|
58
|
+
@name = data["Name"]
|
59
|
+
@state = data["Status"]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the state of the machine using Vagrant symbols.
|
63
|
+
def vagrant_state
|
64
|
+
case @state
|
65
|
+
when "running"
|
66
|
+
:running
|
67
|
+
when "stopped", "suspended"
|
68
|
+
:stopped
|
69
|
+
else
|
70
|
+
:host_state_unknown
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# check if the Vagrant gem is available
|
4
|
+
begin
|
5
|
+
require "vagrant"
|
6
|
+
rescue LoadError
|
7
|
+
raise "The 'vagrant' gem could not be found. Please make sure it is installed."
|
8
|
+
end
|
9
|
+
|
10
|
+
# check if the Vagrant version is sufficient
|
11
|
+
raise "The Vagrant UTM plugin is only compatible with Vagrant 2.3 or later" if Vagrant::VERSION < "2.3.0"
|
12
|
+
|
13
|
+
module VagrantPlugins
|
14
|
+
module Utm
|
15
|
+
# This is the main entry point for the UTM provider plugin.
|
16
|
+
class Plugin < Vagrant.plugin("2")
|
17
|
+
name "utm"
|
18
|
+
description <<-DESCRIPTION
|
19
|
+
UTM provider allows Vagrant to manage and control
|
20
|
+
VMs using UTM through Apple Scripting Bridge.
|
21
|
+
DESCRIPTION
|
22
|
+
|
23
|
+
# Register the provider
|
24
|
+
# TODO: Define box format for UTM
|
25
|
+
# IDEA: UTM file comes as a zip file containing
|
26
|
+
# directory with Data/qcow2, Data/efi_vars.fd and config.plist
|
27
|
+
# Box format will only require additional metadata.json file
|
28
|
+
# Till then use UTM file directly and so box_optional: true
|
29
|
+
provider(:utm, box_optional: true, parallel: false) do
|
30
|
+
setup_i18n
|
31
|
+
require_relative "provider"
|
32
|
+
Provider
|
33
|
+
end
|
34
|
+
|
35
|
+
# Register the configuration
|
36
|
+
config(:utm, :provider) do
|
37
|
+
require_relative "config"
|
38
|
+
Config
|
39
|
+
end
|
40
|
+
|
41
|
+
# Register capabilities
|
42
|
+
provider_capability(:utm, :forwarded_ports) do
|
43
|
+
require_relative "cap"
|
44
|
+
Cap
|
45
|
+
end
|
46
|
+
|
47
|
+
provider_capability(:utm, :snapshot_list) do
|
48
|
+
require_relative "cap"
|
49
|
+
Cap
|
50
|
+
end
|
51
|
+
|
52
|
+
# Register the command
|
53
|
+
command "disposable" do
|
54
|
+
require_relative "disposable"
|
55
|
+
Disposable
|
56
|
+
end
|
57
|
+
|
58
|
+
# Load the translation files
|
59
|
+
def self.setup_i18n
|
60
|
+
I18n.load_path << File.expand_path("locales/en.yml", Utm.source_root)
|
61
|
+
I18n.reload!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|