foreman_fog_proxmox 0.25.0 → 1.0.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_fog_proxmox/proxmox_network_interface_modal.js +11 -0
  3. data/app/assets/stylesheets/foreman_fog_proxmox/network_interface_modal.css +17 -0
  4. data/app/controllers/foreman_fog_proxmox/compute_resources_controller.rb +31 -23
  5. data/app/helpers/proxmox_compute_controllers_helper.rb +6 -4
  6. data/app/helpers/proxmox_compute_selectors_helper.rb +2 -1
  7. data/app/helpers/proxmox_vm_attrs_helper.rb +3 -1
  8. data/app/helpers/proxmox_vm_cloudinit_helper.rb +62 -55
  9. data/app/helpers/proxmox_vm_config_helper.rb +4 -2
  10. data/app/helpers/proxmox_vm_efidisk_helper.rb +64 -0
  11. data/app/helpers/proxmox_vm_helper.rb +22 -0
  12. data/app/helpers/proxmox_vm_image_template_helper.rb +55 -0
  13. data/app/models/concerns/fog_extensions/proxmox/server.rb +1 -1
  14. data/app/models/concerns/fog_extensions/proxmox/server_config.rb +9 -0
  15. data/app/models/foreman_fog_proxmox/proxmox_efidisks.rb +11 -3
  16. data/app/models/foreman_fog_proxmox/proxmox_images.rb +3 -4
  17. data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +15 -13
  18. data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +21 -0
  19. data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
  20. data/app/views/compute_resources_vms/form/proxmox/_add_vm_type_to_nic_provider_specific_form.html.erb +5 -4
  21. data/lib/foreman_fog_proxmox/engine.rb +8 -2
  22. data/lib/foreman_fog_proxmox/version.rb +1 -1
  23. data/test/functional/compute_resources_controller_test.rb +41 -0
  24. data/test/unit/foreman_fog_proxmox/helpers/proxmox_vm_helper_test.rb +100 -0
  25. data/test/unit/foreman_fog_proxmox/proxmox_images_test.rb +16 -5
  26. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_container_test.rb +3 -1
  27. data/test/unit/foreman_fog_proxmox/proxmox_vm_commands_server_create_test.rb +226 -11
  28. data/test/unit/foreman_fog_proxmox/proxmox_vm_new_test.rb +70 -0
  29. data/webpack/components/GeneralTabContent.js +99 -61
  30. data/webpack/components/ProxmoxBiosContext.js +14 -3
  31. data/webpack/components/ProxmoxComputeSelectors.js +1 -0
  32. data/webpack/components/ProxmoxServer/ProxmoxServerOptions.js +17 -4
  33. data/webpack/components/ProxmoxServer/ProxmoxServerStorage.js +5 -1
  34. data/webpack/components/ProxmoxServer/components/EFIDisk.js +13 -4
  35. data/webpack/components/ProxmoxServer/components/SecureBoot.js +69 -0
  36. data/webpack/components/ProxmoxVmType.js +25 -1
  37. data/webpack/components/__tests__/EFIDisk.test.js +59 -0
  38. data/webpack/components/__tests__/GeneralTabContent.test.js +207 -0
  39. data/webpack/components/__tests__/ProxmoxVmType.test.js +36 -1
  40. data/webpack/components/hooks/useInheritedImageSelection.js +42 -0
  41. metadata +9 -3
  42. data/app/controllers/concerns/foreman_fog_proxmox/controller/parameters/compute_resource.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 329a340fdfd2b5e117a05a227477e29108df61d303a5ce5a5a2dc148ae41ec7c
4
- data.tar.gz: '08b9aa3dca59fb2b7a80c2f1e6b0b59c9931f0d974e8d9849a720d6d37a819be'
3
+ metadata.gz: 9dcd1a0e6c90d8847b0e31986425ab3354e4659676994ea6ab84ec9648b0d17d
4
+ data.tar.gz: 8dc424df461ebfe7f7cf07f11e7fecebf56fa75c3bdb4c52b51ef2fdcda01fa5
5
5
  SHA512:
6
- metadata.gz: '094a19a8592b2ed3ad7a88c637c7859f3bbc631a4f08f2fb781ec21a8b9620789fb701dbab917973ee122fb2ead5a44d6af12a28fe6dd2d99dad2267c8d051a7'
7
- data.tar.gz: ce956eaac9a5faf92f2decd8721910dd72fd9a0e7f0ccfbeab40462c1cfc7892897235e7d2fef925405abf993b85f902edac0f4afcb1ec685649d27df0e61168
6
+ metadata.gz: a99138711e3912f07dfe3c875106745d543db41e466fbf50460a3fd9abab1476e1da72620f2233d4455984698c64763b849c87f317131d6021b458f1150ea2f5
7
+ data.tar.gz: 852428fd8ed987daca7f45d0908a8b9d71ecbacb9affe3bd65fdc9ecff12b7be80859ea3071d59bd00d8182d5bd75eac1510f922e9c3977be6fd638a879e8187
@@ -0,0 +1,11 @@
1
+ $(document)
2
+ .on('show.bs.modal', '#interfaceModal', function () {
3
+ var modal = $(this);
4
+ modal.toggleClass(
5
+ 'proxmox-interface-modal',
6
+ modal.find('.proxmox-interface-form').length > 0
7
+ );
8
+ })
9
+ .on('hidden.bs.modal', '#interfaceModal', function () {
10
+ $(this).removeClass('proxmox-interface-modal');
11
+ });
@@ -0,0 +1,17 @@
1
+ #interfaceModal.proxmox-interface-modal {
2
+ overflow-y: hidden;
3
+ }
4
+
5
+ #interfaceModal.proxmox-interface-modal .modal-content {
6
+ display: flex;
7
+ flex-direction: column;
8
+ max-height: 90vh;
9
+ overflow: hidden;
10
+ }
11
+
12
+ #interfaceModal.proxmox-interface-modal .modal-body {
13
+ flex: 1 1 auto;
14
+ min-height: 0;
15
+ overflow-y: auto;
16
+ overflow-x: hidden;
17
+ }
@@ -95,20 +95,21 @@ module ForemanFogProxmox
95
95
  # GET foreman_fog_proxmox/metadata/:compute_resource_id
96
96
  def metadata
97
97
  cr = ComputeResource.find(params[:compute_resource_id])
98
+ nodes = Array(cr.nodes)
98
99
 
99
100
  render json: {
100
- nodes: extract_nodes(cr),
101
+ nodes: extract_nodes(nodes),
101
102
  pools: extract_pools(cr),
102
- storages: extract_storages(cr),
103
- bridges: extract_bridges(cr),
103
+ storages: extract_storages(cr, nodes),
104
+ bridges: extract_bridges(cr, nodes),
104
105
  images: extract_images(cr),
105
106
  }
106
107
  end
107
108
 
108
109
  private
109
110
 
110
- def extract_nodes(compute_resource)
111
- Array(compute_resource.nodes).map { |n| { node: n.node } }
111
+ def extract_nodes(nodes)
112
+ nodes.map { |n| { node: n.node } }
112
113
  end
113
114
 
114
115
  def extract_pools(compute_resource)
@@ -118,27 +119,34 @@ module ForemanFogProxmox
118
119
  end
119
120
  end
120
121
 
121
- def extract_storages(compute_resource)
122
- Array(compute_resource.storages).map do |s|
123
- h = s.respond_to?(:as_json) ? s.as_json : s
124
- {
125
- storage: (h[:storage] || h['storage']),
126
- node_id: (h[:node_id] || h['node_id']),
127
- content: (h[:content] || h['content']),
128
- avail: (h[:avail] || h['avail']),
129
- used: (h[:used] || h['used']),
130
- total: (h[:total] || h['total']),
131
- }
122
+ def extract_storages(compute_resource, nodes)
123
+ nodes.flat_map do |node|
124
+ node_id = node.node
125
+ Array(compute_resource.storages(node_id)).map do |storage|
126
+ h = storage.respond_to?(:as_json) ? storage.as_json : storage
127
+ {
128
+ storage: (h[:storage] || h['storage']),
129
+ node_id: node_id,
130
+ content: (h[:content] || h['content']),
131
+ avail: (h[:avail] || h['avail']),
132
+ used: (h[:used] || h['used']),
133
+ total: (h[:total] || h['total']),
134
+ shared: (h[:shared] || h['shared']),
135
+ }
136
+ end
132
137
  end
133
138
  end
134
139
 
135
- def extract_bridges(compute_resource)
136
- Array(compute_resource.bridges).map do |b|
137
- h = b.respond_to?(:as_json) ? b.as_json : b
138
- {
139
- node_id: (h[:node_id] || h['node_id']),
140
- iface: (h[:iface] || h['iface']),
141
- }
140
+ def extract_bridges(compute_resource, nodes)
141
+ nodes.flat_map do |node|
142
+ node_id = node.node
143
+ Array(compute_resource.bridges(node_id)).map do |bridge|
144
+ h = bridge.respond_to?(:as_json) ? bridge.as_json : bridge
145
+ {
146
+ node_id: node_id,
147
+ iface: (h[:iface] || h['iface']),
148
+ }
149
+ end
142
150
  end
143
151
  end
144
152
 
@@ -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
@@ -130,10 +130,12 @@ module ProxmoxVMAttrsHelper
130
130
  hostname: vms.config.hostname,
131
131
  ostemplate_storage: vms.ostemplate_storage,
132
132
  ostemplate_file: vms.ostemplate_file,
133
+ iso_upload_storage: vms.iso_upload_storage,
133
134
  start_after_create: vms.start_after_create,
134
135
  templated: vms.templated,
136
+ is_secure_boot: vms.config.secure_boot?,
135
137
  }
136
- vms_keys = [:cpu_type, :nameserver, :searchdomain, :hostname]
138
+ vms_keys = [:cpu_type, :nameserver, :searchdomain, :hostname, :is_secure_boot]
137
139
  extra_attrs = ActiveSupport::HashWithIndifferentAccess.new
138
140
  attributes.each do |key, value|
139
141
  camel_key = key.to_s.include?('_') ? snake_to_camel(key.to_s).to_sym : key
@@ -21,6 +21,9 @@ require 'fog/proxmox/helpers/disk_helper'
21
21
  require 'fog/proxmox/helpers/nic_helper'
22
22
  require 'foreman_fog_proxmox/value'
23
23
  require 'foreman_fog_proxmox/hash_collection'
24
+ require 'fileutils'
25
+ require 'open3'
26
+ require 'tmpdir'
24
27
 
25
28
  # Convert a foreman form server hash into a fog-proxmox server attributes hash
26
29
  module ProxmoxVMCloudinitHelper
@@ -42,54 +45,59 @@ module ProxmoxVMCloudinitHelper
42
45
  cloudinit_h
43
46
  end
44
47
 
45
- def create_cloudinit_iso(vm_name, configs, ssh)
46
- iso = File.join(default_iso_path, "#{vm_name.tr('.', '_')}_cloudinit.iso")
48
+ def create_cloudinit_iso(vm_name, configs)
49
+ wd = create_temp_directory
50
+ iso = File.join(wd, "#{vm_name.tr('.', '_')}_cloudinit.iso")
47
51
  files = []
48
- wd = create_temp_directory(ssh)
49
52
 
50
53
  configs.each do |config|
51
- config_file = ssh.run("cat <<'EOF' > '#{wd}/#{config[0]}'\n#{config[1]}\nEOF")
52
- unless config_file.first.status.zero?
53
- delete_temp_dir(ssh, wd)
54
- raise ::Foreman::Exception, "Failed to create file #{config[0]}: #{config_file.first.stdout}"
55
- end
56
- files.append(File.join(wd, config[0]))
54
+ config_file = File.join(wd, config[0])
55
+ File.write(config_file, config[1])
56
+ files.append(config_file)
57
57
  end
58
- generated_iso = ssh.run(generate_iso_command(iso, files))
59
- unless generated_iso.first.status.zero?
60
- delete_temp_dir(ssh, wd)
61
- raise Foreman::Exception, N_("ISO build failed: #{generated_iso.first.stdout}")
62
- end
63
- delete_temp_dir(ssh, wd)
64
- iso
58
+ logger.debug("Generating cloud-init ISO at #{iso}")
59
+ stdout, stderr, status = Open3.capture3(*generate_iso_command(iso, files))
60
+ raise Foreman::Exception, N_("ISO build failed: #{stderr.presence || stdout}") unless status.success?
61
+
62
+ yield iso
63
+ ensure
64
+ delete_temp_dir(wd) if wd && Dir.exist?(wd)
65
65
  end
66
66
 
67
67
  def generate_iso_command(iso_file, config_files)
68
- arguments = ["genisoimage", "-output #{iso_file}", '-volid', 'cidata', '-joliet', '-rock']
69
- iso_command = arguments.concat(config_files).join(' ')
70
- logger.debug("iso image generation args: #{iso_command}")
71
- iso_command
68
+ arguments = ["genisoimage", '-output', iso_file, '-volid', 'cidata', '-joliet', '-rock']
69
+ arguments.concat(config_files)
70
+ logger.debug("iso image generation args: #{arguments.join(' ')}")
71
+ arguments
72
72
  end
73
73
 
74
- def create_temp_directory(ssh)
75
- res = ssh.run("mktemp -d")
76
- raise ::Foreman::Exception, "Could not create working directory to store cloudinit config data: #{res.first.stdout}." unless res.first.status.zero?
77
- res.first.stdout.chomp
74
+ def create_temp_directory
75
+ Dir.mktmpdir
76
+ rescue StandardError => e
77
+ raise ::Foreman::Exception, "Could not create working directory to store cloudinit config data: #{e}."
78
78
  end
79
79
 
80
- def delete_temp_dir(ssh, working_dir)
81
- ssh.run("rm -rf #{working_dir}")
82
- rescue Foreman::Exception => e
80
+ def delete_temp_dir(working_dir)
81
+ FileUtils.remove_entry(working_dir)
82
+ rescue StandardError => e
83
83
  logger.warn("Could not delete directory for config files: #{e}. Please delete it manually at #{working_dir}")
84
84
  end
85
85
 
86
- def parse_cloudinit_config(args)
86
+ def cloudinit_clone_args(args, vm_instance)
87
+ return args unless args[:user_data]
88
+
89
+ actual_node = vm_instance.node_id
90
+ logger.info("Cloud-init ISO storage will be validated for cloned VM node #{actual_node} instead of selected node #{args[:node_id]}") if args[:node_id] != actual_node
91
+
92
+ parse_cloudinit_config(args, vm_node: actual_node)
93
+ end
94
+
95
+ def parse_cloudinit_config(args, vm_node: args[:node_id])
87
96
  filenames = ["meta-data"]
88
97
  config_data = ["instance-id: #{args[:name]}"]
89
98
  user_data = args.delete(:user_data)
90
99
  return args if user_data == ''
91
100
  check_template_format(user_data)
92
- ssh = vm_ssh
93
101
 
94
102
  if user_data.include?('#network-config') && user_data.include?('#cloud-config')
95
103
  config_data.concat(user_data.split('#network-config'))
@@ -105,40 +113,39 @@ module ProxmoxVMCloudinitHelper
105
113
  return args if config_data.length == 1
106
114
  configs = filenames.zip(config_data).to_h
107
115
 
108
- iso = create_cloudinit_iso(args[:name], configs, ssh)
109
- args[:config_attributes]&.merge!(update_boot_order(args[:image_id]))
110
- args.merge!(attach_cloudinit_iso(args[:node_id], iso))
116
+ create_cloudinit_iso(args[:name], configs) do |iso|
117
+ storage = upload_iso(args[:node_id], args[:iso_upload_storage], iso, vm_node: vm_node)
118
+ args.merge!(attach_cloudinit_iso(vm_node, storage, iso))
119
+ end
111
120
  end
112
121
 
113
- def attach_cloudinit_iso(node, iso)
114
- volume = nil
115
- storages(node, 'iso').each do |storage|
116
- volume = storage.volumes.detect { |v| v.volid.include? File.basename(iso) }
117
- break if volume
118
- end
122
+ def upload_iso(node, storage_id, iso, vm_node: node)
123
+ raise ::Foreman::Exception, _('ISO upload storage must be selected') if storage_id.blank?
119
124
 
120
- raise ::Foreman::Exception, "Could not find generated cloud-init ISO #{File.basename(iso)} on any ISO storage for node #{node}" unless volume
125
+ iso_storages = storages(node, 'iso')
126
+ storage = iso_storages.find { |s| s.identity == storage_id }
127
+ raise ::Foreman::Exception, "Could not find selected ISO upload storage #{storage_id} for node #{node}" unless storage
121
128
 
122
- { ide2: "#{volume.volid},media=cdrom" }
123
- end
129
+ if node != vm_node
130
+ vm_storage = storages(vm_node, 'iso').find { |s| s.identity == storage_id }
131
+ raise ::Foreman::Exception, "Selected ISO upload storage #{storage_id} is not accessible from cloned VM node #{vm_node}" unless vm_storage
124
132
 
125
- def default_iso_path
126
- "/var/lib/vz/template/iso"
127
- end
133
+ storage = vm_storage
134
+ end
128
135
 
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 }
136
+ storage.upload_iso(iso)
137
+ storage.identity
134
138
  end
135
139
 
136
- def vm_ssh
137
- ssh = Fog::SSH.new(URI.parse(fog_credentials[:proxmox_url]).host, fog_credentials[:proxmox_username].split('@')[0], { password: fog_credentials[:proxmox_password] })
138
- ssh.run('ls') # test if ssh is successful
139
- ssh
140
- rescue StandardError => e
141
- raise ::Foreman::Exception, "Unable to ssh into proxmox server: #{e}"
140
+ def attach_cloudinit_iso(node, storage_id, iso)
141
+ storage = client.nodes.get(node).storages.get(storage_id)
142
+ volume = storage&.volumes&.all&.detect do |v|
143
+ File.basename(v.volid) == File.basename(iso)
144
+ end
145
+
146
+ raise ::Foreman::Exception, "Could not find generated cloud-init ISO #{File.basename(iso)} on storage #{storage_id} for node #{node}" unless volume
147
+
148
+ { ide2: "#{volume.volid},media=cdrom" }
142
149
  end
143
150
 
144
151
  def check_template_format(user_data)
@@ -41,7 +41,7 @@ module ProxmoxVMConfigHelper
41
41
 
42
42
  def general_a(type)
43
43
  general_a = ['node_id', 'type', 'config_attributes', 'efidisk_attributes', 'volumes_attributes', 'interfaces_attributes', 'image_id']
44
- general_a += ['firmware_type', 'provision_method', 'container_volumes', 'server_volumes', 'start_after_create']
44
+ general_a += ['firmware_type', 'provision_method', 'container_volumes', 'server_volumes', 'start_after_create', 'iso_upload_storage']
45
45
  general_a += ['name'] if type == 'lxc'
46
46
  general_a
47
47
  end
@@ -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
@@ -21,7 +21,7 @@ module FogExtensions
21
21
  module Proxmox
22
22
  module Server
23
23
  extend ActiveSupport::Concern
24
- attr_accessor :image_id, :templated, :ostemplate_storage, :ostemplate_file, :password, :start_after_create, :compute_resource_id
24
+ attr_accessor :image_id, :templated, :ostemplate_storage, :ostemplate_file, :iso_upload_storage, :password, :start_after_create, :compute_resource_id
25
25
 
26
26
  def unique_cluster_identity(compute_resource)
27
27
  compute_resource.id.to_s + '_' + identity.to_s
@@ -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
@@ -41,7 +41,7 @@ module ForemanFogProxmox
41
41
  end
42
42
 
43
43
  def available_images
44
- templates.collect { |template| OpenStruct.new(id: template_uuid(template), name: template_name(template)) }
44
+ templates.collect { |template| OpenStruct.new(id: template_uuid(template), name: template_name(template)) }.uniq
45
45
  end
46
46
 
47
47
  def templates
@@ -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