foreman_bootdisk 16.1.0 → 18.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop.yaml +19 -0
- data/.tx/config +1 -1
- data/AUTHORS +5 -0
- data/README.md +11 -3
- data/app/controllers/concerns/allowed_actions.rb +16 -0
- data/app/controllers/foreman_bootdisk/api/v2/disks_controller.rb +15 -8
- data/app/controllers/foreman_bootdisk/api/v2/subnet_disks_controller.rb +16 -4
- data/app/controllers/foreman_bootdisk/disks_controller.rb +29 -23
- data/app/controllers/foreman_bootdisk/subnet_disks_controller.rb +41 -0
- data/app/helpers/bootdisk_links_helper.rb +46 -0
- data/app/helpers/concerns/foreman_bootdisk/hosts_helper_ext.rb +58 -44
- data/app/helpers/concerns/foreman_bootdisk/pretty_error.rb +13 -0
- data/app/helpers/concerns/foreman_bootdisk/subnets_helper_ext.rb +45 -0
- data/app/helpers/disk_helper.rb +8 -0
- data/app/lib/foreman_bootdisk/scope/bootdisk.rb +28 -1
- data/app/lib/foreman_bootdisk/scope/full_host_bootdisk_efi.rb +15 -0
- data/app/models/concerns/foreman_bootdisk/compute_resources/vmware.rb +10 -1
- data/app/models/concerns/foreman_bootdisk/host_ext.rb +9 -6
- data/app/models/concerns/foreman_bootdisk/orchestration/compute.rb +38 -22
- data/app/models/setting/bootdisk.rb +14 -1
- data/app/services/foreman_bootdisk/iso_generator.rb +134 -56
- data/app/services/foreman_bootdisk/renderer.rb +36 -19
- data/app/views/foreman_bootdisk/disks/help.html.erb +16 -4
- data/app/views/foreman_bootdisk/generic_efi_host.erb +68 -0
- data/app/views/foreman_bootdisk/host.erb +7 -0
- data/app/views/subnets/_bootdisk_action_buttons.erb +1 -0
- data/app/views/subnets/_bootdisk_title_buttons.erb +1 -0
- data/config/routes.rb +1 -1
- data/db/seeds.d/50-bootdisk_templates.rb +1 -0
- data/lib/foreman_bootdisk/engine.rb +12 -2
- data/lib/foreman_bootdisk/version.rb +1 -1
- data/lib/tasks/bootdisk.rake +10 -24
- data/locale/action_names.rb +5 -0
- data/locale/ca/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/ca/foreman_bootdisk.po +74 -11
- data/locale/de/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/de/foreman_bootdisk.po +81 -17
- data/locale/en/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/en/foreman_bootdisk.po +71 -8
- data/locale/en_GB/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/en_GB/foreman_bootdisk.po +75 -12
- data/locale/es/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/es/foreman_bootdisk.po +88 -24
- data/locale/foreman_bootdisk.pot +167 -74
- data/locale/fr/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/fr/foreman_bootdisk.po +96 -32
- data/locale/it/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/it/foreman_bootdisk.po +75 -12
- data/locale/ja/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/ja/foreman_bootdisk.po +86 -22
- data/locale/ko/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/ko/foreman_bootdisk.po +75 -12
- data/locale/pt_BR/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/pt_BR/foreman_bootdisk.po +96 -33
- data/locale/ru/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/ru/foreman_bootdisk.po +75 -12
- data/locale/sv_SE/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/sv_SE/foreman_bootdisk.po +75 -12
- data/locale/zh_CN/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/zh_CN/foreman_bootdisk.po +129 -66
- data/locale/zh_TW/LC_MESSAGES/foreman_bootdisk.mo +0 -0
- data/locale/zh_TW/foreman_bootdisk.po +75 -12
- data/test/functional/foreman_bootdisk/api/v2/disks_controller_test.rb +51 -17
- data/test/functional/foreman_bootdisk/api/v2/subnet_disks_controller_test.rb +23 -10
- data/test/functional/foreman_bootdisk/disks_controller_test.rb +60 -30
- data/test/functional/foreman_bootdisk/subnet_disks_controller_test.rb +51 -0
- data/test/test_plugin_helper.rb +21 -3
- data/test/unit/concerns/host_test.rb +1 -1
- data/test/unit/concerns/orchestration/compute_test.rb +32 -13
- data/test/unit/foreman_bootdisk/renderer_test.rb +1 -1
- data/test/unit/iso_generator_test.rb +6 -7
- metadata +21 -36
@@ -3,10 +3,23 @@
|
|
3
3
|
module ForemanBootdisk
|
4
4
|
module Scope
|
5
5
|
class Bootdisk < ::Foreman::Renderer::Scope::Provisioning
|
6
|
+
extend ApipieDSL::Class
|
7
|
+
|
8
|
+
apipie :class, 'Macros related to provisioning via boot disk' do
|
9
|
+
name 'Bootdisk'
|
10
|
+
sections only: %w[all provisioning]
|
11
|
+
end
|
12
|
+
|
13
|
+
apipie :method, 'Generates URL for boot chain' do
|
14
|
+
optional :mac, String, 'MAC address of the host', default: 'MAC address of the current host'
|
15
|
+
optional :action, String, 'Bootloader to use', default: 'iPXE'
|
16
|
+
returns String, desc: 'URL for boot chain'
|
17
|
+
example 'bootdisk_chain_url #=> "http://foreman.some.host.fqdn/unattended/iPXE?mac=00%3A11%3A22%3A33%3A44%3A55"'
|
18
|
+
example 'bootdisk_chain_url("00:11:22:33:44:55") #=> "http://foreman.some.host.fqdn/unattended/iPXE?mac=00%3A11%3A22%3A33%3A44%3A55"'
|
19
|
+
end
|
6
20
|
def bootdisk_chain_url(mac = host.try(:mac), action = 'iPXE')
|
7
21
|
url = foreman_url(action)
|
8
22
|
u = URI.parse(url)
|
9
|
-
ForemanBootdisk.logger.warn("Foreman or proxy is configured with HTTPS, probably not supported by iPXE: #{u}") if u.scheme == 'https'
|
10
23
|
new_query_data = URI.decode_www_form(u.query || '') << ['mac', mac || '']
|
11
24
|
new_querystring = URI.encode_www_form(new_query_data)
|
12
25
|
u.query = nil
|
@@ -15,9 +28,23 @@ module ForemanBootdisk
|
|
15
28
|
u.to_s
|
16
29
|
end
|
17
30
|
|
31
|
+
apipie :method, 'Always raises an error with a description provided as an argument' do
|
32
|
+
desc 'This method is useful for aborting script execution if some of the conditions are not met'
|
33
|
+
list :args, desc: 'Description for the error'
|
34
|
+
raises error: ::Foreman::Exception, desc: 'The error is always being raised'
|
35
|
+
returns nil, desc: "Doesn't return anything"
|
36
|
+
example "<%
|
37
|
+
interface = @host.provision_interface #=> interface = nil
|
38
|
+
bootdisk_raise(N_('Host has no provisioning interface defined')) unless interface #=> Foreman::Exception is raised and the execution of the script is aborted
|
39
|
+
%>"
|
40
|
+
end
|
18
41
|
def bootdisk_raise(*args)
|
19
42
|
raise ::Foreman::Exception.new(*args)
|
20
43
|
end
|
44
|
+
|
45
|
+
def template_name
|
46
|
+
"Foreman Bootdisk"
|
47
|
+
end
|
21
48
|
end
|
22
49
|
end
|
23
50
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanBootdisk
|
4
|
+
module Scope
|
5
|
+
class FullHostBootdiskEfi < Bootdisk
|
6
|
+
def kernel(medium_provider)
|
7
|
+
'/' + ForemanBootdisk::ISOGenerator.iso9660_filename(super)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initrd(medium_provider)
|
11
|
+
'/' + ForemanBootdisk::ISOGenerator.iso9660_filename(super)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -36,12 +36,21 @@ module ForemanBootdisk
|
|
36
36
|
'instance_uuid' => vm_uuid,
|
37
37
|
'iso' => "foreman_isos/#{iso}",
|
38
38
|
'datastore' => bootdisk_datastore(vm_uuid),
|
39
|
-
'start_connected' =>
|
39
|
+
'start_connected' => true,
|
40
40
|
'connected' => true,
|
41
41
|
'allow_guest_control' => true
|
42
42
|
}
|
43
43
|
client.vm_reconfig_cdrom options
|
44
44
|
end
|
45
|
+
|
46
|
+
def iso_detach(vm_uuid)
|
47
|
+
options = {
|
48
|
+
'instance_uuid' => vm_uuid,
|
49
|
+
'start_connected' => false,
|
50
|
+
'connected' => false,
|
51
|
+
}
|
52
|
+
client.vm_reconfig_cdrom options
|
53
|
+
end
|
45
54
|
end
|
46
55
|
end
|
47
56
|
end
|
@@ -4,15 +4,14 @@ require 'uri'
|
|
4
4
|
|
5
5
|
module ForemanBootdisk
|
6
6
|
module HostExt
|
7
|
+
extend ApipieDSL::Extension
|
8
|
+
|
7
9
|
def bootdisk_template
|
8
|
-
template = ProvisioningTemplate.unscoped.find_by(
|
9
|
-
name: Setting[:bootdisk_host_template]
|
10
|
-
)
|
10
|
+
template = ProvisioningTemplate.unscoped.find_by(name: Setting[:bootdisk_host_template])
|
11
11
|
unless template
|
12
|
-
raise ::Foreman::Exception.new(
|
13
|
-
N_('Unable to find template specified by %s setting'), 'bootdisk_host_template'
|
14
|
-
)
|
12
|
+
raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'), 'bootdisk_host_template')
|
15
13
|
end
|
14
|
+
|
16
15
|
template
|
17
16
|
end
|
18
17
|
|
@@ -39,6 +38,10 @@ module ForemanBootdisk
|
|
39
38
|
def can_be_built?
|
40
39
|
super || (managed? && SETTINGS[:unattended] && bootdisk_build? && !build?)
|
41
40
|
end
|
41
|
+
|
42
|
+
apipie_update :class do
|
43
|
+
property :bootdisk_build?, one_of: [true, false], desc: 'Returns true if provision method for this host is bootdisk, false otherwise'
|
44
|
+
end
|
42
45
|
end
|
43
46
|
end
|
44
47
|
|
@@ -9,7 +9,6 @@ module ForemanBootdisk
|
|
9
9
|
|
10
10
|
included do
|
11
11
|
after_validation :queue_bootdisk_compute
|
12
|
-
after_build :rebuild_with_bootdisk
|
13
12
|
end
|
14
13
|
|
15
14
|
def bootdisk_isodir
|
@@ -21,20 +20,38 @@ module ForemanBootdisk
|
|
21
20
|
end
|
22
21
|
|
23
22
|
def queue_bootdisk_compute
|
24
|
-
return unless compute? && errors.empty?
|
23
|
+
return unless compute? && errors.empty?
|
25
24
|
return unless provision_method == 'bootdisk'
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
# We want to add our queue jobs only if really necessary:
|
27
|
+
# - in case of starting to create a new host
|
28
|
+
# - in case of a rebuild
|
29
|
+
if (old.nil? || !old.build?) && build?
|
30
|
+
queue.create(name: _('Generating ISO image for %s') % self, priority: 5,
|
31
|
+
action: [self, :setGenerateIsoImage])
|
32
|
+
queue.create(name: _('Upload ISO image to datastore for %s') % self, priority: 6,
|
33
|
+
action: [self, :setIsoImage])
|
34
|
+
queue.create(name: _('Attach ISO image to CDROM drive for %s') % self, priority: 1001,
|
35
|
+
action: [self, :setAttachIsoImage])
|
36
|
+
# Detach ISO image when host sends 'built' HTTP POST request
|
37
|
+
elsif old&.build? && !build?
|
38
|
+
queue.create(name: _('Detach ISO image from CDROM drive for %s') % self, priority: 52,
|
39
|
+
action: [self, :setDetachIsoImage])
|
40
|
+
end
|
41
|
+
true
|
33
42
|
end
|
34
43
|
|
35
44
|
def bootdisk_generate_iso_image
|
36
|
-
|
37
|
-
|
45
|
+
if self.build? && self.provisioning_template(kind: :PXELinux) && self.provisioning_template(kind: :PXEGrub2)
|
46
|
+
logger.info format('Generating FULL HOST ISO image for %s', name)
|
47
|
+
ForemanBootdisk::ISOGenerator.generate_full_host(self, dir: Dir.tmpdir) do |image|
|
48
|
+
FileUtils.mv image, bootdisk_isofile
|
49
|
+
end
|
50
|
+
else
|
51
|
+
logger.info format('Generating HOST ISO image for %s', name)
|
52
|
+
ForemanBootdisk::ISOGenerator.generate(ipxe: bootdisk_template_render, dir: Dir.tmpdir) do |image|
|
53
|
+
FileUtils.mv image, bootdisk_isofile
|
54
|
+
end
|
38
55
|
end
|
39
56
|
end
|
40
57
|
|
@@ -46,8 +63,11 @@ module ForemanBootdisk
|
|
46
63
|
compute_resource.iso_attach(File.basename(bootdisk_isofile), uuid)
|
47
64
|
end
|
48
65
|
|
66
|
+
def bootdisk_detach_iso
|
67
|
+
compute_resource.iso_detach(uuid)
|
68
|
+
end
|
69
|
+
|
49
70
|
def setGenerateIsoImage
|
50
|
-
logger.info format('Generating ISO image for %s', name)
|
51
71
|
bootdisk_generate_iso_image
|
52
72
|
rescue StandardError => e
|
53
73
|
failure format(_('Failed to generate ISO image for instance %{name}: %{message}'), name: name, message: e.message), e
|
@@ -73,18 +93,14 @@ module ForemanBootdisk
|
|
73
93
|
|
74
94
|
def delAttachIsoImage; end
|
75
95
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
bootdisk_upload_iso
|
82
|
-
bootdisk_attach_iso
|
83
|
-
rescue StandardError => e
|
84
|
-
Foreman::Logging.exception "Failed to rebuild Bootdisk image for #{name}", e, level: :error
|
85
|
-
return false
|
86
|
-
end
|
96
|
+
def setDetachIsoImage
|
97
|
+
logger.info format('Detaching ISO image from CDROM drive for %s', name)
|
98
|
+
bootdisk_detach_iso
|
99
|
+
rescue StandardError => e
|
100
|
+
failure format(_('Failed to detach ISO image from CDROM drive of instance %{name}: %{message}'), name: name, message: e.message), e
|
87
101
|
end
|
102
|
+
|
103
|
+
def delDetachIsoImage; end
|
88
104
|
end
|
89
105
|
end
|
90
106
|
end
|
@@ -6,23 +6,36 @@ class Setting
|
|
6
6
|
ipxe = ['/usr/lib/ipxe'].find { |p| File.exist?(p) } || '/usr/share/ipxe'
|
7
7
|
isolinux = ['/usr/lib/ISOLINUX'].find { |p| File.exist?(p) } || '/usr/share/syslinux'
|
8
8
|
syslinux = ['/usr/lib/syslinux/modules/bios', '/usr/lib/syslinux'].find { |p| File.exist?(p) } || '/usr/share/syslinux'
|
9
|
+
grub2 = ['/var/lib/tftpboot/grub2'].find { |p| File.exist?(p) } || '/var/lib/foreman/bootdisk'
|
9
10
|
templates = -> { Hash[ProvisioningTemplate.where(template_kind: TemplateKind.where(name: 'Bootdisk')).map { |temp| [temp[:name], temp[:name]] }] }
|
10
11
|
|
11
12
|
[
|
12
13
|
set('bootdisk_ipxe_dir', N_('Path to directory containing iPXE images'), ipxe, N_('iPXE directory')),
|
13
14
|
set('bootdisk_isolinux_dir', N_('Path to directory containing isolinux images'), isolinux, N_('ISOLINUX directory')),
|
14
15
|
set('bootdisk_syslinux_dir', N_('Path to directory containing syslinux images'), syslinux, N_('SYSLINUX directory')),
|
16
|
+
set('bootdisk_grub2_dir', N_('Path to directory containing grubx64.efi and shimx64.efi'), grub2, N_('Grub2 directory')),
|
15
17
|
set('bootdisk_host_template', N_('iPXE template to use for host-specific boot disks'),
|
16
18
|
'Boot disk iPXE - host', N_('Host image template'), nil, collection: templates),
|
17
19
|
set('bootdisk_generic_host_template', N_('iPXE template to use for generic host boot disks'),
|
18
20
|
'Boot disk iPXE - generic host', N_('Generic image template'), nil, collection: templates),
|
21
|
+
set('bootdisk_generic_efi_host_template', N_('Grub2 template to use for generic EFI host boot disks'),
|
22
|
+
'Boot disk Grub2 EFI - generic host', N_('Generic Grub2 EFI image template'), nil, collection: templates),
|
19
23
|
set('bootdisk_mkiso_command', N_('Command to generate ISO image, use genisoimage or mkisofs'), 'genisoimage', N_('ISO generation command')),
|
20
|
-
set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching'))
|
24
|
+
set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching')),
|
25
|
+
set('bootdisk_allowed_types', N_('List of allowed bootdisk types, remove type to disable it'), Setting::Bootdisk.bootdisk_types, N_('Allowed bootdisk types'))
|
21
26
|
]
|
22
27
|
end
|
23
28
|
|
24
29
|
def self.humanized_category
|
25
30
|
N_('Boot disk')
|
26
31
|
end
|
32
|
+
|
33
|
+
def self.bootdisk_types
|
34
|
+
%w(generic host full_host subnet)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.allowed_types
|
38
|
+
Setting['bootdisk_allowed_types']
|
39
|
+
end
|
27
40
|
end
|
28
41
|
end
|
@@ -10,10 +10,13 @@ require 'uri'
|
|
10
10
|
# requires syslinux, ipxe/ipxe-bootimgs, genisoimage, isohybrid
|
11
11
|
module ForemanBootdisk
|
12
12
|
class ISOGenerator
|
13
|
+
extend Foreman::HTTPProxy
|
14
|
+
|
13
15
|
def self.generate_full_host(host, opts = {}, &block)
|
14
|
-
raise
|
16
|
+
raise Foreman::Exception.new(N_('Host is not in build mode, so the template cannot be rendered')) unless host.build?
|
15
17
|
|
16
|
-
|
18
|
+
isolinux_template = render_template(host, :PXELinux, ForemanBootdisk::Scope::FullHostBootdisk)
|
19
|
+
grub_template = render_template(host, :PXEGrub2, ForemanBootdisk::Scope::FullHostBootdiskEfi)
|
17
20
|
|
18
21
|
# pxe_files and filename conversion is utterly bizarre
|
19
22
|
# aim to convert filenames to something usable under ISO 9660, to match as rendered in the template
|
@@ -24,87 +27,154 @@ module ForemanBootdisk
|
|
24
27
|
hash[iso_filename] = host.url_for_boot(type)
|
25
28
|
end
|
26
29
|
|
27
|
-
generate(opts.merge(isolinux:
|
30
|
+
generate(opts.merge(isolinux: isolinux_template, grub: grub_template, files: files), &block)
|
31
|
+
rescue ::Foreman::Exception => e
|
32
|
+
if e.code == 'ERF42-0067'
|
33
|
+
ForemanBootdisk.logger.warn e
|
34
|
+
else
|
35
|
+
raise e
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
30
|
-
def self.
|
31
|
-
|
39
|
+
def self.render_template(host, kind, scope_class)
|
40
|
+
template = host.provisioning_template(kind: kind)
|
32
41
|
|
33
|
-
raise
|
42
|
+
raise Foreman::Exception.new(N_('Unable to generate disk template, %{kind} template not found.'), kind: kind) unless template
|
34
43
|
|
35
44
|
template = ForemanBootdisk::Renderer.new.render_template(
|
36
|
-
template:
|
45
|
+
template: template,
|
37
46
|
host: host,
|
38
|
-
scope_class:
|
47
|
+
scope_class: scope_class
|
39
48
|
)
|
40
49
|
|
41
50
|
unless template
|
42
51
|
err = host.errors.full_messages.to_sentence
|
43
|
-
raise
|
52
|
+
raise Foreman::Exception.new(N_('Unable to generate disk %{kind} template: %{error}'), kind: kind, error: err)
|
44
53
|
end
|
45
54
|
|
46
55
|
template
|
47
56
|
end
|
48
57
|
|
58
|
+
def self.system_or_exception(command, error)
|
59
|
+
unless system(command)
|
60
|
+
ForemanBootdisk.logger.debug "Bootdisk command failed: #{command}"
|
61
|
+
raise Foreman::Exception.new(N_(error))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
49
65
|
def self.generate(opts = {})
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if opts[:isolinux]
|
61
|
-
isolinux_source_file = File.join(Setting[:bootdisk_isolinux_dir], 'isolinux.bin')
|
62
|
-
raise(Foreman::Exception, N_('Please ensure the isolinux/syslinux package(s) are installed.')) unless File.exist?(isolinux_source_file)
|
63
|
-
|
64
|
-
FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
|
65
|
-
|
66
|
-
source_files = ['ldlinux.c32', 'menu.c32']
|
67
|
-
source_files.each do |source_file|
|
68
|
-
full_path = File.join(Setting[:bootdisk_syslinux_dir], source_file)
|
69
|
-
FileUtils.cp(full_path, File.join(wd, 'build', source_file)) if File.exist?(full_path)
|
70
|
-
end
|
66
|
+
# isolinux template not provided for generic BIOS bootdisks
|
67
|
+
if opts[:isolinux].nil? && opts[:ipxe]
|
68
|
+
opts[:isolinux] = <<~EOT
|
69
|
+
default ipxe
|
70
|
+
label ipxe
|
71
|
+
kernel /ipxe
|
72
|
+
initrd /script
|
73
|
+
EOT
|
74
|
+
end
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
+
if opts[:grub].nil? && opts[:ipxe]
|
77
|
+
opts[:grub] = <<~EOT
|
78
|
+
echo "Grub2 template not associated and/or unsupported bootdisk type."
|
79
|
+
echo "The following bootdisks are supported for EFI systems:"
|
80
|
+
echo " * FULL HOST BOOTDISK"
|
81
|
+
echo " * SUBNET GENERIC BOOTDISK"
|
82
|
+
echo "The system will poweroff in few minutes..."
|
83
|
+
sleep 500
|
84
|
+
poweroff
|
85
|
+
EOT
|
86
|
+
end
|
87
|
+
|
88
|
+
# And create new temporary directory:
|
89
|
+
wd = Dir.mktmpdir('bootdisk-iso-', Rails.root.join('tmp'))
|
90
|
+
Dir.mkdir(File.join(wd, 'build'))
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
if opts[:isolinux]
|
93
|
+
isolinux_source_file = File.join(Setting[:bootdisk_isolinux_dir], 'isolinux.bin')
|
94
|
+
raise Foreman::Exception.new(N_('Please ensure the isolinux/syslinux package(s) are installed.')) unless File.exist?(isolinux_source_file)
|
80
95
|
|
81
|
-
|
82
|
-
|
96
|
+
FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
|
97
|
+
|
98
|
+
source_files = ['ldlinux.c32', 'menu.c32', 'libutil.c32']
|
99
|
+
source_files.each do |source_file|
|
100
|
+
full_path = File.join(Setting[:bootdisk_syslinux_dir], source_file)
|
101
|
+
FileUtils.cp(full_path, File.join(wd, 'build', source_file)) if File.exist?(full_path)
|
83
102
|
end
|
84
103
|
|
85
|
-
|
86
|
-
|
87
|
-
opts[:files].each do |file, source|
|
88
|
-
fetch(File.join(wd, 'build', file), source)
|
89
|
-
end
|
90
|
-
end
|
104
|
+
File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
|
105
|
+
file.write(opts[:isolinux])
|
91
106
|
end
|
107
|
+
end
|
92
108
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
File.join(wd, 'output.iso')
|
97
|
-
end
|
98
|
-
raise(Foreman::Exception, N_('ISO build failed')) unless system(build_mkiso_command(output_file: iso, source_directory: File.join(wd, 'build')))
|
109
|
+
if opts[:ipxe]
|
110
|
+
ipxe_source_file = File.join(Setting[:bootdisk_ipxe_dir], 'ipxe.lkrn')
|
111
|
+
raise Foreman::Exception.new(N_('Please ensure the ipxe-bootimgs package is installed.')) unless File.exist?(ipxe_source_file)
|
99
112
|
|
100
|
-
|
101
|
-
|
113
|
+
FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
|
114
|
+
File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
|
115
|
+
end
|
116
|
+
|
117
|
+
if opts[:grub]
|
118
|
+
FileUtils.mkdir_p(File.join(wd, 'build', 'EFI', 'BOOT'))
|
119
|
+
# a copy when using CDROM directly
|
120
|
+
File.open(File.join(wd, 'build', 'EFI', 'BOOT', 'grub.cfg'), 'w') { |file| file.write(opts[:grub]) }
|
121
|
+
# a copy when using USB/HDD (copied into EFI image as well)
|
122
|
+
File.open(File.join(wd, 'build', 'grub-hdd.cfg'), 'w') do |f|
|
123
|
+
# set root to (cd0) or similar when booting via disk
|
124
|
+
f.write("search --file /ISOLINUX.BIN --set root\n")
|
125
|
+
f.write(opts[:grub])
|
126
|
+
end
|
127
|
+
efibootimg = File.join(wd, 'build', 'efiboot.img')
|
128
|
+
system_or_exception("truncate -s 4M #{efibootimg}", N_('Creating new image failed, install truncate utility'))
|
129
|
+
system_or_exception("mkfs.msdos #{efibootimg}", N_('Failed to format the ESP image via mkfs.msdos'))
|
130
|
+
system_or_exception("mmd -i #{efibootimg} '::/EFI'", N_('Failed to create a directory within the ESP image'))
|
131
|
+
system_or_exception("mmd -i #{efibootimg} '::/EFI/BOOT'", N_('Failed to create a directory within the ESP image'))
|
132
|
+
{
|
133
|
+
File.join(wd, 'build', 'grub-hdd.cfg').to_s => 'GRUB.CFG',
|
134
|
+
File.join(Setting[:bootdisk_grub2_dir], 'grubx64.efi').to_s => 'GRUBX64.EFI',
|
135
|
+
File.join(Setting[:bootdisk_grub2_dir], 'shimx64.efi').to_s => 'BOOTX64.EFI',
|
136
|
+
}.each do |src, dest|
|
137
|
+
raise(Foreman::Exception.new(N_('Ensure %{file} is readable (or update "Grub2 directory" setting)'), file: src)) unless File.exist?(src)
|
138
|
+
raise(Foreman::Exception.new(N_('Unable to mcopy %{file}'), file: src)) unless system("mcopy -m -i #{efibootimg} '#{src}' '#{"::/EFI/BOOT/#{dest}"}'")
|
139
|
+
end
|
140
|
+
mkiso_args = "-eltorito-alt-boot -e efiboot.img -no-emul-boot"
|
141
|
+
isohybrod_args = ["--uefi"]
|
142
|
+
else
|
143
|
+
mkiso_args = ''
|
144
|
+
isohybrod_args = []
|
145
|
+
end
|
102
146
|
|
103
|
-
|
147
|
+
if opts[:files]
|
148
|
+
if opts[:files].respond_to?(:each)
|
149
|
+
opts[:files].each do |file, source|
|
150
|
+
fetch(File.join(wd, 'build', file), source)
|
151
|
+
end
|
152
|
+
end
|
104
153
|
end
|
154
|
+
|
155
|
+
iso = if opts[:dir]
|
156
|
+
Tempfile.new(['bootdisk', '.iso'], opts[:dir]).path
|
157
|
+
else
|
158
|
+
File.join(wd, 'output.iso')
|
159
|
+
end
|
160
|
+
raise Foreman::Exception.new(N_('ISO build failed')) unless system(build_mkiso_command(output_file: iso, extra_commands: mkiso_args, source_directory: File.join(wd, 'build')))
|
161
|
+
|
162
|
+
# Make the ISO bootable as a HDD/USB disk too
|
163
|
+
raise Foreman::Exception.new(N_('ISO hybrid conversion failed: %s'), $?) unless system(*["isohybrid", isohybrod_args, iso].flatten.compact)
|
164
|
+
|
165
|
+
yield iso
|
166
|
+
ensure
|
167
|
+
# Clean the working directory (not the ISO file itself)
|
168
|
+
FileUtils.rm_f(File.join(wd, 'build'))
|
169
|
+
# Temporary directory cannot be cleaned in-process due to asynchronous send_file call.
|
170
|
+
# Also we cannot rely on systemd-tmpfiles-clean as private temporary files are not subject
|
171
|
+
# of scheduled cleanups. Let's clean bootdisks from prevous requests manually by finding
|
172
|
+
# and deleting all directories created 30 minutes ago.
|
173
|
+
delete_older_than = Time.now.to_i - (60 * 30)
|
174
|
+
Rails.root.glob('tmp/bootdisk-iso-*').select { |f| File.ctime(f) < delete_older_than }.each { |f| FileUtils.rm_f(f) }
|
105
175
|
end
|
106
176
|
|
107
|
-
def self.build_mkiso_command(output_file:, source_directory:)
|
177
|
+
def self.build_mkiso_command(output_file:, source_directory:, extra_commands:)
|
108
178
|
arguments = [
|
109
179
|
"-o #{output_file}",
|
110
180
|
'-iso-level 2',
|
@@ -114,6 +184,7 @@ module ForemanBootdisk
|
|
114
184
|
'-boot-load-size 4',
|
115
185
|
'-boot-info-table'
|
116
186
|
]
|
187
|
+
arguments.push(extra_commands) unless extra_commands.nil?
|
117
188
|
[Setting[:bootdisk_mkiso_command], arguments, source_directory].flatten.join(' ')
|
118
189
|
end
|
119
190
|
|
@@ -140,7 +211,14 @@ module ForemanBootdisk
|
|
140
211
|
ForemanBootdisk.logger.info("Fetching #{uri}")
|
141
212
|
write_cache = use_cache
|
142
213
|
uri = URI(uri)
|
143
|
-
|
214
|
+
|
215
|
+
if proxy_http_request?(nil, uri.host, uri.scheme)
|
216
|
+
proxy_uri = URI.parse(http_proxy)
|
217
|
+
http_object = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
|
218
|
+
else
|
219
|
+
http_object = Net::HTTP
|
220
|
+
end
|
221
|
+
http_object.start(uri.host, uri.port) do |http|
|
144
222
|
request = Net::HTTP::Get.new(uri.request_uri, 'Accept-Encoding' => 'plain')
|
145
223
|
|
146
224
|
http.request(request) do |response|
|
@@ -4,36 +4,46 @@ require 'uri'
|
|
4
4
|
|
5
5
|
module ForemanBootdisk
|
6
6
|
class Renderer
|
7
|
-
def
|
8
|
-
host = if subnet.present?
|
9
|
-
# rendering a subnet-level bootdisk requires tricking the renderer into thinking it has a
|
10
|
-
# valid host, without a token, but with a tftp proxy
|
11
|
-
Struct.new(:token, :provision_interface, :content_source).new(
|
12
|
-
nil,
|
13
|
-
Struct.new(:subnet).new(subnet),
|
14
|
-
nil
|
15
|
-
)
|
16
|
-
else
|
17
|
-
Struct.new(:token, :subnet, :content_source).new(nil, nil, nil)
|
18
|
-
end
|
19
|
-
|
20
|
-
render_template(template: generic_host_template, host: host)
|
21
|
-
end
|
22
|
-
|
23
|
-
def render_template(template:, host:, scope_class: renderer_scope)
|
7
|
+
def render_template(template:, host:, scope_class: renderer_scope, subnet: nil)
|
24
8
|
source = Foreman::Renderer.get_source(
|
25
9
|
template: template,
|
26
|
-
host: host
|
27
10
|
)
|
28
11
|
scope = Foreman::Renderer.get_scope(
|
29
12
|
host: host,
|
30
|
-
|
13
|
+
variables: { subnet: subnet, bootdisk: true },
|
14
|
+
klass: scope_class,
|
31
15
|
)
|
32
16
|
Foreman::Renderer.render(source, scope)
|
33
17
|
end
|
34
18
|
|
19
|
+
def generic_template_render(subnet = nil)
|
20
|
+
render_template(template: generic_host_template, host: stub_host(subnet), subnet: subnet)
|
21
|
+
end
|
22
|
+
|
23
|
+
def generic_efi_template_render(subnet)
|
24
|
+
if subnet.httpboot?
|
25
|
+
render_template(template: generic_efi_host_template, host: stub_host(subnet), subnet: subnet)
|
26
|
+
else
|
27
|
+
ForemanBootdisk.logger.warn('HTTPBOOT feature is not enabled for subnet %s, UEFI may not be available for bootdisk' % subnet.name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
35
31
|
private
|
36
32
|
|
33
|
+
def stub_host(subnet)
|
34
|
+
if subnet.present?
|
35
|
+
# rendering a subnet-level bootdisk requires tricking the renderer into thinking it has a
|
36
|
+
# valid host, without a token, but with a tftp proxy
|
37
|
+
Struct.new(:token, :provision_interface, :content_source).new(
|
38
|
+
nil,
|
39
|
+
Struct.new(:subnet).new(subnet),
|
40
|
+
nil
|
41
|
+
)
|
42
|
+
else
|
43
|
+
Struct.new(:token, :subnet, :content_source).new(nil, nil, nil)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
37
47
|
def renderer_scope
|
38
48
|
ForemanBootdisk::Scope::Bootdisk
|
39
49
|
end
|
@@ -44,5 +54,12 @@ module ForemanBootdisk
|
|
44
54
|
|
45
55
|
template
|
46
56
|
end
|
57
|
+
|
58
|
+
def generic_efi_host_template
|
59
|
+
template = ProvisioningTemplate.unscoped.find_by(name: Setting[:bootdisk_generic_efi_host_template])
|
60
|
+
raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'), 'bootdisk_generic_efi_host_template') unless template
|
61
|
+
|
62
|
+
template
|
63
|
+
end
|
47
64
|
end
|
48
65
|
end
|
@@ -9,7 +9,12 @@
|
|
9
9
|
<%= _('All images are usable as either ISOs or as disk images, including being written to a USB disk with `dd`.') %>
|
10
10
|
</p>
|
11
11
|
|
12
|
-
<h2><%= _('Host
|
12
|
+
<h2><%= _('Host images') %></h2>
|
13
|
+
<p>
|
14
|
+
<%= _('These images are used for host. You can find them at host detail page.') %>
|
15
|
+
</p>
|
16
|
+
|
17
|
+
<h3><%= _('Host image') %><%= mark_disabled_bootdisk_type('host') %></h3>
|
13
18
|
<p>
|
14
19
|
<%= _("Per-host images contain data about a particular host registered in Foreman and set up fully static networking, avoiding the requirement for DHCP. After networking is configured, they chainload from Foreman, picking up the current OS configuration and build state from the server.") %>
|
15
20
|
</p>
|
@@ -17,12 +22,17 @@
|
|
17
22
|
<%= _("Once chainloaded, the OS bootloader and installer are downloaded directly from the installation media configured in Foreman, and the provisioning script (kickstart/preseed) is downloaded from Foreman.") %>
|
18
23
|
</p>
|
19
24
|
|
20
|
-
<
|
25
|
+
<h3><%= _('Full host image') %><%= mark_disabled_bootdisk_type('full_host') %></h3>
|
21
26
|
<p>
|
22
27
|
<%= _('A variant of the per-host image which contains the OS bootloader embedded inside the disk. This may be useful if chainloading fails on certain hardware, but has the downside that the image must be regenerated for any change in the OS, bootloader or PXELinux templates.') %>
|
23
28
|
</p>
|
24
29
|
|
25
|
-
<h2><%= _('Generic
|
30
|
+
<h2><%= _('Generic images') %></h2>
|
31
|
+
<p>
|
32
|
+
<%= _('These images are more generic than previous images. You can find them at subnet index page.') %>
|
33
|
+
</p>
|
34
|
+
|
35
|
+
<h3><%= _('Generic image') %><%= mark_disabled_bootdisk_type('generic') %></h3>
|
26
36
|
<p>
|
27
37
|
<%= _('Generic images are a reusable disk image that works for any host registered in Foreman. It requires a basic DHCP and DNS service to function and contact the server, but does not require DHCP reservations or static IP addresses.') %>
|
28
38
|
</p>
|
@@ -30,11 +40,13 @@
|
|
30
40
|
<%= _('The OS install continues using the installation media configured in Foreman, and it will typically configure static networking, depending on how the OS iPXE template is configured.') %>
|
31
41
|
</p>
|
32
42
|
|
33
|
-
<
|
43
|
+
<h3><%= _('Subnet image') %><%= mark_disabled_bootdisk_type('subnet') %></h3>
|
34
44
|
<p>
|
35
45
|
<%= _('Subnet images are similar to generic images, but chain-loading is done via the TFTP Smart Proxy assigned to the Subnet of the host. The smart proxy must have the "Templates" module enabled and configured.') %>
|
36
46
|
</p>
|
37
47
|
<p>
|
38
48
|
<%= _('This image is generic for all hosts with a provisioning NIC on that subnet.') %>
|
39
49
|
</p>
|
50
|
+
|
51
|
+
<p><%= _('* - These bootdisk types were disabled, you can enable them in Administer - Settings.') %></p>
|
40
52
|
</div>
|