foreman_bootdisk 15.0.0 → 17.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yaml +19 -0
  3. data/.tx/config +1 -1
  4. data/AUTHORS +11 -0
  5. data/README.md +25 -0
  6. data/app/controllers/concerns/allowed_actions.rb +16 -0
  7. data/app/controllers/foreman_bootdisk/api/v2/disks_controller.rb +9 -7
  8. data/app/controllers/foreman_bootdisk/api/v2/subnet_disks_controller.rb +16 -4
  9. data/app/controllers/foreman_bootdisk/disks_controller.rb +14 -10
  10. data/app/helpers/concerns/foreman_bootdisk/hosts_helper_ext.rb +71 -45
  11. data/app/lib/foreman_bootdisk/scope/bootdisk.rb +28 -1
  12. data/app/lib/foreman_bootdisk/scope/full_host_bootdisk_efi.rb +15 -0
  13. data/app/models/concerns/foreman_bootdisk/compute_resources/vmware.rb +10 -1
  14. data/app/models/concerns/foreman_bootdisk/host_ext.rb +13 -6
  15. data/app/models/concerns/foreman_bootdisk/orchestration/compute.rb +38 -22
  16. data/app/models/setting/bootdisk.rb +25 -19
  17. data/app/services/foreman_bootdisk/iso_generator.rb +132 -51
  18. data/app/services/foreman_bootdisk/renderer.rb +36 -19
  19. data/app/views/foreman_bootdisk/generic_efi_host.erb +68 -0
  20. data/app/views/foreman_bootdisk/generic_static_host.erb +34 -0
  21. data/app/views/foreman_bootdisk/host.erb +29 -8
  22. data/db/seeds.d/50-bootdisk_templates.rb +15 -31
  23. data/lib/foreman_bootdisk/engine.rb +9 -6
  24. data/lib/foreman_bootdisk/version.rb +1 -1
  25. data/lib/tasks/bootdisk.rake +10 -24
  26. data/locale/action_names.rb +7 -0
  27. data/locale/ca/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  28. data/locale/ca/foreman_bootdisk.po +58 -7
  29. data/locale/de/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  30. data/locale/de/foreman_bootdisk.po +62 -11
  31. data/locale/en/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  32. data/locale/en/foreman_bootdisk.po +56 -5
  33. data/locale/en_GB/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  34. data/locale/en_GB/foreman_bootdisk.po +62 -11
  35. data/locale/es/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  36. data/locale/es/foreman_bootdisk.po +62 -11
  37. data/locale/foreman_bootdisk.pot +108 -36
  38. data/locale/fr/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  39. data/locale/fr/foreman_bootdisk.po +61 -10
  40. data/locale/it/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  41. data/locale/it/foreman_bootdisk.po +60 -9
  42. data/locale/ja/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  43. data/locale/ja/foreman_bootdisk.po +61 -10
  44. data/locale/ko/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  45. data/locale/ko/foreman_bootdisk.po +60 -9
  46. data/locale/pt_BR/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  47. data/locale/pt_BR/foreman_bootdisk.po +60 -9
  48. data/locale/ru/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  49. data/locale/ru/foreman_bootdisk.po +60 -9
  50. data/locale/sv_SE/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  51. data/locale/sv_SE/foreman_bootdisk.po +59 -8
  52. data/locale/zh_CN/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  53. data/locale/zh_CN/foreman_bootdisk.po +60 -9
  54. data/locale/zh_TW/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  55. data/locale/zh_TW/foreman_bootdisk.po +60 -9
  56. data/release-gem +2 -0
  57. data/test/functional/foreman_bootdisk/api/v2/disks_controller_test.rb +51 -17
  58. data/test/functional/foreman_bootdisk/api/v2/subnet_disks_controller_test.rb +23 -10
  59. data/test/functional/foreman_bootdisk/disks_controller_test.rb +65 -25
  60. data/test/test_plugin_helper.rb +21 -3
  61. data/test/unit/concerns/host_test.rb +12 -1
  62. data/test/unit/concerns/orchestration/compute_test.rb +32 -13
  63. data/test/unit/foreman_bootdisk/renderer_test.rb +1 -1
  64. data/test/unit/iso_generator_test.rb +6 -7
  65. metadata +14 -9
@@ -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' => 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
@@ -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,5 +38,13 @@ 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
47
+
48
+ class Host::Managed::Jail < Safemode::Jail
49
+ allow :bootdisk_build?
50
+ end
@@ -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? && 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
36
- ForemanBootdisk::ISOGenerator.generate(ipxe: bootdisk_template_render, dir: Dir.tmpdir) do |image|
37
- FileUtils.mv image, bootdisk_isofile
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 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
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
@@ -2,34 +2,40 @@
2
2
 
3
3
  class Setting
4
4
  class Bootdisk < ::Setting
5
- def self.load_defaults
6
- return unless ApplicationRecord.connection.table_exists?('settings')
7
- return unless super
8
-
5
+ def self.default_settings
9
6
  ipxe = ['/usr/lib/ipxe'].find { |p| File.exist?(p) } || '/usr/share/ipxe'
10
7
  isolinux = ['/usr/lib/ISOLINUX'].find { |p| File.exist?(p) } || '/usr/share/syslinux'
11
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'
12
10
  templates = -> { Hash[ProvisioningTemplate.where(template_kind: TemplateKind.where(name: 'Bootdisk')).map { |temp| [temp[:name], temp[:name]] }] }
13
11
 
14
- Setting.transaction do
15
- [
16
- set('bootdisk_ipxe_dir', N_('Path to directory containing iPXE images'), ipxe, N_('iPXE directory')),
17
- set('bootdisk_isolinux_dir', N_('Path to directory containing isolinux images'), isolinux, N_('ISOLINUX directory')),
18
- set('bootdisk_syslinux_dir', N_('Path to directory containing syslinux images'), syslinux, N_('SYSLINUX directory')),
19
- set('bootdisk_host_template', N_('iPXE template to use for host-specific boot disks'),
20
- 'Boot disk iPXE - host', N_('Host image template'), nil, collection: templates),
21
- set('bootdisk_generic_host_template', N_('iPXE template to use for generic host boot disks'),
22
- 'Boot disk iPXE - generic host', N_('Generic image template'), nil, collection: templates),
23
- set('bootdisk_mkiso_command', N_('Command to generate ISO image, use genisoimage or mkisofs'), 'genisoimage', N_('ISO generation command')),
24
- set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching'))
25
- ].compact.each { |s| create s.update(category: 'Setting::Bootdisk') }
26
- end
27
-
28
- true
12
+ [
13
+ set('bootdisk_ipxe_dir', N_('Path to directory containing iPXE images'), ipxe, N_('iPXE directory')),
14
+ set('bootdisk_isolinux_dir', N_('Path to directory containing isolinux images'), isolinux, N_('ISOLINUX directory')),
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')),
17
+ set('bootdisk_host_template', N_('iPXE template to use for host-specific boot disks'),
18
+ 'Boot disk iPXE - host', N_('Host image template'), nil, collection: templates),
19
+ set('bootdisk_generic_host_template', N_('iPXE template to use for generic host boot disks'),
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),
23
+ set('bootdisk_mkiso_command', N_('Command to generate ISO image, use genisoimage or mkisofs'), 'genisoimage', N_('ISO generation command')),
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'))
26
+ ]
29
27
  end
30
28
 
31
29
  def self.humanized_category
32
30
  N_('Boot disk')
33
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
34
40
  end
35
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 Foreman::Exception, N_('Host is not in build mode, so the template cannot be rendered') unless host.build?
16
+ raise Foreman::Exception.new(N_('Host is not in build mode, so the template cannot be rendered')) unless host.build?
15
17
 
16
- tmpl = render_pxelinux_template(host)
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,84 +27,154 @@ module ForemanBootdisk
24
27
  hash[iso_filename] = host.url_for_boot(type)
25
28
  end
26
29
 
27
- generate(opts.merge(isolinux: tmpl, files: files), &block)
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.render_pxelinux_template(host)
31
- pxelinux_template = host.provisioning_template(kind: :PXELinux)
39
+ def self.render_template(host, kind, scope_class)
40
+ template = host.provisioning_template(kind: kind)
32
41
 
33
- raise Foreman::Exception, N_('Unable to generate disk template, PXELinux template not found.') unless pxelinux_template
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: pxelinux_template,
45
+ template: template,
37
46
  host: host,
38
- scope_class: ForemanBootdisk::Scope::FullHostBootdisk
47
+ scope_class: scope_class
39
48
  )
40
49
 
41
50
  unless template
42
51
  err = host.errors.full_messages.to_sentence
43
- raise ::Foreman::Exception.new(N_('Unable to generate disk PXELinux template: %s'), err)
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
- opts[:isolinux] = <<~ISOLINUX if opts[:isolinux].nil? && opts[:ipxe]
51
- default ipxe
52
- label ipxe
53
- kernel /ipxe
54
- initrd /script
55
- ISOLINUX
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
56
75
 
57
- Dir.mktmpdir('bootdisk') do |wd|
58
- Dir.mkdir(File.join(wd, 'build'))
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
59
87
 
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)
88
+ # And create new temporary directory:
89
+ wd = Dir.mktmpdir('bootdisk-iso-', Rails.root.join('tmp'))
90
+ Dir.mkdir(File.join(wd, 'build'))
63
91
 
64
- FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
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)
65
95
 
66
- ldlinux_source_file = File.join(Setting[:bootdisk_syslinux_dir], 'ldlinux.c32')
67
- FileUtils.cp(ldlinux_source_file, File.join(wd, 'build', 'ldlinux.c32')) if File.exist?(ldlinux_source_file)
96
+ FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
68
97
 
69
- File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
70
- file.write(opts[:isolinux])
71
- end
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)
72
102
  end
73
103
 
74
- if opts[:ipxe]
75
- ipxe_source_file = File.join(Setting[:bootdisk_ipxe_dir], 'ipxe.lkrn')
76
- raise Foreman::Exception, N_('Please ensure the ipxe-bootimgs package is installed.') unless File.exist?(ipxe_source_file)
77
-
78
- FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
79
- File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
104
+ File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
105
+ file.write(opts[:isolinux])
80
106
  end
107
+ end
81
108
 
82
- if opts[:files]
83
- if opts[:files].respond_to?(:each)
84
- opts[:files].each do |file, source|
85
- fetch(File.join(wd, 'build', file), source)
86
- end
87
- end
88
- end
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)
89
112
 
90
- iso = if opts[:dir]
91
- Tempfile.new(['bootdisk', '.iso'], opts[:dir]).path
92
- else
93
- File.join(wd, 'output.iso')
94
- end
95
- raise Foreman::Exception, N_('ISO build failed') unless system(build_mkiso_command(output_file: iso, source_directory: File.join(wd, 'build')))
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
96
116
 
97
- # Make the ISO bootable as a HDD/USB disk too
98
- raise Foreman::Exception, N_('ISO hybrid conversion failed') unless system('isohybrid', iso)
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
99
146
 
100
- yield iso
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
101
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) }
102
175
  end
103
176
 
104
- def self.build_mkiso_command(output_file:, source_directory:)
177
+ def self.build_mkiso_command(output_file:, source_directory:, extra_commands:)
105
178
  arguments = [
106
179
  "-o #{output_file}",
107
180
  '-iso-level 2',
@@ -111,6 +184,7 @@ module ForemanBootdisk
111
184
  '-boot-load-size 4',
112
185
  '-boot-info-table'
113
186
  ]
187
+ arguments.push(extra_commands) unless extra_commands.nil?
114
188
  [Setting[:bootdisk_mkiso_command], arguments, source_directory].flatten.join(' ')
115
189
  end
116
190
 
@@ -137,7 +211,14 @@ module ForemanBootdisk
137
211
  ForemanBootdisk.logger.info("Fetching #{uri}")
138
212
  write_cache = use_cache
139
213
  uri = URI(uri)
140
- Net::HTTP.start(uri.host, uri.port) do |http|
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|
141
222
  request = Net::HTTP::Get.new(uri.request_uri, 'Accept-Encoding' => 'plain')
142
223
 
143
224
  http.request(request) do |response|
@@ -153,7 +234,7 @@ module ForemanBootdisk
153
234
  # prevent multiple writes to the cache
154
235
  write_cache = false
155
236
  else
156
- response.error!
237
+ raise ::Foreman::Exception, N_(format("Unable to download boot file %{uri}, HTTP return code %{code}", uri: uri, code: response.code))
157
238
  end
158
239
  end
159
240
  end