foreman_bootdisk 16.1.0 → 17.0.2

Sign up to get free protection for your applications and to get access to all the features.
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