foreman_bootdisk 15.0.0 → 17.1.0

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