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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/proxmox_compute_controllers_helper.rb +6 -4
  3. data/app/helpers/proxmox_compute_selectors_helper.rb +2 -1
  4. data/app/helpers/proxmox_vm_attrs_helper.rb +2 -1
  5. data/app/helpers/proxmox_vm_cloudinit_helper.rb +0 -8
  6. data/app/helpers/proxmox_vm_config_helper.rb +3 -1
  7. data/app/helpers/proxmox_vm_efidisk_helper.rb +64 -0
  8. data/app/helpers/proxmox_vm_helper.rb +22 -0
  9. data/app/helpers/proxmox_vm_image_template_helper.rb +55 -0
  10. data/app/models/concerns/fog_extensions/proxmox/server_config.rb +9 -0
  11. data/app/models/foreman_fog_proxmox/proxmox_efidisks.rb +11 -3
  12. data/app/models/foreman_fog_proxmox/proxmox_images.rb +2 -3
  13. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +14 -12
  14. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +7 -0
  15. data/lib/foreman_fog_proxmox/version.rb +1 -1
  16. data/test/unit/foreman_fog_proxmox/helpers/proxmox_vm_helper_test.rb +100 -0
  17. data/test/unit/foreman_fog_proxmox/proxmox_images_test.rb +3 -5
  18. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_container_test.rb +3 -1
  19. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +103 -1
  20. data/test/unit/foreman_fog_proxmox/proxmox_vm_new_test.rb +30 -0
  21. data/webpack/components/ProxmoxBiosContext.js +14 -3
  22. data/webpack/components/ProxmoxComputeSelectors.js +1 -0
  23. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +17 -4
  24. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +5 -1
  25. data/webpack/components/ProxmoxServer/components/EFIDisk.js +13 -4
  26. data/webpack/components/ProxmoxServer/components/SecureBoot.js +69 -0
  27. data/webpack/components/ProxmoxVmType.js +5 -1
  28. data/webpack/components/__tests__/EFIDisk.test.js +59 -0
  29. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 329a340fdfd2b5e117a05a227477e29108df61d303a5ce5a5a2dc148ae41ec7c
4
- data.tar.gz: '08b9aa3dca59fb2b7a80c2f1e6b0b59c9931f0d974e8d9849a720d6d37a819be'
3
+ metadata.gz: d59641775e4da477886d59bf332dadd576426ef079d1c6af43ef2e9f70b9c5a5
4
+ data.tar.gz: 5e24be7c7881de3c05d5a59a06500bd9d35f7c1f06d4c10390df473ec6ef8b01
5
5
  SHA512:
6
- metadata.gz: '094a19a8592b2ed3ad7a88c637c7859f3bbc631a4f08f2fb781ec21a8b9620789fb701dbab917973ee122fb2ead5a44d6af12a28fe6dd2d99dad2267c8d051a7'
7
- data.tar.gz: ce956eaac9a5faf92f2decd8721910dd72fd9a0e7f0ccfbeab40462c1cfc7892897235e7d2fef925405abf993b85f902edac0f4afcb1ec685649d27df0e61168
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: 15)
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: 3),
28
- ForemanFogProxmox::OptionsSelect.new(id: 'sata', name: 'SATA', range: 5),
29
- ForemanFogProxmox::OptionsSelect.new(id: 'scsi', name: 'SCSI', range: 13)]
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(vmobj)
25
- logger.info("vm #{vmobj.identity} delete efidisk0")
26
- vmobj.detach('efidisk0')
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(image_id, vmid)
74
- logger.debug("create_vm(): clone #{image_id} in #{vmid}")
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
- vm = clone_from_image(image_id, vmid)
43
- vm.update(compute_clone_attributes(args, vm.container?, type))
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
- parsed_attr = parse_typed_vm(
114
- ForemanFogProxmox::HashCollection.new_hash_reject_keys(new_attributes,
115
- ['volumes_attributes']).merge(type: vm.type), vm.type
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
- efidisk_attributes = new_attributes['efidisk_attributes']
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))
@@ -18,5 +18,5 @@
18
18
  # along with ForemanFogProxmox. If not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  module ForemanFogProxmox
21
- VERSION = '0.25.0'
21
+ VERSION = '0.25.1'
22
22
  end
@@ -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(@image_id, @vmid)
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(@image_id, @vmid)
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
- cr.expects(:clone_from_image).with('999', 100).returns(vm)
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
- cr.expects(:clone_from_image).with('999', 100).returns(vm)
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({ children, initialBios = null }) {
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
- // memoize so the provider value object changes only when bios changes
10
- const value = useMemo(() => ({ bios, setBios }), [bios]);
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
  };
@@ -179,6 +179,7 @@ const ProxmoxComputeSelectors = {
179
179
  proxmoxBiosMap: [
180
180
  { value: 'seabios', label: '(Default) Seabios' },
181
181
  { value: 'ovmf', label: 'OVMF (UEFI)' },
182
+ { value: 'uefi_secure_boot', label: 'OVMF (UEFI Secure Boot)' },
182
183
  ],
183
184
  };
184
185
 
@@ -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
- [updatedKey]: { ...prevOpts[updatedKey], value },
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 = bios === 'ovmf';
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={efidisk?.preEnrolledKeys?.value}
143
- checked={String(efidisk?.preEnrolledKeys?.value) === '1'}
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.0
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