foreman_fog_proxmox 0.25.0 → 0.25.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 +4 -4
- data/app/helpers/proxmox_compute_controllers_helper.rb +6 -4
- data/app/helpers/proxmox_compute_selectors_helper.rb +2 -1
- data/app/helpers/proxmox_vm_attrs_helper.rb +2 -1
- data/app/helpers/proxmox_vm_cloudinit_helper.rb +0 -8
- data/app/helpers/proxmox_vm_config_helper.rb +3 -1
- data/app/helpers/proxmox_vm_efidisk_helper.rb +64 -0
- data/app/helpers/proxmox_vm_helper.rb +22 -0
- data/app/helpers/proxmox_vm_image_template_helper.rb +55 -0
- data/app/models/concerns/fog_extensions/proxmox/server_config.rb +9 -0
- data/app/models/foreman_fog_proxmox/proxmox_efidisks.rb +11 -3
- data/app/models/foreman_fog_proxmox/proxmox_images.rb +2 -3
- data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +14 -12
- data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +7 -0
- data/lib/foreman_fog_proxmox/version.rb +1 -1
- data/test/unit/foreman_fog_proxmox/helpers/proxmox_vm_helper_test.rb +100 -0
- data/test/unit/foreman_fog_proxmox/proxmox_images_test.rb +3 -5
- data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_container_test.rb +3 -1
- data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +103 -1
- data/test/unit/foreman_fog_proxmox/proxmox_vm_new_test.rb +30 -0
- data/webpack/components/ProxmoxBiosContext.js +14 -3
- data/webpack/components/ProxmoxComputeSelectors.js +1 -0
- data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +17 -4
- data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +5 -1
- data/webpack/components/ProxmoxServer/components/EFIDisk.js +13 -4
- data/webpack/components/ProxmoxServer/components/SecureBoot.js +69 -0
- data/webpack/components/ProxmoxVmType.js +5 -1
- data/webpack/components/__tests__/EFIDisk.test.js +59 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d59641775e4da477886d59bf332dadd576426ef079d1c6af43ef2e9f70b9c5a5
|
|
4
|
+
data.tar.gz: 5e24be7c7881de3c05d5a59a06500bd9d35f7c1f06d4c10390df473ec6ef8b01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e04095637c92c2a341043e96f345d4d9ddcd5a3f86b4285fc1ee5199a0e9c36fd542e40a28e748d66d4da56cec743618d144300b907be7c4d82a0a4daac21d82
|
|
7
|
+
data.tar.gz: 18a1bb26c3d35273d36826012d35c7e5f21a9c65193e86b7ab9babbde49ae5d15dcea828fa37a024e91740bdfb5a29788047f8ad8cd5829215324e21267e4079
|
|
@@ -18,15 +18,17 @@
|
|
|
18
18
|
# along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
|
|
20
20
|
module ProxmoxComputeControllersHelper
|
|
21
|
+
CONTROLLER_LIMITS = { 'ide' => 3, 'sata' => 5, 'scsi' => 30, 'virtio' => 15 }.freeze
|
|
22
|
+
|
|
21
23
|
def proxmox_controllers_map
|
|
22
24
|
proxmox_controllers_cloudinit_map << ForemanFogProxmox::OptionsSelect.new(name: 'VirtIO Block', id: 'virtio',
|
|
23
|
-
range:
|
|
25
|
+
range: CONTROLLER_LIMITS['virtio'])
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def proxmox_controllers_cloudinit_map
|
|
27
|
-
[ForemanFogProxmox::OptionsSelect.new(id: 'ide', name: 'IDE', range:
|
|
28
|
-
ForemanFogProxmox::OptionsSelect.new(id: 'sata', name: 'SATA', range:
|
|
29
|
-
ForemanFogProxmox::OptionsSelect.new(id: 'scsi', name: 'SCSI', range:
|
|
29
|
+
[ForemanFogProxmox::OptionsSelect.new(id: 'ide', name: 'IDE', range: CONTROLLER_LIMITS['ide']),
|
|
30
|
+
ForemanFogProxmox::OptionsSelect.new(id: 'sata', name: 'SATA', range: CONTROLLER_LIMITS['sata']),
|
|
31
|
+
ForemanFogProxmox::OptionsSelect.new(id: 'scsi', name: 'SCSI', range: CONTROLLER_LIMITS['scsi'])]
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def proxmox_scsi_controllers_map
|
|
@@ -141,6 +141,7 @@ module ProxmoxComputeSelectorsHelper
|
|
|
141
141
|
|
|
142
142
|
def proxmox_bios_map
|
|
143
143
|
[OpenStruct.new(id: 'seabios', name: '(Default) Seabios'),
|
|
144
|
-
OpenStruct.new(id: 'ovmf', name: 'OVMF (UEFI)')
|
|
144
|
+
OpenStruct.new(id: 'ovmf', name: 'OVMF (UEFI)'),
|
|
145
|
+
OpenStruct.new(id: 'uefi_secure_boot', name: 'OVMF (UEFI Secure Boot)')]
|
|
145
146
|
end
|
|
146
147
|
end
|
|
@@ -132,8 +132,9 @@ module ProxmoxVMAttrsHelper
|
|
|
132
132
|
ostemplate_file: vms.ostemplate_file,
|
|
133
133
|
start_after_create: vms.start_after_create,
|
|
134
134
|
templated: vms.templated,
|
|
135
|
+
is_secure_boot: vms.config.secure_boot?,
|
|
135
136
|
}
|
|
136
|
-
vms_keys = [:cpu_type, :nameserver, :searchdomain, :hostname]
|
|
137
|
+
vms_keys = [:cpu_type, :nameserver, :searchdomain, :hostname, :is_secure_boot]
|
|
137
138
|
extra_attrs = ActiveSupport::HashWithIndifferentAccess.new
|
|
138
139
|
attributes.each do |key, value|
|
|
139
140
|
camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
|
|
@@ -106,7 +106,6 @@ module ProxmoxVMCloudinitHelper
|
|
|
106
106
|
configs = filenames.zip(config_data).to_h
|
|
107
107
|
|
|
108
108
|
iso = create_cloudinit_iso(args[:name], configs, ssh)
|
|
109
|
-
args[:config_attributes]&.merge!(update_boot_order(args[:image_id]))
|
|
110
109
|
args.merge!(attach_cloudinit_iso(args[:node_id], iso))
|
|
111
110
|
end
|
|
112
111
|
|
|
@@ -126,13 +125,6 @@ module ProxmoxVMCloudinitHelper
|
|
|
126
125
|
"/var/lib/vz/template/iso"
|
|
127
126
|
end
|
|
128
127
|
|
|
129
|
-
def update_boot_order(image_id)
|
|
130
|
-
vm = find_vm_by_uuid(image_id)
|
|
131
|
-
return if vm.disks.nil?
|
|
132
|
-
disks = vm.disks.map { |disk| disk.split(":")[0] }.join(";")
|
|
133
|
-
{ boot: "order=" + disks }
|
|
134
|
-
end
|
|
135
|
-
|
|
136
128
|
def vm_ssh
|
|
137
129
|
ssh = Fog::SSH.new(URI.parse(fog_credentials[:proxmox_url]).host, fog_credentials[:proxmox_username].split('@')[0], { password: fog_credentials[:proxmox_password] })
|
|
138
130
|
ssh.run('ls') # test if ssh is successful
|
|
@@ -60,7 +60,9 @@ module ProxmoxVMConfigHelper
|
|
|
60
60
|
cpu_a += ForemanFogProxmox::HashCollection.stringify_keys(Fog::Proxmox::CpuHelper.flags).keys
|
|
61
61
|
memory_a = ['memory', 'balloon', 'shares']
|
|
62
62
|
cloud_init_a = ['ciuser', 'cipassword', 'searchdomain', 'nameserver']
|
|
63
|
+
firmware_a = ['is_secure_boot']
|
|
63
64
|
keys.store(:cloud_init, cloud_init_a)
|
|
65
|
+
keys.store(:firmware, firmware_a)
|
|
64
66
|
end
|
|
65
67
|
keys.store(:main, main_a)
|
|
66
68
|
keys.store(:cpu, cpu_a)
|
|
@@ -76,7 +78,7 @@ module ProxmoxVMConfigHelper
|
|
|
76
78
|
config_a = []
|
|
77
79
|
case type
|
|
78
80
|
when 'qemu'
|
|
79
|
-
[:cpu, :memory, :general, :cloud_init].each { |key| config_a += config_typed_keys(type)[key] }
|
|
81
|
+
[:cpu, :memory, :general, :cloud_init, :firmware].each { |key| config_a += config_typed_keys(type)[key] }
|
|
80
82
|
when 'lxc'
|
|
81
83
|
[:main].each { |key| config_a += config_typed_keys(type)[key] }
|
|
82
84
|
end
|
|
@@ -23,6 +23,70 @@ require 'foreman_fog_proxmox/hash_collection'
|
|
|
23
23
|
|
|
24
24
|
# Convert a foreman form server hash into a fog-proxmox server attributes hash
|
|
25
25
|
module ProxmoxVMEfidiskHelper
|
|
26
|
+
def process_firmware_attributes(args, type)
|
|
27
|
+
return args unless type == 'qemu'
|
|
28
|
+
return args unless args
|
|
29
|
+
|
|
30
|
+
normalized_args = ActiveSupport::HashWithIndifferentAccess.new(args)
|
|
31
|
+
config = normalized_args[:config_attributes] || normalized_args
|
|
32
|
+
return args unless config.key?(:bios) || config.key?(:is_secure_boot)
|
|
33
|
+
|
|
34
|
+
args = normalized_args
|
|
35
|
+
|
|
36
|
+
bios = config[:bios]
|
|
37
|
+
secure_boot_value = config[:is_secure_boot]
|
|
38
|
+
secure_boot = bios == 'uefi_secure_boot' ||
|
|
39
|
+
(bios == 'ovmf' && Foreman::Cast.to_bool(secure_boot_value))
|
|
40
|
+
config[:is_secure_boot] = secure_boot ? '1' : '0'
|
|
41
|
+
|
|
42
|
+
unless secure_boot
|
|
43
|
+
normalize_pre_enrolled_keys(args)
|
|
44
|
+
return args
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
config[:bios] = 'ovmf'
|
|
48
|
+
process_secure_boot_efidisk(args, config)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def normalize_pre_enrolled_keys(args)
|
|
52
|
+
efidisk = args[:efidisk_attributes]
|
|
53
|
+
return args if ForemanFogProxmox::Value.empty?(efidisk)
|
|
54
|
+
|
|
55
|
+
efidisk[:pre_enrolled_keys] = '0'
|
|
56
|
+
args
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def process_secure_boot_efidisk(args, config)
|
|
60
|
+
efidisk = args[:efidisk_attributes]
|
|
61
|
+
unless ForemanFogProxmox::Value.empty?(efidisk)
|
|
62
|
+
args[:efidisk_attributes] = secure_boot_efidisk_attributes(
|
|
63
|
+
efidisk.merge('efitype' => '4m', 'pre_enrolled_keys' => '1')
|
|
64
|
+
)
|
|
65
|
+
return args
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
volumes = args[:volumes_attributes] || config[:volumes_attributes]
|
|
69
|
+
storage = volumes&.each_value&.filter_map { |volume| volume[:storage] }&.find(&:present?)
|
|
70
|
+
return args if ForemanFogProxmox::Value.empty?(storage)
|
|
71
|
+
|
|
72
|
+
args[:efidisk_attributes] = secure_boot_efidisk_attributes(storage: storage)
|
|
73
|
+
args
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def efidisk_defaults(storage)
|
|
77
|
+
{
|
|
78
|
+
'id' => '0',
|
|
79
|
+
'volid' => "#{storage}:0",
|
|
80
|
+
'efitype' => '4m',
|
|
81
|
+
'pre_enrolled_keys' => '1',
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def secure_boot_efidisk_attributes(attr = {})
|
|
86
|
+
attr = attr.to_h.stringify_keys
|
|
87
|
+
efidisk_defaults(attr['storage']).merge(attr)
|
|
88
|
+
end
|
|
89
|
+
|
|
26
90
|
def parsed_typed_efidisk(args, type, parsed_vm)
|
|
27
91
|
attrs = args['efidisk_attributes']
|
|
28
92
|
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
|
|
20
20
|
require 'fog/proxmox/helpers/disk_helper'
|
|
21
21
|
require 'fog/proxmox/helpers/nic_helper'
|
|
22
|
+
require 'foreman_fog_proxmox/hash_collection'
|
|
22
23
|
require 'foreman_fog_proxmox/value'
|
|
23
24
|
|
|
24
25
|
module ProxmoxVMHelper
|
|
25
26
|
include ProxmoxVMInterfacesHelper
|
|
26
27
|
include ProxmoxVMVolumesHelper
|
|
28
|
+
include ProxmoxVMImageTemplateHelper
|
|
27
29
|
include ProxmoxVMConfigHelper
|
|
28
30
|
include ProxmoxVMOsTemplateHelper
|
|
29
31
|
include ProxmoxVMEfidiskHelper
|
|
@@ -34,8 +36,28 @@ module ProxmoxVMHelper
|
|
|
34
36
|
collection
|
|
35
37
|
end
|
|
36
38
|
|
|
39
|
+
def parse_vm_update_attributes(attributes, type)
|
|
40
|
+
ForemanFogProxmox::HashCollection.new_hash_reject_keys(
|
|
41
|
+
attributes, ['volumes_attributes']
|
|
42
|
+
).merge(type: type)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update_boot_order(instance, exclude_cdrom: false, include_network: false)
|
|
46
|
+
return {} unless instance
|
|
47
|
+
|
|
48
|
+
disks = Array(instance.disks).map { |disk| disk.split(":")[0] }
|
|
49
|
+
disks.delete("ide2") if exclude_cdrom
|
|
50
|
+
network_interfaces = include_network ? Array(instance.interfaces).map(&:id) : []
|
|
51
|
+
boot_devices = network_interfaces + disks
|
|
52
|
+
|
|
53
|
+
return {} if boot_devices.empty?
|
|
54
|
+
|
|
55
|
+
{ boot: "order=" + boot_devices.join(";") }
|
|
56
|
+
end
|
|
57
|
+
|
|
37
58
|
# Convert a foreman form server/container vm hash into a fog-proxmox server/container attributes hash
|
|
38
59
|
def parse_typed_vm(args, type)
|
|
60
|
+
args = process_firmware_attributes(args, type)
|
|
39
61
|
args = ActiveSupport::HashWithIndifferentAccess.new(args)
|
|
40
62
|
return {} unless args
|
|
41
63
|
return {} if args.empty?
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file is part of ForemanFogProxmox.
|
|
4
|
+
|
|
5
|
+
# ForemanFogProxmox is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# ForemanFogProxmox is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
require 'fog/proxmox/helpers/disk_helper'
|
|
19
|
+
|
|
20
|
+
module ProxmoxVMImageTemplateHelper
|
|
21
|
+
def validate_image_template_disk_slots!(image, args)
|
|
22
|
+
reserved_slots = image.config.disks.select(&:hard_disk?).map(&:id)
|
|
23
|
+
limits = ProxmoxComputeControllersHelper::CONTROLLER_LIMITS
|
|
24
|
+
volumes = args['volumes_attributes']&.values || []
|
|
25
|
+
used_slots = reserved_slots + volumes.filter_map { |volume| hard_disk_slot(volume) }
|
|
26
|
+
|
|
27
|
+
volumes.each do |volume_attributes|
|
|
28
|
+
volume = volume_attributes.with_indifferent_access
|
|
29
|
+
slot = hard_disk_slot(volume_attributes)
|
|
30
|
+
next unless slot
|
|
31
|
+
|
|
32
|
+
controller = volume[:controller]
|
|
33
|
+
if reserved_slots.include?(slot)
|
|
34
|
+
device = (0..limits.fetch(controller, -1)).find do |number|
|
|
35
|
+
!used_slots.include?("#{controller}#{number}")
|
|
36
|
+
end
|
|
37
|
+
unless device
|
|
38
|
+
raise ::Foreman::Exception,
|
|
39
|
+
format(_('No free disk device is available for the %<controller>s controller.'), controller: controller)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
volume_attributes['device'] = device.to_s
|
|
43
|
+
volume_attributes['id'] = slot = "#{controller}#{device}"
|
|
44
|
+
end
|
|
45
|
+
used_slots << slot
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def hard_disk_slot(attributes)
|
|
50
|
+
volume = attributes.with_indifferent_access
|
|
51
|
+
id = volume[:id]
|
|
52
|
+
id if volume_type?(volume, 'hard_disk') &&
|
|
53
|
+
Fog::Proxmox::DiskHelper.server_disk?(id)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -44,6 +44,15 @@ module FogExtensions
|
|
|
44
44
|
def cloud_init?
|
|
45
45
|
disks.any?(&:cloud_init?)
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
def secure_boot?
|
|
49
|
+
attrs = attributes.with_indifferent_access
|
|
50
|
+
secure_boot = attrs[:is_secure_boot]
|
|
51
|
+
return Foreman::Cast.to_bool(secure_boot) ? '1' : '0' if attrs.key?(:is_secure_boot)
|
|
52
|
+
|
|
53
|
+
secure_boot = bios == 'ovmf' && Foreman::Cast.to_bool(efidisk&.pre_enrolled_keys)
|
|
54
|
+
Foreman::Cast.to_bool(secure_boot) ? '1' : '0'
|
|
55
|
+
end
|
|
47
56
|
end
|
|
48
57
|
end
|
|
49
58
|
end
|
|
@@ -21,9 +21,17 @@ module ForemanFogProxmox
|
|
|
21
21
|
module ProxmoxEfidisks
|
|
22
22
|
include ProxmoxVMHelper
|
|
23
23
|
|
|
24
|
-
def delete_efidisk(
|
|
25
|
-
logger.info("vm #{
|
|
26
|
-
|
|
24
|
+
def delete_efidisk(vm_obj)
|
|
25
|
+
logger.info("vm #{vm_obj.identity} delete efidisk0")
|
|
26
|
+
vm_obj.detach('efidisk0')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_efidisk_removal(vm_obj, attributes)
|
|
30
|
+
return if vm_obj.config.efidisk.blank?
|
|
31
|
+
return if attributes['efidisk_attributes'].present?
|
|
32
|
+
|
|
33
|
+
logger.debug("Removing efidisk from VM #{vm_obj}")
|
|
34
|
+
delete_efidisk(vm_obj)
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
end
|
|
@@ -70,9 +70,8 @@ module ForemanFogProxmox
|
|
|
70
70
|
find_vm_by_uuid(uuid)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
def clone_from_image(
|
|
74
|
-
logger.debug("create_vm(): clone #{
|
|
75
|
-
image = find_vm_by_uuid(image_id)
|
|
73
|
+
def clone_from_image(image, vmid)
|
|
74
|
+
logger.debug("create_vm(): clone #{image.identity} in #{vmid}")
|
|
76
75
|
image.clone(vmid)
|
|
77
76
|
find_vm_by_uuid(id.to_s + '_' + vmid.to_s)
|
|
78
77
|
end
|
|
@@ -39,12 +39,18 @@ module ForemanFogProxmox
|
|
|
39
39
|
image_id = args[:image_id]
|
|
40
40
|
remove_volume_keys(args)
|
|
41
41
|
if image_id
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
image = find_vm_by_uuid(image_id)
|
|
43
|
+
validate_image_template_disk_slots!(image, args) if type == 'qemu'
|
|
44
|
+
vm = clone_from_image(image, vmid)
|
|
45
|
+
vm.update(compute_clone_attributes(args, vm.container?, type, image: image))
|
|
44
46
|
update_pool(vm, args[:pool]) if args[:pool]
|
|
45
47
|
else
|
|
46
48
|
logger.warn("create vm: args=#{args}")
|
|
47
49
|
vm = node.send(vm_collection(type)).create(parse_typed_vm(args, type))
|
|
50
|
+
if type == 'qemu' && vm
|
|
51
|
+
boot_order = update_boot_order(vm, exclude_cdrom: true, include_network: true)
|
|
52
|
+
vm.update(boot_order) if boot_order
|
|
53
|
+
end
|
|
48
54
|
end
|
|
49
55
|
start_on_boot(vm, args)
|
|
50
56
|
rescue StandardError => e
|
|
@@ -64,8 +70,9 @@ module ForemanFogProxmox
|
|
|
64
70
|
vmid
|
|
65
71
|
end
|
|
66
72
|
|
|
67
|
-
def compute_clone_attributes(args, container, type)
|
|
73
|
+
def compute_clone_attributes(args, container, type, image: nil)
|
|
68
74
|
args = parse_cloudinit_config(args) if args[:user_data]
|
|
75
|
+
args[:config_attributes].merge!(update_boot_order(image)) if image && args[:config_attributes]
|
|
69
76
|
parsed_args = parse_typed_vm(args, type)
|
|
70
77
|
if container
|
|
71
78
|
options = { :hostname => args[:name] }
|
|
@@ -110,10 +117,9 @@ module ForemanFogProxmox
|
|
|
110
117
|
elsif vm.node_id != node_id
|
|
111
118
|
vm.migrate(node_id)
|
|
112
119
|
else
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
120
|
+
processed_attributes = process_firmware_attributes(new_attributes, vm.type)
|
|
121
|
+
update_attributes = parse_vm_update_attributes(processed_attributes, vm.type)
|
|
122
|
+
parsed_attr = parse_typed_vm(update_attributes, vm.type)
|
|
117
123
|
config_attributes = compute_config_attributes(parsed_attr)
|
|
118
124
|
|
|
119
125
|
volumes_attributes = new_attributes['volumes_attributes']
|
|
@@ -123,11 +129,7 @@ module ForemanFogProxmox
|
|
|
123
129
|
save_volume(vm, volume_attributes)
|
|
124
130
|
end
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
if vm.config.efidisk.present? && efidisk_attributes.empty?
|
|
128
|
-
logger.debug("Removing efidisk from VM #{vm}")
|
|
129
|
-
delete_efidisk(vm)
|
|
130
|
-
end
|
|
132
|
+
process_efidisk_removal(vm, processed_attributes)
|
|
131
133
|
|
|
132
134
|
vm.update(config_attributes[:config_attributes])
|
|
133
135
|
poolid = new_attributes['pool'] if new_attributes.key?('pool')
|
|
@@ -190,6 +190,11 @@ module ForemanFogProxmox
|
|
|
190
190
|
new_attr['vmid'] = assign_vmid(new_attr['vmid'].to_i, node, log: false)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
+
def add_secure_boot_attribute(vm_h, options, type)
|
|
194
|
+
secure_boot = options.dig('config_attributes', 'is_secure_boot')
|
|
195
|
+
vm_h[:is_secure_boot] = secure_boot if type == 'qemu' && secure_boot
|
|
196
|
+
end
|
|
197
|
+
|
|
193
198
|
def new_typed_vm(new_attr, type)
|
|
194
199
|
convert_config_attributes(new_attr) if new_attr.key?(:config_attributes)
|
|
195
200
|
node_id = new_attr['node_id']
|
|
@@ -201,8 +206,10 @@ module ForemanFogProxmox
|
|
|
201
206
|
logger.debug("new_typed_vm(#{type}): new_attr_type=#{new_attr_type}")
|
|
202
207
|
logger.debug("new_typed_vm(#{type}): new_attr=#{new_attr}'")
|
|
203
208
|
options = (!new_attr.key?('vmid') || ForemanFogProxmox::Value.empty?(new_attr['vmid'])) ? vm_typed_instance_defaults(type).merge(new_attr).merge(type: type) : new_attr
|
|
209
|
+
options = process_firmware_attributes(options, type)
|
|
204
210
|
logger.debug("new_typed_vm(#{type}): options=#{options}")
|
|
205
211
|
vm_h = parse_typed_vm(options, type).deep_symbolize_keys
|
|
212
|
+
add_secure_boot_attribute(vm_h, options, type)
|
|
206
213
|
logger.debug("new_typed_vm(#{type}): vm_h=#{vm_h}")
|
|
207
214
|
vm_h = vm_h.merge(vm_typed_instance_defaults(type)) if vm_h.empty?
|
|
208
215
|
logger.debug(format(_('new_typed_vm(%<type>s) with vm_typed_instance_defaults: vm_h=%<vm_h>s'), type: type, vm_h: vm_h))
|
|
@@ -137,6 +137,106 @@ module ForemanFogProxmox
|
|
|
137
137
|
} }
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
describe '#parse_vm_update_attributes' do
|
|
141
|
+
it 'removes volume attributes and adds the VM type without parsing' do
|
|
142
|
+
attributes = parse_vm_update_attributes(host_server, 'qemu')
|
|
143
|
+
|
|
144
|
+
assert_not attributes.key?('volumes_attributes')
|
|
145
|
+
assert_equal 'qemu', attributes['type']
|
|
146
|
+
assert_equal host_server['config_attributes'], attributes['config_attributes']
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe '#parse_typed_vm firmware attributes' do
|
|
151
|
+
it 'maps secure boot to OVMF and adds a default EFI disk' do
|
|
152
|
+
host_server['config_attributes']['bios'] = 'uefi_secure_boot'
|
|
153
|
+
|
|
154
|
+
parsed_vm = parse_typed_vm(host_server, 'qemu')
|
|
155
|
+
|
|
156
|
+
assert_equal 'ovmf', parsed_vm['bios']
|
|
157
|
+
assert_equal 'local-lvm:0,efitype=4m,pre-enrolled-keys=1', parsed_vm['efidisk0']
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'enables secure boot on an explicitly configured EFI disk' do
|
|
161
|
+
host_server['config_attributes']['bios'] = 'uefi_secure_boot'
|
|
162
|
+
host_server['efidisk_attributes'] = {
|
|
163
|
+
'id' => '0',
|
|
164
|
+
'volid' => 'fast-storage:0',
|
|
165
|
+
'efitype' => '2m',
|
|
166
|
+
'pre_enrolled_keys' => '0',
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
parsed_vm = parse_typed_vm(host_server, 'qemu')
|
|
170
|
+
|
|
171
|
+
assert_equal 'ovmf', parsed_vm['bios']
|
|
172
|
+
assert_equal 'fast-storage:0,efitype=4m,pre-enrolled-keys=1', parsed_vm['efidisk0']
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'disables pre-enrolled keys on an existing OVMF EFI disk' do
|
|
176
|
+
host_server['config_attributes']['bios'] = 'ovmf'
|
|
177
|
+
host_server['efidisk_attributes'] = {
|
|
178
|
+
'id' => '0',
|
|
179
|
+
'volid' => 'fast-storage:0',
|
|
180
|
+
'efitype' => '4m',
|
|
181
|
+
'pre_enrolled_keys' => '1',
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
parsed_vm = parse_typed_vm(host_server, 'qemu')
|
|
185
|
+
|
|
186
|
+
assert_equal 'ovmf', parsed_vm['bios']
|
|
187
|
+
assert_equal 'fast-storage:0,efitype=4m,pre-enrolled-keys=0', parsed_vm['efidisk0']
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'disables pre-enrolled keys for another BIOS' do
|
|
191
|
+
host_server['config_attributes']['bios'] = 'seabios'
|
|
192
|
+
host_server['efidisk_attributes'] = {
|
|
193
|
+
'id' => '0',
|
|
194
|
+
'volid' => 'fast-storage:0',
|
|
195
|
+
'efitype' => '4m',
|
|
196
|
+
'pre_enrolled_keys' => '1',
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
parsed_vm = parse_typed_vm(host_server, 'qemu')
|
|
200
|
+
|
|
201
|
+
assert_equal 'seabios', parsed_vm['bios']
|
|
202
|
+
assert_equal 'fast-storage:0,efitype=4m,pre-enrolled-keys=0', parsed_vm['efidisk0']
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe 'update_boot_order' do
|
|
207
|
+
let(:vm) do
|
|
208
|
+
vm = mock('vm')
|
|
209
|
+
vm.stubs(:disks).returns(['scsi0: local-lvm:vm-100-disk-0', 'ide2: none,media=cdrom', 'virtio1: local-lvm:vm-100-disk-1'])
|
|
210
|
+
net0 = mock('net0')
|
|
211
|
+
net1 = mock('net1')
|
|
212
|
+
net0.stubs(:id).returns('net0')
|
|
213
|
+
net1.stubs(:id).returns('net1')
|
|
214
|
+
vm.stubs(:interfaces).returns([net0, net1])
|
|
215
|
+
vm
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'generates the image provisioning boot order from all disks' do
|
|
219
|
+
assert_equal({ boot: 'order=scsi0;ide2;virtio1' }, update_boot_order(vm))
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'includes network interfaces and excludes the default CD-ROM for network provisioning' do
|
|
223
|
+
expected_boot_order = { boot: 'order=net0;net1;scsi0;virtio1' }
|
|
224
|
+
|
|
225
|
+
assert_equal expected_boot_order, update_boot_order(vm, exclude_cdrom: true, include_network: true)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it 'returns an empty hash when the VM is missing' do
|
|
229
|
+
assert_empty(update_boot_order(nil))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'returns an empty hash when the VM has no disks' do
|
|
233
|
+
vm = mock('vm')
|
|
234
|
+
vm.stubs(:disks).returns(nil)
|
|
235
|
+
|
|
236
|
+
assert_empty(update_boot_order(vm))
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
140
240
|
describe 'object_to_config_hash' do
|
|
141
241
|
setup { Fog.mock! }
|
|
142
242
|
teardown { Fog.unmock! }
|
|
@@ -27,22 +27,20 @@ module ForemanFogProxmox
|
|
|
27
27
|
describe 'clone_from_image' do
|
|
28
28
|
before do
|
|
29
29
|
@cr = FactoryBot.build_stubbed(:proxmox_cr)
|
|
30
|
-
@image_id = @cr.id.to_s + '_' + 100.to_s
|
|
31
30
|
@vmid = 101
|
|
32
|
-
@image = mock('vm')
|
|
31
|
+
@image = mock('vm', identity: '100')
|
|
33
32
|
@image.expects(:clone)
|
|
34
|
-
@cr.stubs(:find_vm_by_uuid).with(@image_id).returns(@image)
|
|
35
33
|
@clone = mock('vm')
|
|
36
34
|
end
|
|
37
35
|
it 'clones server from image' do
|
|
38
36
|
@clone.stubs(:container?).returns(false)
|
|
39
37
|
@cr.stubs(:find_vm_by_uuid).with(@cr.id.to_s + '_' + @vmid.to_s).returns(@clone)
|
|
40
|
-
@cr.clone_from_image(@
|
|
38
|
+
@cr.clone_from_image(@image, @vmid)
|
|
41
39
|
end
|
|
42
40
|
it 'clones container from image' do
|
|
43
41
|
@clone.stubs(:container?).returns(true)
|
|
44
42
|
@cr.stubs(:find_vm_by_uuid).with(@cr.id.to_s + '_' + @vmid.to_s).returns(@clone)
|
|
45
|
-
@cr.clone_from_image(@
|
|
43
|
+
@cr.clone_from_image(@image, @vmid)
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
end
|
|
@@ -299,7 +299,9 @@ module ForemanFogProxmox
|
|
|
299
299
|
containers = mock('containers')
|
|
300
300
|
cr = mock_node_servers_containers(ForemanFogProxmox::Proxmox.new, servers, containers)
|
|
301
301
|
vm = mock('vm')
|
|
302
|
-
|
|
302
|
+
image = mock('image')
|
|
303
|
+
cr.stubs(:find_vm_by_uuid).with('999').returns(image)
|
|
304
|
+
cr.expects(:clone_from_image).with(image, 100).returns(vm)
|
|
303
305
|
vm.expects(:container?).returns(true)
|
|
304
306
|
expected_args = { :vmid => "100", :type => "lxc" }
|
|
305
307
|
cr.stubs(:parse_typed_vm).with(args, 'lxc').returns(expected_args)
|
|
@@ -38,6 +38,7 @@ module ForemanFogProxmox
|
|
|
38
38
|
servers.expects(:next_id).returns('101')
|
|
39
39
|
cr = mock_node_servers(ForemanFogProxmox::Proxmox.new, servers)
|
|
40
40
|
cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(args)
|
|
41
|
+
cr.stubs(:update_boot_order).returns(nil)
|
|
41
42
|
vm = mock('vm')
|
|
42
43
|
servers.expects(:create).with(args).returns(vm)
|
|
43
44
|
cr.create_vm(args)
|
|
@@ -70,12 +71,27 @@ module ForemanFogProxmox
|
|
|
70
71
|
cr.create_vm(args)
|
|
71
72
|
end
|
|
72
73
|
|
|
74
|
+
it 'updates the boot order after creating a network-provisioned server' do
|
|
75
|
+
args = { vmid: '100', type: 'qemu', node_id: 'proxmox', start_after_create: '0' }
|
|
76
|
+
servers = mock('servers')
|
|
77
|
+
servers.stubs(:id_valid?).returns(true)
|
|
78
|
+
vm = mock('vm')
|
|
79
|
+
servers.expects(:create).with(args).returns(vm)
|
|
80
|
+
cr = mock_node_servers(ForemanFogProxmox::Proxmox.new, servers)
|
|
81
|
+
cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(args)
|
|
82
|
+
cr.expects(:update_boot_order).with(vm, exclude_cdrom: true, include_network: true).returns(boot: 'order=net0;scsi0;virtio1')
|
|
83
|
+
vm.expects(:update).with({ boot: 'order=net0;scsi0;virtio1' })
|
|
84
|
+
|
|
85
|
+
cr.create_vm(args)
|
|
86
|
+
end
|
|
87
|
+
|
|
73
88
|
it 'creates server with bootstart' do
|
|
74
89
|
args = { vmid: '100', type: 'qemu', node_id: 'proxmox', start_after_create: '1' }
|
|
75
90
|
servers = mock('servers')
|
|
76
91
|
servers.stubs(:id_valid?).returns(true)
|
|
77
92
|
cr = mock_node_servers(ForemanFogProxmox::Proxmox.new, servers)
|
|
78
93
|
cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(args)
|
|
94
|
+
cr.stubs(:update_boot_order).returns(nil)
|
|
79
95
|
vm = mock('vm')
|
|
80
96
|
servers.stubs(:create).with(args).returns(vm)
|
|
81
97
|
cr.stubs(:find_vm_by_uuid).with((args[:vmid]).to_s).returns(vm)
|
|
@@ -89,6 +105,7 @@ module ForemanFogProxmox
|
|
|
89
105
|
servers.stubs(:id_valid?).returns(true)
|
|
90
106
|
cr = mock_node_servers(ForemanFogProxmox::Proxmox.new, servers)
|
|
91
107
|
cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(args)
|
|
108
|
+
cr.stubs(:update_boot_order).returns(nil)
|
|
92
109
|
vm = mock('vm')
|
|
93
110
|
servers.stubs(:create).with(args).returns(vm)
|
|
94
111
|
cr.stubs(:find_vm_by_uuid).with((args[:vmid]).to_s).returns(vm)
|
|
@@ -103,7 +120,9 @@ module ForemanFogProxmox
|
|
|
103
120
|
servers.stubs(:id_valid?).returns(true)
|
|
104
121
|
cr = mock_node_servers_containers(ForemanFogProxmox::Proxmox.new, servers, containers)
|
|
105
122
|
vm = mock('vm')
|
|
106
|
-
|
|
123
|
+
image = mock('image', config: mock('config', disks: []))
|
|
124
|
+
cr.stubs(:find_vm_by_uuid).with('999').returns(image)
|
|
125
|
+
cr.expects(:clone_from_image).with(image, 100).returns(vm)
|
|
107
126
|
vm.expects(:container?).returns(false)
|
|
108
127
|
expected_args = { :vmid => "100", :type => "qemu", :name => "name" }
|
|
109
128
|
cr.stubs(:parse_typed_vm).with(args, 'qemu').returns(expected_args)
|
|
@@ -111,6 +130,26 @@ module ForemanFogProxmox
|
|
|
111
130
|
cr.create_vm(args)
|
|
112
131
|
end
|
|
113
132
|
|
|
133
|
+
it 'updates the boot order when cloning without user data' do
|
|
134
|
+
args = { vmid: '100', type: 'qemu', image_id: '999', name: 'name', config_attributes: { onboot: '0' } }
|
|
135
|
+
servers = mock('servers')
|
|
136
|
+
containers = mock('containers')
|
|
137
|
+
servers.stubs(:id_valid?).returns(true)
|
|
138
|
+
cr = mock_node_servers_containers(ForemanFogProxmox::Proxmox.new, servers, containers)
|
|
139
|
+
image = mock('image', config: mock('config', disks: []))
|
|
140
|
+
vm = mock('vm')
|
|
141
|
+
cr.expects(:clone_from_image).with(image, 100).returns(vm)
|
|
142
|
+
vm.expects(:container?).returns(false)
|
|
143
|
+
cr.expects(:parse_cloudinit_config).never
|
|
144
|
+
cr.expects(:find_vm_by_uuid).with('999').once.returns(image)
|
|
145
|
+
cr.expects(:update_boot_order).with(image).returns(boot: 'order=scsi0;virtio1')
|
|
146
|
+
expected_args = { vmid: '100', type: 'qemu', name: 'name', config_attributes: { onboot: '0', boot: 'order=scsi0;virtio1' } }
|
|
147
|
+
cr.expects(:parse_typed_vm).with(args, 'qemu').returns(expected_args)
|
|
148
|
+
vm.expects(:update).with(expected_args)
|
|
149
|
+
|
|
150
|
+
cr.create_vm(args)
|
|
151
|
+
end
|
|
152
|
+
|
|
114
153
|
it 'attaches generated cloud-init ISO from a later ISO storage' do
|
|
115
154
|
cr = ForemanFogProxmox::Proxmox.new
|
|
116
155
|
first_storage = mock('first_storage')
|
|
@@ -142,5 +181,68 @@ module ForemanFogProxmox
|
|
|
142
181
|
assert err.message.end_with?('Could not find generated cloud-init ISO name_cloudinit.iso on any ISO storage for node proxmox')
|
|
143
182
|
end
|
|
144
183
|
end
|
|
184
|
+
|
|
185
|
+
describe 'validate_image_template_disk_slots!' do
|
|
186
|
+
it 'moves a disk that conflicts with an image template disk to the next free slot' do
|
|
187
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
188
|
+
disk = mock('disk', hard_disk?: true, id: 'scsi0')
|
|
189
|
+
image = mock('image', config: mock('config', disks: [disk]))
|
|
190
|
+
args = {
|
|
191
|
+
'volumes_attributes' => {
|
|
192
|
+
'0' => { 'storage_type' => 'hard_disk', 'controller' => 'scsi', 'device' => '0', 'id' => 'scsi0' },
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
cr.validate_image_template_disk_slots!(image, args)
|
|
196
|
+
|
|
197
|
+
assert_equal '1', args['volumes_attributes']['0']['device']
|
|
198
|
+
assert_equal 'scsi1', args['volumes_attributes']['0']['id']
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'uses a free slot before the conflicting slot' do
|
|
202
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
203
|
+
disk = mock('disk', hard_disk?: true, id: 'scsi1')
|
|
204
|
+
image = mock('image', config: mock('config', disks: [disk]))
|
|
205
|
+
args = {
|
|
206
|
+
'volumes_attributes' => {
|
|
207
|
+
'0' => { 'storage_type' => 'hard_disk', 'controller' => 'scsi', 'device' => '1', 'id' => 'scsi1' },
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
cr.validate_image_template_disk_slots!(image, args)
|
|
211
|
+
|
|
212
|
+
assert_equal '0', args['volumes_attributes']['0']['device']
|
|
213
|
+
assert_equal 'scsi0', args['volumes_attributes']['0']['id']
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'leaves a disk unchanged when its slot does not conflict with an image template disk' do
|
|
217
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
218
|
+
disk = mock('disk', hard_disk?: true, id: 'scsi0')
|
|
219
|
+
image = mock('image', config: mock('config', disks: [disk]))
|
|
220
|
+
args = {
|
|
221
|
+
'volumes_attributes' => {
|
|
222
|
+
'0' => { 'storage_type' => 'hard_disk', 'controller' => 'scsi', 'device' => '1', 'id' => 'scsi1' },
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
original_args = args.deep_dup
|
|
226
|
+
cr.validate_image_template_disk_slots!(image, args)
|
|
227
|
+
|
|
228
|
+
assert_equal original_args, args
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'raises when no free disk slot is available for the controller' do
|
|
232
|
+
cr = ForemanFogProxmox::Proxmox.new
|
|
233
|
+
disks = (0..3).map { |device| mock("disk#{device}", hard_disk?: true, id: "ide#{device}") }
|
|
234
|
+
image = mock('image', config: mock('config', disks: disks))
|
|
235
|
+
args = {
|
|
236
|
+
'volumes_attributes' => {
|
|
237
|
+
'0' => { 'storage_type' => 'hard_disk', 'controller' => 'ide', 'device' => '3', 'id' => 'ide3' },
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
error = assert_raises Foreman::Exception do
|
|
241
|
+
cr.validate_image_template_disk_slots!(image, args)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
assert_equal 'No free disk device is available for the ide controller.', error.bare_message
|
|
245
|
+
end
|
|
246
|
+
end
|
|
145
247
|
end
|
|
146
248
|
end
|
|
@@ -110,6 +110,36 @@ module ForemanFogProxmox
|
|
|
110
110
|
@cr.stubs(:new_typed_vm).with(attr, 'lxc').returns(vm)
|
|
111
111
|
assert_equal vm, @cr.new_vm(attr)
|
|
112
112
|
end
|
|
113
|
+
|
|
114
|
+
it 'processes secure boot firmware attributes before building profile VM' do
|
|
115
|
+
attr = {
|
|
116
|
+
'vmid' => '100',
|
|
117
|
+
'node_id' => 'proxmox',
|
|
118
|
+
'type' => 'qemu',
|
|
119
|
+
'config_attributes' => {
|
|
120
|
+
'bios' => 'uefi_secure_boot',
|
|
121
|
+
},
|
|
122
|
+
'volumes_attributes' => {
|
|
123
|
+
'0' => { 'storage' => 'local-lvm' },
|
|
124
|
+
},
|
|
125
|
+
}.with_indifferent_access
|
|
126
|
+
servers = mock('servers')
|
|
127
|
+
vm = mock('vm')
|
|
128
|
+
mock_node_servers(@cr, servers)
|
|
129
|
+
servers.stubs(:id_valid?).with(100).returns(true)
|
|
130
|
+
@cr.expects(:parse_typed_vm).with do |options, type|
|
|
131
|
+
assert_equal 'qemu', type
|
|
132
|
+
assert_equal 'ovmf', options['config_attributes']['bios']
|
|
133
|
+
assert_equal 'local-lvm:0', options['efidisk_attributes']['volid']
|
|
134
|
+
end.returns('bios' => 'ovmf', 'efidisk0' => 'local-lvm:0,efitype=4m,pre-enrolled-keys=1')
|
|
135
|
+
servers.expects(:new).with({
|
|
136
|
+
bios: 'ovmf',
|
|
137
|
+
efidisk0: 'local-lvm:0,efitype=4m,pre-enrolled-keys=1',
|
|
138
|
+
is_secure_boot: '1',
|
|
139
|
+
}).returns(vm)
|
|
140
|
+
|
|
141
|
+
assert_equal vm, @cr.new_typed_vm(attr, 'qemu')
|
|
142
|
+
end
|
|
113
143
|
end
|
|
114
144
|
|
|
115
145
|
describe 'assign_available_vmid' do
|
|
@@ -3,11 +3,20 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
|
|
4
4
|
const ProxmoxBiosContext = createContext(null);
|
|
5
5
|
|
|
6
|
-
export function ProxmoxBiosProvider({
|
|
6
|
+
export function ProxmoxBiosProvider({
|
|
7
|
+
children,
|
|
8
|
+
initialBios = null,
|
|
9
|
+
initialEfiDiskSelected = false,
|
|
10
|
+
}) {
|
|
7
11
|
const [bios, setBios] = useState(initialBios);
|
|
12
|
+
const [efiDiskSelected, setEfiDiskSelected] = useState(
|
|
13
|
+
initialEfiDiskSelected
|
|
14
|
+
);
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
const value = useMemo(
|
|
17
|
+
() => ({ bios, setBios, efiDiskSelected, setEfiDiskSelected }),
|
|
18
|
+
[bios, efiDiskSelected]
|
|
19
|
+
);
|
|
11
20
|
|
|
12
21
|
return (
|
|
13
22
|
<ProxmoxBiosContext.Provider value={value}>
|
|
@@ -26,8 +35,10 @@ export function useBios() {
|
|
|
26
35
|
ProxmoxBiosProvider.propTypes = {
|
|
27
36
|
children: PropTypes.object.isRequired,
|
|
28
37
|
initialBios: PropTypes.string,
|
|
38
|
+
initialEfiDiskSelected: PropTypes.bool,
|
|
29
39
|
};
|
|
30
40
|
|
|
31
41
|
ProxmoxBiosProvider.defaultProps = {
|
|
32
42
|
initialBios: null,
|
|
43
|
+
initialEfiDiskSelected: false,
|
|
33
44
|
};
|
|
@@ -4,10 +4,11 @@ import { translate as __ } from 'foremanReact/common/I18n';
|
|
|
4
4
|
import InputField from '../common/FormInputs';
|
|
5
5
|
import ProxmoxComputeSelectors from '../ProxmoxComputeSelectors';
|
|
6
6
|
import { useBios } from '../ProxmoxBiosContext';
|
|
7
|
+
import SecureBoot, { normalizeFirmwareOptions } from './components/SecureBoot';
|
|
7
8
|
|
|
8
9
|
const ProxmoxServerOptions = ({ options }) => {
|
|
9
|
-
const [opts, setOpts] = useState(options);
|
|
10
|
-
const { setBios } = useBios();
|
|
10
|
+
const [opts, setOpts] = useState(normalizeFirmwareOptions(options));
|
|
11
|
+
const { efiDiskSelected, setBios } = useBios();
|
|
11
12
|
|
|
12
13
|
const handleChange = e => {
|
|
13
14
|
const { name, type, checked, value: targetValue } = e.target;
|
|
@@ -18,14 +19,21 @@ const ProxmoxServerOptions = ({ options }) => {
|
|
|
18
19
|
value = targetValue;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
const updatedKey = Object.keys(opts).find(key => opts[key].name === name);
|
|
23
|
+
const updates = {
|
|
24
|
+
[updatedKey]: { ...opts[updatedKey], value },
|
|
25
|
+
};
|
|
21
26
|
if (name === opts?.bios?.name) {
|
|
27
|
+
updates.isSecureBoot = {
|
|
28
|
+
...opts.isSecureBoot,
|
|
29
|
+
value: value === 'uefi_secure_boot' ? '1' : '0',
|
|
30
|
+
};
|
|
22
31
|
setBios(value);
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
const updatedKey = Object.keys(opts).find(key => opts[key].name === name);
|
|
26
34
|
setOpts(prevOpts => ({
|
|
27
35
|
...prevOpts,
|
|
28
|
-
|
|
36
|
+
...updates,
|
|
29
37
|
}));
|
|
30
38
|
};
|
|
31
39
|
|
|
@@ -94,6 +102,11 @@ const ProxmoxServerOptions = ({ options }) => {
|
|
|
94
102
|
value={opts?.bios?.value}
|
|
95
103
|
onChange={handleChange}
|
|
96
104
|
/>
|
|
105
|
+
<SecureBoot
|
|
106
|
+
bios={opts?.bios}
|
|
107
|
+
isSecureBoot={opts?.isSecureBoot}
|
|
108
|
+
efiDiskSelected={efiDiskSelected}
|
|
109
|
+
/>
|
|
97
110
|
<InputField
|
|
98
111
|
name={opts?.ostype?.name}
|
|
99
112
|
label={__('OS Type')}
|
|
@@ -18,6 +18,7 @@ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
|
|
18
18
|
import HardDisk from './components/HardDisk';
|
|
19
19
|
import CDRom from './components/CDRom';
|
|
20
20
|
import EFIDisk from './components/EFIDisk';
|
|
21
|
+
import { useBios } from '../ProxmoxBiosContext';
|
|
21
22
|
|
|
22
23
|
const ProxmoxServerStorage = ({
|
|
23
24
|
storage,
|
|
@@ -75,6 +76,7 @@ const ProxmoxServerStorage = ({
|
|
|
75
76
|
const [cdRomIsNew, setCdRomIsNew] = useState(false);
|
|
76
77
|
const [efiDisk, setEfiDisk] = useState(false);
|
|
77
78
|
const [efiDiskData, setEfiDiskData] = useState(null);
|
|
79
|
+
const { setEfiDiskSelected } = useBios();
|
|
78
80
|
const [nextDeviceNumbers, setNextDeviceNumbers] = useState({
|
|
79
81
|
ide: 0,
|
|
80
82
|
sata: 0,
|
|
@@ -381,13 +383,15 @@ const ProxmoxServerStorage = ({
|
|
|
381
383
|
}
|
|
382
384
|
|
|
383
385
|
setEfiDisk(true);
|
|
386
|
+
setEfiDiskSelected(true);
|
|
384
387
|
setEfiDiskData(initEfiDisk);
|
|
385
388
|
},
|
|
386
|
-
[efiDisk, paramScope]
|
|
389
|
+
[efiDisk, paramScope, setEfiDiskSelected]
|
|
387
390
|
);
|
|
388
391
|
|
|
389
392
|
const removeEfiDisk = () => {
|
|
390
393
|
setEfiDisk(false);
|
|
394
|
+
setEfiDiskSelected(false);
|
|
391
395
|
};
|
|
392
396
|
|
|
393
397
|
if (isLoading) {
|
|
@@ -19,7 +19,8 @@ const EFIDisk = ({ onRemove, data, storages, nodeId, vmId }) => {
|
|
|
19
19
|
const [efidisk, setEfiDisk] = useState(data);
|
|
20
20
|
const storagesMap = createStoragesMap(storages, null, nodeId);
|
|
21
21
|
const { bios } = useBios();
|
|
22
|
-
const isBiosOvmf =
|
|
22
|
+
const isBiosOvmf = ['ovmf', 'uefi_secure_boot'].includes(bios);
|
|
23
|
+
const secureBootEnabled = bios === 'uefi_secure_boot';
|
|
23
24
|
|
|
24
25
|
useEffect(() => {
|
|
25
26
|
if (storagesMap?.length > 0 && !efidisk?.volid?.value) {
|
|
@@ -136,12 +137,20 @@ const EFIDisk = ({ onRemove, data, storages, nodeId, vmId }) => {
|
|
|
136
137
|
onChange={handleChange}
|
|
137
138
|
/>
|
|
138
139
|
<InputField
|
|
139
|
-
name={efidisk?.preEnrolledKeys?.name}
|
|
140
140
|
label={__('Pre-Enrolled Keys')}
|
|
141
|
+
info={__(
|
|
142
|
+
'This checkbox is read-only and automatically checked when UEFI Secure Boot is selected'
|
|
143
|
+
)}
|
|
141
144
|
type="checkbox"
|
|
142
|
-
value={
|
|
143
|
-
checked={
|
|
145
|
+
value={secureBootEnabled ? '1' : '0'}
|
|
146
|
+
checked={secureBootEnabled}
|
|
144
147
|
onChange={handleChange}
|
|
148
|
+
disabled
|
|
149
|
+
/>
|
|
150
|
+
<input
|
|
151
|
+
name={efidisk?.preEnrolledKeys?.name}
|
|
152
|
+
type="hidden"
|
|
153
|
+
value={secureBootEnabled ? '1' : '0'}
|
|
145
154
|
/>
|
|
146
155
|
</PageSection>
|
|
147
156
|
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import {
|
|
4
|
+
FormHelperText,
|
|
5
|
+
HelperText,
|
|
6
|
+
HelperTextItem,
|
|
7
|
+
} from '@patternfly/react-core';
|
|
8
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
9
|
+
|
|
10
|
+
export const secureBootSelected = options =>
|
|
11
|
+
options?.bios?.value === 'uefi_secure_boot' ||
|
|
12
|
+
(options?.bios?.value === 'ovmf' &&
|
|
13
|
+
(String(options?.isSecureBoot?.value) === '1' ||
|
|
14
|
+
String(options?.efidisk?.preEnrolledKeys?.value) === '1'));
|
|
15
|
+
|
|
16
|
+
export const normalizeFirmwareOptions = options => {
|
|
17
|
+
const isSecureBootSelected = secureBootSelected(options);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...options,
|
|
21
|
+
bios: {
|
|
22
|
+
...options?.bios,
|
|
23
|
+
value: isSecureBootSelected ? 'uefi_secure_boot' : options?.bios?.value,
|
|
24
|
+
},
|
|
25
|
+
isSecureBoot: {
|
|
26
|
+
...options?.isSecureBoot,
|
|
27
|
+
value: isSecureBootSelected ? '1' : '0',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const SecureBoot = ({ bios, isSecureBoot, efiDiskSelected }) => (
|
|
33
|
+
<>
|
|
34
|
+
{bios?.value === 'uefi_secure_boot' && !efiDiskSelected && (
|
|
35
|
+
<div className="form-group">
|
|
36
|
+
<div className="col-md-offset-2 col-md-6">
|
|
37
|
+
<FormHelperText>
|
|
38
|
+
<HelperText id="helper-secure-boot-efidisk">
|
|
39
|
+
<HelperTextItem variant="warning">
|
|
40
|
+
{__('Please make sure that an EFI disk is selected.')}
|
|
41
|
+
</HelperTextItem>
|
|
42
|
+
</HelperText>
|
|
43
|
+
</FormHelperText>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
{isSecureBoot?.name && (
|
|
48
|
+
<input
|
|
49
|
+
name={isSecureBoot.name}
|
|
50
|
+
type="hidden"
|
|
51
|
+
value={isSecureBoot?.value || '0'}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
SecureBoot.propTypes = {
|
|
58
|
+
bios: PropTypes.object,
|
|
59
|
+
isSecureBoot: PropTypes.object,
|
|
60
|
+
efiDiskSelected: PropTypes.bool,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
SecureBoot.defaultProps = {
|
|
64
|
+
bios: {},
|
|
65
|
+
isSecureBoot: {},
|
|
66
|
+
efiDiskSelected: false,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default SecureBoot;
|
|
@@ -299,7 +299,11 @@ const ProxmoxVmType = ({
|
|
|
299
299
|
};
|
|
300
300
|
|
|
301
301
|
return (
|
|
302
|
-
<ProxmoxBiosProvider
|
|
302
|
+
<ProxmoxBiosProvider
|
|
303
|
+
initialEfiDiskSelected={
|
|
304
|
+
!!(vmAttrs?.efidisk && Object.keys(vmAttrs.efidisk).length > 0)
|
|
305
|
+
}
|
|
306
|
+
>
|
|
303
307
|
<div>
|
|
304
308
|
<InputField
|
|
305
309
|
name={general?.type?.name}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { ProxmoxBiosProvider } from '../ProxmoxBiosContext';
|
|
4
|
+
import EFIDisk from '../ProxmoxServer/components/EFIDisk';
|
|
5
|
+
|
|
6
|
+
const efiDiskData = {
|
|
7
|
+
id: { name: 'efidisk[id]', value: 0 },
|
|
8
|
+
volid: { name: 'efidisk[volid]', value: 'local:0' },
|
|
9
|
+
storage: { name: 'efidisk[storage]', value: 'local' },
|
|
10
|
+
format: { name: 'efidisk[format]', value: 'raw' },
|
|
11
|
+
preEnrolledKeys: { name: 'efidisk[pre_enrolled_keys]', value: '0' },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const renderEfiDisk = bios =>
|
|
15
|
+
render(
|
|
16
|
+
<ProxmoxBiosProvider initialBios={bios}>
|
|
17
|
+
<EFIDisk
|
|
18
|
+
onRemove={jest.fn()}
|
|
19
|
+
data={efiDiskData}
|
|
20
|
+
storages={[]}
|
|
21
|
+
nodeId="node-1"
|
|
22
|
+
vmId="100"
|
|
23
|
+
/>
|
|
24
|
+
</ProxmoxBiosProvider>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe('EFIDisk', () => {
|
|
28
|
+
it('checks and disables pre-enrolled keys for UEFI Secure Boot', async () => {
|
|
29
|
+
const { container } = renderEfiDisk('uefi_secure_boot');
|
|
30
|
+
|
|
31
|
+
const checkbox = screen.getByRole('checkbox');
|
|
32
|
+
const submittedValue = container.querySelector(
|
|
33
|
+
'input[name="efidisk[pre_enrolled_keys]"]'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(checkbox).toBeDisabled();
|
|
37
|
+
expect(checkbox).toBeChecked();
|
|
38
|
+
expect(submittedValue).toHaveValue('1');
|
|
39
|
+
fireEvent.click(container.querySelector('.field-help'));
|
|
40
|
+
expect(
|
|
41
|
+
await screen.findByText(
|
|
42
|
+
'This checkbox is read-only and automatically checked when UEFI Secure Boot is selected'
|
|
43
|
+
)
|
|
44
|
+
).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('unchecks and disables pre-enrolled keys without UEFI Secure Boot', () => {
|
|
48
|
+
const { container } = renderEfiDisk('ovmf');
|
|
49
|
+
|
|
50
|
+
const checkbox = screen.getByRole('checkbox');
|
|
51
|
+
const submittedValue = container.querySelector(
|
|
52
|
+
'input[name="efidisk[pre_enrolled_keys]"]'
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(checkbox).toBeDisabled();
|
|
56
|
+
expect(checkbox).not.toBeChecked();
|
|
57
|
+
expect(submittedValue).toHaveValue('0');
|
|
58
|
+
});
|
|
59
|
+
});
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_fog_proxmox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.25.
|
|
4
|
+
version: 0.25.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tristan Robert
|
|
@@ -140,6 +140,7 @@ files:
|
|
|
140
140
|
- app/helpers/proxmox_vm_config_helper.rb
|
|
141
141
|
- app/helpers/proxmox_vm_efidisk_helper.rb
|
|
142
142
|
- app/helpers/proxmox_vm_helper.rb
|
|
143
|
+
- app/helpers/proxmox_vm_image_template_helper.rb
|
|
143
144
|
- app/helpers/proxmox_vm_interfaces_helper.rb
|
|
144
145
|
- app/helpers/proxmox_vm_os_template_helper.rb
|
|
145
146
|
- app/helpers/proxmox_vm_uuid_helper.rb
|
|
@@ -295,9 +296,11 @@ files:
|
|
|
295
296
|
- webpack/components/ProxmoxServer/components/EFIDisk.js
|
|
296
297
|
- webpack/components/ProxmoxServer/components/HardDisk.js
|
|
297
298
|
- webpack/components/ProxmoxServer/components/NetworkInterface.js
|
|
299
|
+
- webpack/components/ProxmoxServer/components/SecureBoot.js
|
|
298
300
|
- webpack/components/ProxmoxStoragesUtils.js
|
|
299
301
|
- webpack/components/ProxmoxVmType.js
|
|
300
302
|
- webpack/components/ProxmoxVmUtils.js
|
|
303
|
+
- webpack/components/__tests__/EFIDisk.test.js
|
|
301
304
|
- webpack/components/__tests__/ProxmoxComputeSelectors.test.js
|
|
302
305
|
- webpack/components/__tests__/ProxmoxVmType.test.js
|
|
303
306
|
- webpack/components/__tests__/ProxmoxVmUtils.test.js
|