foreman_bootdisk 16.1.0 → 17.0.2

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -0
  3. data/README.md +9 -0
  4. data/app/controllers/foreman_bootdisk/api/v2/disks_controller.rb +5 -7
  5. data/app/controllers/foreman_bootdisk/api/v2/subnet_disks_controller.rb +9 -4
  6. data/app/controllers/foreman_bootdisk/disks_controller.rb +11 -10
  7. data/app/helpers/concerns/foreman_bootdisk/hosts_helper_ext.rb +1 -1
  8. data/app/lib/foreman_bootdisk/scope/bootdisk.rb +4 -1
  9. data/app/lib/foreman_bootdisk/scope/full_host_bootdisk_efi.rb +15 -0
  10. data/app/models/concerns/foreman_bootdisk/compute_resources/vmware.rb +10 -1
  11. data/app/models/concerns/foreman_bootdisk/host_ext.rb +3 -6
  12. data/app/models/concerns/foreman_bootdisk/orchestration/compute.rb +28 -19
  13. data/app/models/setting/bootdisk.rb +4 -0
  14. data/app/services/foreman_bootdisk/iso_generator.rb +114 -54
  15. data/app/services/foreman_bootdisk/renderer.rb +36 -19
  16. data/app/views/foreman_bootdisk/generic_efi_host.erb +64 -0
  17. data/db/seeds.d/50-bootdisk_templates.rb +1 -0
  18. data/lib/foreman_bootdisk/engine.rb +1 -1
  19. data/lib/foreman_bootdisk/version.rb +1 -1
  20. data/lib/tasks/bootdisk.rake +10 -6
  21. data/locale/action_names.rb +5 -0
  22. data/locale/ca/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  23. data/locale/ca/foreman_bootdisk.po +37 -4
  24. data/locale/de/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  25. data/locale/de/foreman_bootdisk.po +40 -7
  26. data/locale/en/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  27. data/locale/en/foreman_bootdisk.po +37 -4
  28. data/locale/en_GB/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  29. data/locale/en_GB/foreman_bootdisk.po +38 -5
  30. data/locale/es/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  31. data/locale/es/foreman_bootdisk.po +40 -7
  32. data/locale/foreman_bootdisk.pot +87 -39
  33. data/locale/fr/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  34. data/locale/fr/foreman_bootdisk.po +38 -5
  35. data/locale/it/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  36. data/locale/it/foreman_bootdisk.po +38 -5
  37. data/locale/ja/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  38. data/locale/ja/foreman_bootdisk.po +38 -5
  39. data/locale/ko/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  40. data/locale/ko/foreman_bootdisk.po +38 -5
  41. data/locale/pt_BR/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  42. data/locale/pt_BR/foreman_bootdisk.po +38 -5
  43. data/locale/ru/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  44. data/locale/ru/foreman_bootdisk.po +38 -5
  45. data/locale/sv_SE/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  46. data/locale/sv_SE/foreman_bootdisk.po +38 -5
  47. data/locale/zh_CN/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  48. data/locale/zh_CN/foreman_bootdisk.po +38 -5
  49. data/locale/zh_TW/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  50. data/locale/zh_TW/foreman_bootdisk.po +38 -5
  51. data/test/functional/foreman_bootdisk/api/v2/disks_controller_test.rb +51 -17
  52. data/test/functional/foreman_bootdisk/api/v2/subnet_disks_controller_test.rb +23 -10
  53. data/test/functional/foreman_bootdisk/disks_controller_test.rb +65 -25
  54. data/test/test_plugin_helper.rb +21 -3
  55. data/test/unit/concerns/host_test.rb +1 -1
  56. data/test/unit/concerns/orchestration/compute_test.rb +32 -13
  57. data/test/unit/foreman_bootdisk/renderer_test.rb +1 -1
  58. data/test/unit/iso_generator_test.rb +6 -7
  59. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f4504c1358b4e4a789bf50d8eeac0e61b5097039694039e77151e47980b2f14
4
- data.tar.gz: e46dffaf3739195d4f5988458e2eaa6b38d3eac66ede08c77c07034f8a77d40a
3
+ metadata.gz: cc43113465fd89f890f9606fb04af3e948b96ac1bd6c1943211a452e38c4cb2a
4
+ data.tar.gz: 9db1069085f706df776576fc0df25f49a7d09ae71555ea15397d57656ddd0da5
5
5
  SHA512:
6
- metadata.gz: '09868d38c5adb714d029fe6ffc554bf3469204343d14d77f789b5c2bbb8badc1bdc760be477ffa69fb1cf3850a6854ae257165e2baba2a7d28373d58b4b9a5b5'
7
- data.tar.gz: 4d25edcd1342b1b619d7001a7a4b82f282da9aaed4561e4b8deb85ddccad3df09100ff038f8782a45f4f04b75a8a8403554b3378a6344ea5424047d80d3efec5
6
+ metadata.gz: 115bddba362f7c53b78d06ca78a1f120babae76cc2f922447cd0e05b1f322f189e30b186e6cb0718b7d9a24bfca67bf2287283fd9495eba4193a9b6bb560ec29
7
+ data.tar.gz: 1cd764fdc0a939d45934070d6b34e2619870c8c206afd4915cae93f68f7553cfcc9b18d3c04770aba9f4b6f45d62bbd779563798b7175595ee8c2b991f03b660
data/AUTHORS CHANGED
@@ -1,4 +1,5 @@
1
1
  Adam Ruzicka
2
+ Bernhard Suttner
2
3
  Bryan Kearney
3
4
  crito
4
5
  Damon Maria
data/README.md CHANGED
@@ -50,6 +50,10 @@ gPXE images are unsupported due to lack of initrd support.
50
50
  | >= 1.19 | ~> 13.0 |
51
51
  | >= 1.20 | ~> 14.0 |
52
52
  | >= 1.21 | ~> 15.0 |
53
+ | >= 1.23 | ~> 16.0 |
54
+ | >= 1.24 | ~> 16.1 |
55
+ | >= 2.0 | ~> 16.1 |
56
+ | >= 2.1 | ~> 17.0 |
53
57
 
54
58
  # Usage
55
59
 
@@ -119,6 +123,7 @@ TFTP settings are needed.
119
123
  <th>DHCP reservation</th>
120
124
  <th>Pre-register host</th>
121
125
  <th>OS-specific</th>
126
+ <th>EFI supported</th>
122
127
  </tr>
123
128
  <tr>
124
129
  <td>Per-host image</td>
@@ -127,6 +132,7 @@ TFTP settings are needed.
127
132
  <td>No</td>
128
133
  <td>Yes</td>
129
134
  <td>No</td>
135
+ <td>No</td>
130
136
  </tr>
131
137
  <tr>
132
138
  <td>Full host image</td>
@@ -135,6 +141,7 @@ TFTP settings are needed.
135
141
  <td>No</td>
136
142
  <td>Yes</td>
137
143
  <td>Yes</td>
144
+ <td>Yes</td>
138
145
  </tr>
139
146
  <tr>
140
147
  <td>Generic image</td>
@@ -143,6 +150,7 @@ TFTP settings are needed.
143
150
  <td>No</td>
144
151
  <td>Yes</td>
145
152
  <td>No</td>
153
+ <td>No</td>
146
154
  </tr>
147
155
  <tr>
148
156
  <td>Subnet image</td>
@@ -151,6 +159,7 @@ TFTP settings are needed.
151
159
  <td>No</td>
152
160
  <td>Yes</td>
153
161
  <td>No</td>
162
+ <td>Yes</td>
154
163
  </tr>
155
164
  </table>
156
165
 
@@ -21,9 +21,10 @@ module ForemanBootdisk
21
21
 
22
22
  api :GET, '/generic', N_('Download generic image')
23
23
  def generic
24
+ # EFI not supported for iPXE generic bootdisk
24
25
  tmpl = ForemanBootdisk::Renderer.new.generic_template_render
25
26
  ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
26
- send_data read_file(iso), filename: "bootdisk_#{URI.parse(Setting[:foreman_url]).host}.iso"
27
+ send_file(iso, filename: "bootdisk_#{URI.parse(Setting[:foreman_url]).host}.iso")
27
28
  end
28
29
  end
29
30
 
@@ -34,22 +35,19 @@ module ForemanBootdisk
34
35
  host = @disk
35
36
  if params[:full]
36
37
  ForemanBootdisk::ISOGenerator.generate_full_host(host) do |iso|
37
- send_data read_file(iso), filename: "#{host.name}#{ForemanBootdisk::ISOGenerator.token_expiry(host)}.iso"
38
+ send_file(iso, filename: "#{host.name}#{ForemanBootdisk::ISOGenerator.token_expiry(host)}.iso")
38
39
  end
39
40
  else
41
+ # EFI not supported for iPXE host bootdisk
40
42
  tmpl = host.bootdisk_template_render
41
43
  ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
42
- send_data read_file(iso), filename: "#{host.name}.iso"
44
+ send_file(iso, filename: "#{host.name}.iso")
43
45
  end
44
46
  end
45
47
  end
46
48
 
47
49
  private
48
50
 
49
- def read_file(filename)
50
- File.read(filename)
51
- end
52
-
53
51
  def resource_scope
54
52
  Host::Managed.authorized('view_hosts')
55
53
  end
@@ -25,10 +25,15 @@ module ForemanBootdisk
25
25
  def subnet
26
26
  subnet = @subnet_disk
27
27
  subnet.tftp || raise(::Foreman::Exception.new(N_('TFTP feature not enabled for subnet %s'), subnet.name))
28
- tmpl = ForemanBootdisk::Renderer.new.generic_template_render(subnet)
29
- ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
30
- name = subnet.name
31
- send_data File.read(iso), filename: "bootdisk_subnet_#{name}.iso"
28
+ tmpl_bios = ForemanBootdisk::Renderer.new.generic_template_render(subnet)
29
+ tmpl_efi = nil
30
+ if subnet.httpboot
31
+ tmpl_efi = ForemanBootdisk::Renderer.new.generic_efi_template_render(subnet)
32
+ else
33
+ ForemanBootdisk.logger.warn('HTTPBOOT feature is not enabled for subnet %s, UEFI may not be available for bootdisk' % subnet.name)
34
+ end
35
+ ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl_bios, grub: tmpl_efi) do |temp_iso_filename|
36
+ send_file(temp_iso_filename, filename: subnet.name)
32
37
  end
33
38
  end
34
39
 
@@ -12,6 +12,7 @@ module ForemanBootdisk
12
12
 
13
13
  def generic
14
14
  begin
15
+ # EFI not supported for iPXE generic bootdisk
15
16
  tmpl = ForemanBootdisk::Renderer.new.generic_template_render
16
17
  rescue StandardError => e
17
18
  error_rendering(e)
@@ -20,13 +21,14 @@ module ForemanBootdisk
20
21
  end
21
22
 
22
23
  ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
23
- send_data read_file(iso), filename: "bootdisk_#{URI.parse(Setting[:foreman_url]).host}.iso"
24
+ send_file(iso, filename: "bootdisk_#{URI.parse(Setting[:foreman_url]).host}.iso")
24
25
  end
25
26
  end
26
27
 
27
28
  def host
28
29
  host = @disk
29
30
  begin
31
+ # EFI not supported for iPXE host bootdisk
30
32
  tmpl = host.bootdisk_template_render
31
33
  rescue StandardError => e
32
34
  error_rendering(e)
@@ -35,14 +37,14 @@ module ForemanBootdisk
35
37
  end
36
38
 
37
39
  ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
38
- send_data read_file(iso), filename: "#{host.name}.iso"
40
+ send_file(iso, filename: "#{host.name}.iso")
39
41
  end
40
42
  end
41
43
 
42
44
  def full_host
43
45
  host = @disk
44
46
  ForemanBootdisk::ISOGenerator.generate_full_host(host) do |iso|
45
- send_data read_file(iso), filename: "#{host.name}#{ForemanBootdisk::ISOGenerator.token_expiry(host)}.iso"
47
+ send_file(iso, filename: "#{host.name}#{ForemanBootdisk::ISOGenerator.token_expiry(host)}.iso")
46
48
  end
47
49
  end
48
50
 
@@ -51,15 +53,18 @@ module ForemanBootdisk
51
53
  begin
52
54
  subnet = host.try(:subnet) || raise(::Foreman::Exception.new(N_('Subnet is not assigned to the host %s'), host.name))
53
55
  subnet.tftp || raise(::Foreman::Exception.new(N_('TFTP feature not enabled for subnet %s'), subnet.name))
54
- tmpl = ForemanBootdisk::Renderer.new.generic_template_render(subnet)
56
+ subnet.httpboot || ForemanBootdisk.logger.warn('HTTPBOOT feature is not enabled for subnet %s, UEFI may not be available for bootdisk' % subnet.name)
57
+
58
+ tmpl_bios = ForemanBootdisk::Renderer.new.generic_template_render(subnet)
59
+ tmpl_efi = ForemanBootdisk::Renderer.new.generic_efi_template_render(subnet)
55
60
  rescue StandardError => e
56
61
  error_rendering(e)
57
62
  redirect_back(fallback_location: '/')
58
63
  return
59
64
  end
60
65
 
61
- ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl) do |iso|
62
- send_data read_file(iso), filename: "bootdisk_subnet_#{subnet.name}.iso"
66
+ ForemanBootdisk::ISOGenerator.generate(ipxe: tmpl_bios, grub: tmpl_efi) do |iso|
67
+ send_file(iso, filename: "bootdisk_subnet_#{subnet.name}.iso")
63
68
  end
64
69
  end
65
70
 
@@ -67,10 +72,6 @@ module ForemanBootdisk
67
72
 
68
73
  private
69
74
 
70
- def read_file(filename)
71
- File.read(filename)
72
- end
73
-
74
75
  def resource_scope(_controller = controller_name)
75
76
  Host::Managed.authorized(:view_hosts)
76
77
  end
@@ -65,7 +65,7 @@ module ForemanBootdisk
65
65
 
66
66
  # need to wrap this one in a test for template proxy presence
67
67
  def display_bootdisk_for_subnet(host)
68
- if (proxy = host.try(:subnet).try(:tftp)) && proxy.has_feature?('Templates')
68
+ if (proxy = host.try(:subnet).try(:tftp) || host.try(:subnet).try(:httpboot)) && proxy.has_feature?('Templates')
69
69
  display_bootdisk_link_if_authorized(
70
70
  _("Subnet '%s' generic image") % host.subnet.name, {
71
71
  controller: 'foreman_bootdisk/disks',
@@ -6,7 +6,6 @@ module ForemanBootdisk
6
6
  def bootdisk_chain_url(mac = host.try(:mac), action = 'iPXE')
7
7
  url = foreman_url(action)
8
8
  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
9
  new_query_data = URI.decode_www_form(u.query || '') << ['mac', mac || '']
11
10
  new_querystring = URI.encode_www_form(new_query_data)
12
11
  u.query = nil
@@ -18,6 +17,10 @@ module ForemanBootdisk
18
17
  def bootdisk_raise(*args)
19
18
  raise ::Foreman::Exception.new(*args)
20
19
  end
20
+
21
+ def template_name
22
+ "Foreman Bootdisk"
23
+ end
21
24
  end
22
25
  end
23
26
  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' => false,
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
@@ -5,14 +5,11 @@ require 'uri'
5
5
  module ForemanBootdisk
6
6
  module HostExt
7
7
  def bootdisk_template
8
- template = ProvisioningTemplate.unscoped.find_by(
9
- name: Setting[:bootdisk_host_template]
10
- )
8
+ template = ProvisioningTemplate.unscoped.find_by(name: Setting[:bootdisk_host_template])
11
9
  unless template
12
- raise ::Foreman::Exception.new(
13
- N_('Unable to find template specified by %s setting'), 'bootdisk_host_template'
14
- )
10
+ raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'), 'bootdisk_host_template')
15
11
  end
12
+
16
13
  template
17
14
  end
18
15
 
@@ -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,15 +20,25 @@ module ForemanBootdisk
21
20
  end
22
21
 
23
22
  def queue_bootdisk_compute
24
- return unless compute? && errors.empty? && new_record?
23
+ return unless compute? && errors.empty?
25
24
  return unless provision_method == 'bootdisk'
26
25
 
27
- queue.create(name: _('Generating ISO image for %s') % self, priority: 5,
28
- action: [self, :setGenerateIsoImage])
29
- queue.create(name: _('Upload ISO image to datastore for %s') % self, priority: 6,
30
- action: [self, :setIsoImage])
31
- queue.create(name: _('Attach ISO image to CDROM drive for %s') % self, priority: 1001,
32
- action: [self, :setAttachIsoImage])
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
@@ -46,6 +55,10 @@ module ForemanBootdisk
46
55
  compute_resource.iso_attach(File.basename(bootdisk_isofile), uuid)
47
56
  end
48
57
 
58
+ def bootdisk_detach_iso
59
+ compute_resource.iso_detach(uuid)
60
+ end
61
+
49
62
  def setGenerateIsoImage
50
63
  logger.info format('Generating ISO image for %s', name)
51
64
  bootdisk_generate_iso_image
@@ -73,18 +86,14 @@ module ForemanBootdisk
73
86
 
74
87
  def delAttachIsoImage; end
75
88
 
76
- def rebuild_with_bootdisk
77
- return true unless bootdisk?
78
-
79
- begin
80
- bootdisk_generate_iso_image
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
89
+ def setDetachIsoImage
90
+ logger.info format('Detaching ISO image from CDROM drive for %s', name)
91
+ bootdisk_detach_iso
92
+ rescue StandardError => e
93
+ failure format(_('Failed to detach ISO image from CDROM drive of instance %{name}: %{message}'), name: name, message: e.message), e
87
94
  end
95
+
96
+ def delDetachIsoImage; end
88
97
  end
89
98
  end
90
99
  end
@@ -6,16 +6,20 @@ 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
24
  set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching'))
21
25
  ]
@@ -11,9 +11,10 @@ require 'uri'
11
11
  module ForemanBootdisk
12
12
  class ISOGenerator
13
13
  def self.generate_full_host(host, opts = {}, &block)
14
- raise(Foreman::Exception, N_('Host is not in build mode, so the template cannot be rendered')) unless host.build?
14
+ raise Foreman::Exception.new(N_('Host is not in build mode, so the template cannot be rendered')) unless host.build?
15
15
 
16
- tmpl = render_pxelinux_template(host)
16
+ isolinux_template = render_template(host, :PXELinux, ForemanBootdisk::Scope::FullHostBootdisk)
17
+ grub_template = render_template(host, :PXEGrub2, ForemanBootdisk::Scope::FullHostBootdiskEfi)
17
18
 
18
19
  # pxe_files and filename conversion is utterly bizarre
19
20
  # aim to convert filenames to something usable under ISO 9660, to match as rendered in the template
@@ -24,87 +25,145 @@ module ForemanBootdisk
24
25
  hash[iso_filename] = host.url_for_boot(type)
25
26
  end
26
27
 
27
- generate(opts.merge(isolinux: tmpl, files: files), &block)
28
+ generate(opts.merge(isolinux: isolinux_template, grub: grub_template, files: files), &block)
29
+ rescue ::Foreman::Exception => e
30
+ if e.code == 'ERF42-0067'
31
+ ForemanBootdisk.logger.warn e
32
+ else
33
+ raise e
34
+ end
28
35
  end
29
36
 
30
- def self.render_pxelinux_template(host)
31
- pxelinux_template = host.provisioning_template(kind: :PXELinux)
37
+ def self.render_template(host, kind, scope_class)
38
+ template = host.provisioning_template(kind: kind)
32
39
 
33
- raise(Foreman::Exception, N_('Unable to generate disk template, PXELinux template not found.')) unless pxelinux_template
40
+ raise Foreman::Exception.new(N_('Unable to generate disk template, %{kind} template not found.'), kind: kind) unless template
34
41
 
35
42
  template = ForemanBootdisk::Renderer.new.render_template(
36
- template: pxelinux_template,
43
+ template: template,
37
44
  host: host,
38
- scope_class: ForemanBootdisk::Scope::FullHostBootdisk
45
+ scope_class: scope_class
39
46
  )
40
47
 
41
48
  unless template
42
49
  err = host.errors.full_messages.to_sentence
43
- raise(::Foreman::Exception.new(N_('Unable to generate disk PXELinux template: %s'), err))
50
+ raise Foreman::Exception.new(N_('Unable to generate disk %{kind} template: %{error}'), kind: kind, error: err)
44
51
  end
45
52
 
46
53
  template
47
54
  end
48
55
 
56
+ def self.system_or_exception(command, error)
57
+ unless system(command)
58
+ ForemanBootdisk.logger.debug "Bootdisk command failed: #{command}"
59
+ raise Foreman::Exception.new(N_(error))
60
+ end
61
+ end
62
+
49
63
  def self.generate(opts = {})
50
- opts[:isolinux] = <<~ISOLINUX if opts[:isolinux].nil? && opts[:ipxe]
51
- default ipxe
52
- label ipxe
53
- kernel /ipxe
54
- initrd /script
55
- ISOLINUX
56
-
57
- Dir.mktmpdir('bootdisk') do |wd|
58
- Dir.mkdir(File.join(wd, 'build'))
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
64
+ # isolinux template not provided for generic BIOS bootdisks
65
+ if opts[:isolinux].nil? && opts[:ipxe]
66
+ opts[:isolinux] = <<~EOT
67
+ default ipxe
68
+ label ipxe
69
+ kernel /ipxe
70
+ initrd /script
71
+ EOT
72
+ end
71
73
 
72
- File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
73
- file.write(opts[:isolinux])
74
- end
74
+ if opts[:grub].nil? && opts[:ipxe]
75
+ opts[:grub] = <<~EOT
76
+ echo "Grub2 template not associated and/or unsupported bootdisk type."
77
+ echo "The following bootdisks are supported for EFI systems:"
78
+ echo " * FULL HOST BOOTDISK"
79
+ echo " * SUBNET GENERIC BOOTDISK"
80
+ echo "The system will poweroff in few minutes..."
81
+ sleep 500
82
+ poweroff
83
+ EOT
84
+ end
85
+
86
+ # Temporary directory cannot be cleaned inprocess due to send_file offloaded to web server
87
+ wd = Dir.mktmpdir('bootdisk-iso-')
88
+ Dir.mkdir(File.join(wd, 'build'))
89
+
90
+ if opts[:isolinux]
91
+ isolinux_source_file = File.join(Setting[:bootdisk_isolinux_dir], 'isolinux.bin')
92
+ raise Foreman::Exception.new(N_('Please ensure the isolinux/syslinux package(s) are installed.')) unless File.exist?(isolinux_source_file)
93
+
94
+ FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
95
+
96
+ source_files = ['ldlinux.c32', 'menu.c32', 'libutil.c32']
97
+ source_files.each do |source_file|
98
+ full_path = File.join(Setting[:bootdisk_syslinux_dir], source_file)
99
+ FileUtils.cp(full_path, File.join(wd, 'build', source_file)) if File.exist?(full_path)
75
100
  end
76
101
 
77
- if opts[:ipxe]
78
- ipxe_source_file = File.join(Setting[:bootdisk_ipxe_dir], 'ipxe.lkrn')
79
- raise(Foreman::Exception, N_('Please ensure the ipxe-bootimgs package is installed.')) unless File.exist?(ipxe_source_file)
102
+ File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
103
+ file.write(opts[:isolinux])
104
+ end
105
+ end
106
+
107
+ if opts[:ipxe]
108
+ ipxe_source_file = File.join(Setting[:bootdisk_ipxe_dir], 'ipxe.lkrn')
109
+ raise Foreman::Exception.new(N_('Please ensure the ipxe-bootimgs package is installed.')) unless File.exist?(ipxe_source_file)
110
+
111
+ FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
112
+ File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
113
+ end
80
114
 
81
- FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
82
- File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
115
+ if opts[:grub]
116
+ FileUtils.mkdir_p(File.join(wd, 'build', 'EFI', 'BOOT'))
117
+ # a copy when using CDROM directly
118
+ File.open(File.join(wd, 'build', 'EFI', 'BOOT', 'grub.cfg'), 'w') { |file| file.write(opts[:grub]) }
119
+ # a copy when using USB/HDD (copied into EFI image as well)
120
+ File.open(File.join(wd, 'build', 'grub-hdd.cfg'), 'w') do |f|
121
+ # set root to (cd0) or similar when booting via disk
122
+ f.write("search --file /ISOLINUX.BIN --set root\n")
123
+ f.write(opts[:grub])
124
+ end
125
+ efibootimg = File.join(wd, 'build', 'efiboot.img')
126
+ system_or_exception("truncate -s 4M #{efibootimg}", N_('Creating new image failed, install truncate utility'))
127
+ system_or_exception("mkfs.msdos #{efibootimg}", N_('Failed to format the ESP image via mkfs.msdos'))
128
+ system_or_exception("mmd -i #{efibootimg} '::/EFI'", N_('Failed to create a directory within the ESP image'))
129
+ system_or_exception("mmd -i #{efibootimg} '::/EFI/BOOT'", N_('Failed to create a directory within the ESP image'))
130
+ {
131
+ File.join(wd, 'build', 'grub-hdd.cfg').to_s => 'GRUB.CFG',
132
+ File.join(Setting[:bootdisk_grub2_dir], 'grubx64.efi').to_s => 'GRUBX64.EFI',
133
+ File.join(Setting[:bootdisk_grub2_dir], 'shimx64.efi').to_s => 'BOOTX64.EFI',
134
+ }.each do |src, dest|
135
+ raise(Foreman::Exception.new(N_('Ensure %{path} contains grubx64.efi and shimx64.efi: install TFTP module or copy those files into /var/lib/foreman/bootdisk (Grub2 directory setting)'), path: src)) unless File.exist?(src)
136
+ raise(Foreman::Exception.new(N_('Unable to mcopy %{path}'), path: src)) unless system("mcopy -m -i #{efibootimg} '#{src}' '#{"::/EFI/BOOT/#{dest}"}'")
83
137
  end
138
+ mkiso_args = "-eltorito-alt-boot -e efiboot.img -no-emul-boot"
139
+ isohybrod_args = ["--uefi"]
140
+ else
141
+ mkiso_args = ''
142
+ isohybrod_args = []
143
+ end
84
144
 
85
- if opts[:files]
86
- if opts[:files].respond_to?(:each)
87
- opts[:files].each do |file, source|
88
- fetch(File.join(wd, 'build', file), source)
89
- end
145
+ if opts[:files]
146
+ if opts[:files].respond_to?(:each)
147
+ opts[:files].each do |file, source|
148
+ fetch(File.join(wd, 'build', file), source)
90
149
  end
91
150
  end
151
+ end
92
152
 
93
- iso = if opts[:dir]
94
- Tempfile.new(['bootdisk', '.iso'], opts[:dir]).path
95
- else
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')))
153
+ iso = if opts[:dir]
154
+ Tempfile.new(['bootdisk', '.iso'], opts[:dir]).path
155
+ else
156
+ File.join(wd, 'output.iso')
157
+ end
158
+ 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')))
99
159
 
100
- # Make the ISO bootable as a HDD/USB disk too
101
- raise(Foreman::Exception, N_('ISO hybrid conversion failed')) unless system('isohybrid', iso)
160
+ # Make the ISO bootable as a HDD/USB disk too
161
+ raise Foreman::Exception.new(N_('ISO hybrid conversion failed: %s'), $?) unless system(*["isohybrid", isohybrod_args, iso].flatten.compact)
102
162
 
103
- yield iso
104
- end
163
+ yield iso
105
164
  end
106
165
 
107
- def self.build_mkiso_command(output_file:, source_directory:)
166
+ def self.build_mkiso_command(output_file:, source_directory:, extra_commands:)
108
167
  arguments = [
109
168
  "-o #{output_file}",
110
169
  '-iso-level 2',
@@ -114,6 +173,7 @@ module ForemanBootdisk
114
173
  '-boot-load-size 4',
115
174
  '-boot-info-table'
116
175
  ]
176
+ arguments.push(extra_commands) unless extra_commands.nil?
117
177
  [Setting[:bootdisk_mkiso_command], arguments, source_directory].flatten.join(' ')
118
178
  end
119
179