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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_fog_proxmox/proxmox_network_interface_modal.js +11 -0
- data/app/assets/stylesheets/foreman_fog_proxmox/network_interface_modal.css +17 -0
- data/app/controllers/foreman_fog_proxmox/compute_resources_controller.rb +31 -23
- 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 +3 -1
- data/app/helpers/proxmox_vm_cloudinit_helper.rb +62 -55
- data/app/helpers/proxmox_vm_config_helper.rb +4 -2
- 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.rb +1 -1
- 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 +3 -4
- data/app/models/foreman_fog_proxmox/proxmox_vm_commands.rb +15 -13
- data/app/models/foreman_fog_proxmox/proxmox_vm_new.rb +21 -0
- data/app/views/compute_resources_vms/form/proxmox/_add_react_component_to_host_form.html.erb +5 -0
- data/app/views/compute_resources_vms/form/proxmox/_add_vm_type_to_nic_provider_specific_form.html.erb +5 -4
- data/lib/foreman_fog_proxmox/engine.rb +8 -2
- data/lib/foreman_fog_proxmox/version.rb +1 -1
- data/test/functional/compute_resources_controller_test.rb +41 -0
- data/test/unit/foreman_fog_proxmox/helpers/proxmox_vm_helper_test.rb +100 -0
- data/test/unit/foreman_fog_proxmox/proxmox_images_test.rb +16 -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 +226 -11
- data/test/unit/foreman_fog_proxmox/proxmox_vm_new_test.rb +70 -0
- data/webpack/components/GeneralTabContent.js +99 -61
- 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 +25 -1
- data/webpack/components/__tests__/EFIDisk.test.js +59 -0
- data/webpack/components/__tests__/GeneralTabContent.test.js +207 -0
- data/webpack/components/__tests__/ProxmoxVmType.test.js +36 -1
- data/webpack/components/hooks/useInheritedImageSelection.js +42 -0
- metadata +9 -3
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9dcd1a0e6c90d8847b0e31986425ab3354e4659676994ea6ab84ec9648b0d17d
|
|
4
|
+
data.tar.gz: 8dc424df461ebfe7f7cf07f11e7fecebf56fa75c3bdb4c52b51ef2fdcda01fa5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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(
|
|
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(
|
|
111
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
storage:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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:
|
|
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
|
|
@@ -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
|
|
46
|
-
|
|
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 =
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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",
|
|
69
|
-
|
|
70
|
-
logger.debug("iso image generation args: #{
|
|
71
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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(
|
|
81
|
-
|
|
82
|
-
rescue
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
133
|
+
storage = vm_storage
|
|
134
|
+
end
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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(
|
|
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
|
|
@@ -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(
|
|
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
|