foreman_bootdisk 16.0.0 → 18.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yaml +19 -0
  3. data/.tx/config +1 -1
  4. data/AUTHORS +9 -1
  5. data/README.md +11 -3
  6. data/app/controllers/concerns/allowed_actions.rb +16 -0
  7. data/app/controllers/foreman_bootdisk/api/v2/disks_controller.rb +15 -8
  8. data/app/controllers/foreman_bootdisk/api/v2/subnet_disks_controller.rb +16 -4
  9. data/app/controllers/foreman_bootdisk/disks_controller.rb +29 -23
  10. data/app/controllers/foreman_bootdisk/subnet_disks_controller.rb +41 -0
  11. data/app/helpers/bootdisk_links_helper.rb +46 -0
  12. data/app/helpers/concerns/foreman_bootdisk/hosts_helper_ext.rb +58 -44
  13. data/app/helpers/concerns/foreman_bootdisk/pretty_error.rb +13 -0
  14. data/app/helpers/concerns/foreman_bootdisk/subnets_helper_ext.rb +45 -0
  15. data/app/helpers/disk_helper.rb +8 -0
  16. data/app/lib/foreman_bootdisk/scope/bootdisk.rb +28 -1
  17. data/app/lib/foreman_bootdisk/scope/full_host_bootdisk_efi.rb +15 -0
  18. data/app/models/concerns/foreman_bootdisk/compute_resources/vmware.rb +10 -1
  19. data/app/models/concerns/foreman_bootdisk/host_ext.rb +13 -6
  20. data/app/models/concerns/foreman_bootdisk/orchestration/compute.rb +38 -22
  21. data/app/models/setting/bootdisk.rb +14 -1
  22. data/app/services/foreman_bootdisk/iso_generator.rb +134 -56
  23. data/app/services/foreman_bootdisk/renderer.rb +36 -19
  24. data/app/views/foreman_bootdisk/disks/help.html.erb +16 -4
  25. data/app/views/foreman_bootdisk/generic_efi_host.erb +68 -0
  26. data/app/views/foreman_bootdisk/host.erb +7 -0
  27. data/app/views/subnets/_bootdisk_action_buttons.erb +1 -0
  28. data/app/views/subnets/_bootdisk_title_buttons.erb +1 -0
  29. data/config/routes.rb +1 -1
  30. data/db/seeds.d/50-bootdisk_templates.rb +1 -0
  31. data/lib/foreman_bootdisk/engine.rb +18 -8
  32. data/lib/foreman_bootdisk/version.rb +1 -1
  33. data/lib/tasks/bootdisk.rake +10 -24
  34. data/locale/action_names.rb +5 -0
  35. data/locale/ca/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  36. data/locale/ca/foreman_bootdisk.po +76 -13
  37. data/locale/de/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  38. data/locale/de/foreman_bootdisk.po +81 -18
  39. data/locale/en/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  40. data/locale/en/foreman_bootdisk.po +71 -8
  41. data/locale/en_GB/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  42. data/locale/en_GB/foreman_bootdisk.po +79 -16
  43. data/locale/es/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  44. data/locale/es/foreman_bootdisk.po +92 -28
  45. data/locale/foreman_bootdisk.pot +164 -71
  46. data/locale/fr/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  47. data/locale/fr/foreman_bootdisk.po +100 -36
  48. data/locale/it/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  49. data/locale/it/foreman_bootdisk.po +78 -15
  50. data/locale/ja/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  51. data/locale/ja/foreman_bootdisk.po +90 -26
  52. data/locale/ko/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  53. data/locale/ko/foreman_bootdisk.po +79 -16
  54. data/locale/pt_BR/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  55. data/locale/pt_BR/foreman_bootdisk.po +99 -36
  56. data/locale/ru/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  57. data/locale/ru/foreman_bootdisk.po +79 -16
  58. data/locale/sv_SE/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  59. data/locale/sv_SE/foreman_bootdisk.po +75 -12
  60. data/locale/zh_CN/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  61. data/locale/zh_CN/foreman_bootdisk.po +131 -68
  62. data/locale/zh_TW/LC_MESSAGES/foreman_bootdisk.mo +0 -0
  63. data/locale/zh_TW/foreman_bootdisk.po +79 -16
  64. data/test/functional/foreman_bootdisk/api/v2/disks_controller_test.rb +51 -17
  65. data/test/functional/foreman_bootdisk/api/v2/subnet_disks_controller_test.rb +23 -10
  66. data/test/functional/foreman_bootdisk/disks_controller_test.rb +60 -30
  67. data/test/functional/foreman_bootdisk/subnet_disks_controller_test.rb +51 -0
  68. data/test/test_plugin_helper.rb +21 -3
  69. data/test/unit/concerns/host_test.rb +12 -1
  70. data/test/unit/concerns/orchestration/compute_test.rb +32 -13
  71. data/test/unit/foreman_bootdisk/renderer_test.rb +1 -1
  72. data/test/unit/iso_generator_test.rb +6 -7
  73. metadata +21 -37
@@ -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
@@ -6,23 +6,36 @@ class Setting
6
6
  ipxe = ['/usr/lib/ipxe'].find { |p| File.exist?(p) } || '/usr/share/ipxe'
7
7
  isolinux = ['/usr/lib/ISOLINUX'].find { |p| File.exist?(p) } || '/usr/share/syslinux'
8
8
  syslinux = ['/usr/lib/syslinux/modules/bios', '/usr/lib/syslinux'].find { |p| File.exist?(p) } || '/usr/share/syslinux'
9
+ grub2 = ['/var/lib/tftpboot/grub2'].find { |p| File.exist?(p) } || '/var/lib/foreman/bootdisk'
9
10
  templates = -> { Hash[ProvisioningTemplate.where(template_kind: TemplateKind.where(name: 'Bootdisk')).map { |temp| [temp[:name], temp[:name]] }] }
10
11
 
11
12
  [
12
13
  set('bootdisk_ipxe_dir', N_('Path to directory containing iPXE images'), ipxe, N_('iPXE directory')),
13
14
  set('bootdisk_isolinux_dir', N_('Path to directory containing isolinux images'), isolinux, N_('ISOLINUX directory')),
14
15
  set('bootdisk_syslinux_dir', N_('Path to directory containing syslinux images'), syslinux, N_('SYSLINUX directory')),
16
+ set('bootdisk_grub2_dir', N_('Path to directory containing grubx64.efi and shimx64.efi'), grub2, N_('Grub2 directory')),
15
17
  set('bootdisk_host_template', N_('iPXE template to use for host-specific boot disks'),
16
18
  'Boot disk iPXE - host', N_('Host image template'), nil, collection: templates),
17
19
  set('bootdisk_generic_host_template', N_('iPXE template to use for generic host boot disks'),
18
20
  'Boot disk iPXE - generic host', N_('Generic image template'), nil, collection: templates),
21
+ set('bootdisk_generic_efi_host_template', N_('Grub2 template to use for generic EFI host boot disks'),
22
+ 'Boot disk Grub2 EFI - generic host', N_('Generic Grub2 EFI image template'), nil, collection: templates),
19
23
  set('bootdisk_mkiso_command', N_('Command to generate ISO image, use genisoimage or mkisofs'), 'genisoimage', N_('ISO generation command')),
20
- set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching'))
24
+ set('bootdisk_cache_media', N_('Installation media files will be cached for full host images'), true, N_('Installation media caching')),
25
+ set('bootdisk_allowed_types', N_('List of allowed bootdisk types, remove type to disable it'), Setting::Bootdisk.bootdisk_types, N_('Allowed bootdisk types'))
21
26
  ]
22
27
  end
23
28
 
24
29
  def self.humanized_category
25
30
  N_('Boot disk')
26
31
  end
32
+
33
+ def self.bootdisk_types
34
+ %w(generic host full_host subnet)
35
+ end
36
+
37
+ def self.allowed_types
38
+ Setting['bootdisk_allowed_types']
39
+ end
27
40
  end
28
41
  end
@@ -10,10 +10,13 @@ require 'uri'
10
10
  # requires syslinux, ipxe/ipxe-bootimgs, genisoimage, isohybrid
11
11
  module ForemanBootdisk
12
12
  class ISOGenerator
13
+ extend Foreman::HTTPProxy
14
+
13
15
  def self.generate_full_host(host, opts = {}, &block)
14
- raise(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,87 +27,154 @@ module ForemanBootdisk
24
27
  hash[iso_filename] = host.url_for_boot(type)
25
28
  end
26
29
 
27
- generate(opts.merge(isolinux: 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
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?(source_file)
70
- end
66
+ # isolinux template not provided for generic BIOS bootdisks
67
+ if opts[:isolinux].nil? && opts[:ipxe]
68
+ opts[:isolinux] = <<~EOT
69
+ default ipxe
70
+ label ipxe
71
+ kernel /ipxe
72
+ initrd /script
73
+ EOT
74
+ end
71
75
 
72
- File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
73
- file.write(opts[:isolinux])
74
- end
75
- end
76
+ if opts[:grub].nil? && opts[:ipxe]
77
+ opts[:grub] = <<~EOT
78
+ echo "Grub2 template not associated and/or unsupported bootdisk type."
79
+ echo "The following bootdisks are supported for EFI systems:"
80
+ echo " * FULL HOST BOOTDISK"
81
+ echo " * SUBNET GENERIC BOOTDISK"
82
+ echo "The system will poweroff in few minutes..."
83
+ sleep 500
84
+ poweroff
85
+ EOT
86
+ end
87
+
88
+ # And create new temporary directory:
89
+ wd = Dir.mktmpdir('bootdisk-iso-', Rails.root.join('tmp'))
90
+ Dir.mkdir(File.join(wd, 'build'))
76
91
 
77
- 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)
92
+ if opts[:isolinux]
93
+ isolinux_source_file = File.join(Setting[:bootdisk_isolinux_dir], 'isolinux.bin')
94
+ raise Foreman::Exception.new(N_('Please ensure the isolinux/syslinux package(s) are installed.')) unless File.exist?(isolinux_source_file)
80
95
 
81
- FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
82
- File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
96
+ FileUtils.cp(isolinux_source_file, File.join(wd, 'build', 'isolinux.bin'))
97
+
98
+ source_files = ['ldlinux.c32', 'menu.c32', 'libutil.c32']
99
+ source_files.each do |source_file|
100
+ full_path = File.join(Setting[:bootdisk_syslinux_dir], source_file)
101
+ FileUtils.cp(full_path, File.join(wd, 'build', source_file)) if File.exist?(full_path)
83
102
  end
84
103
 
85
- 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
90
- end
104
+ File.open(File.join(wd, 'build', 'isolinux.cfg'), 'w') do |file|
105
+ file.write(opts[:isolinux])
91
106
  end
107
+ end
92
108
 
93
- 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')))
109
+ if opts[:ipxe]
110
+ ipxe_source_file = File.join(Setting[:bootdisk_ipxe_dir], 'ipxe.lkrn')
111
+ raise Foreman::Exception.new(N_('Please ensure the ipxe-bootimgs package is installed.')) unless File.exist?(ipxe_source_file)
99
112
 
100
- # Make the ISO bootable as a HDD/USB disk too
101
- raise(Foreman::Exception, N_('ISO hybrid conversion failed')) unless system('isohybrid', iso)
113
+ FileUtils.cp(ipxe_source_file, File.join(wd, 'build', 'ipxe'))
114
+ File.open(File.join(wd, 'build', 'script'), 'w') { |file| file.write(opts[:ipxe]) }
115
+ end
116
+
117
+ if opts[:grub]
118
+ FileUtils.mkdir_p(File.join(wd, 'build', 'EFI', 'BOOT'))
119
+ # a copy when using CDROM directly
120
+ File.open(File.join(wd, 'build', 'EFI', 'BOOT', 'grub.cfg'), 'w') { |file| file.write(opts[:grub]) }
121
+ # a copy when using USB/HDD (copied into EFI image as well)
122
+ File.open(File.join(wd, 'build', 'grub-hdd.cfg'), 'w') do |f|
123
+ # set root to (cd0) or similar when booting via disk
124
+ f.write("search --file /ISOLINUX.BIN --set root\n")
125
+ f.write(opts[:grub])
126
+ end
127
+ efibootimg = File.join(wd, 'build', 'efiboot.img')
128
+ system_or_exception("truncate -s 4M #{efibootimg}", N_('Creating new image failed, install truncate utility'))
129
+ system_or_exception("mkfs.msdos #{efibootimg}", N_('Failed to format the ESP image via mkfs.msdos'))
130
+ system_or_exception("mmd -i #{efibootimg} '::/EFI'", N_('Failed to create a directory within the ESP image'))
131
+ system_or_exception("mmd -i #{efibootimg} '::/EFI/BOOT'", N_('Failed to create a directory within the ESP image'))
132
+ {
133
+ File.join(wd, 'build', 'grub-hdd.cfg').to_s => 'GRUB.CFG',
134
+ File.join(Setting[:bootdisk_grub2_dir], 'grubx64.efi').to_s => 'GRUBX64.EFI',
135
+ File.join(Setting[:bootdisk_grub2_dir], 'shimx64.efi').to_s => 'BOOTX64.EFI',
136
+ }.each do |src, dest|
137
+ raise(Foreman::Exception.new(N_('Ensure %{file} is readable (or update "Grub2 directory" setting)'), file: src)) unless File.exist?(src)
138
+ raise(Foreman::Exception.new(N_('Unable to mcopy %{file}'), file: src)) unless system("mcopy -m -i #{efibootimg} '#{src}' '#{"::/EFI/BOOT/#{dest}"}'")
139
+ end
140
+ mkiso_args = "-eltorito-alt-boot -e efiboot.img -no-emul-boot"
141
+ isohybrod_args = ["--uefi"]
142
+ else
143
+ mkiso_args = ''
144
+ isohybrod_args = []
145
+ end
102
146
 
103
- 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
104
153
  end
154
+
155
+ iso = if opts[:dir]
156
+ Tempfile.new(['bootdisk', '.iso'], opts[:dir]).path
157
+ else
158
+ File.join(wd, 'output.iso')
159
+ end
160
+ raise Foreman::Exception.new(N_('ISO build failed')) unless system(build_mkiso_command(output_file: iso, extra_commands: mkiso_args, source_directory: File.join(wd, 'build')))
161
+
162
+ # Make the ISO bootable as a HDD/USB disk too
163
+ raise Foreman::Exception.new(N_('ISO hybrid conversion failed: %s'), $?) unless system(*["isohybrid", isohybrod_args, iso].flatten.compact)
164
+
165
+ yield iso
166
+ ensure
167
+ # Clean the working directory (not the ISO file itself)
168
+ FileUtils.rm_f(File.join(wd, 'build'))
169
+ # Temporary directory cannot be cleaned in-process due to asynchronous send_file call.
170
+ # Also we cannot rely on systemd-tmpfiles-clean as private temporary files are not subject
171
+ # of scheduled cleanups. Let's clean bootdisks from prevous requests manually by finding
172
+ # and deleting all directories created 30 minutes ago.
173
+ delete_older_than = Time.now.to_i - (60 * 30)
174
+ Rails.root.glob('tmp/bootdisk-iso-*').select { |f| File.ctime(f) < delete_older_than }.each { |f| FileUtils.rm_f(f) }
105
175
  end
106
176
 
107
- def self.build_mkiso_command(output_file:, source_directory:)
177
+ def self.build_mkiso_command(output_file:, source_directory:, extra_commands:)
108
178
  arguments = [
109
179
  "-o #{output_file}",
110
180
  '-iso-level 2',
@@ -114,6 +184,7 @@ module ForemanBootdisk
114
184
  '-boot-load-size 4',
115
185
  '-boot-info-table'
116
186
  ]
187
+ arguments.push(extra_commands) unless extra_commands.nil?
117
188
  [Setting[:bootdisk_mkiso_command], arguments, source_directory].flatten.join(' ')
118
189
  end
119
190
 
@@ -140,7 +211,14 @@ module ForemanBootdisk
140
211
  ForemanBootdisk.logger.info("Fetching #{uri}")
141
212
  write_cache = use_cache
142
213
  uri = URI(uri)
143
- 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|
144
222
  request = Net::HTTP::Get.new(uri.request_uri, 'Accept-Encoding' => 'plain')
145
223
 
146
224
  http.request(request) do |response|
@@ -4,36 +4,46 @@ require 'uri'
4
4
 
5
5
  module ForemanBootdisk
6
6
  class Renderer
7
- def generic_template_render(subnet = nil)
8
- host = if subnet.present?
9
- # rendering a subnet-level bootdisk requires tricking the renderer into thinking it has a
10
- # valid host, without a token, but with a tftp proxy
11
- Struct.new(:token, :provision_interface, :content_source).new(
12
- nil,
13
- Struct.new(:subnet).new(subnet),
14
- nil
15
- )
16
- else
17
- Struct.new(:token, :subnet, :content_source).new(nil, nil, nil)
18
- end
19
-
20
- render_template(template: generic_host_template, host: host)
21
- end
22
-
23
- def render_template(template:, host:, scope_class: renderer_scope)
7
+ def render_template(template:, host:, scope_class: renderer_scope, subnet: nil)
24
8
  source = Foreman::Renderer.get_source(
25
9
  template: template,
26
- host: host
27
10
  )
28
11
  scope = Foreman::Renderer.get_scope(
29
12
  host: host,
30
- klass: scope_class
13
+ variables: { subnet: subnet, bootdisk: true },
14
+ klass: scope_class,
31
15
  )
32
16
  Foreman::Renderer.render(source, scope)
33
17
  end
34
18
 
19
+ def generic_template_render(subnet = nil)
20
+ render_template(template: generic_host_template, host: stub_host(subnet), subnet: subnet)
21
+ end
22
+
23
+ def generic_efi_template_render(subnet)
24
+ if subnet.httpboot?
25
+ render_template(template: generic_efi_host_template, host: stub_host(subnet), subnet: subnet)
26
+ else
27
+ ForemanBootdisk.logger.warn('HTTPBOOT feature is not enabled for subnet %s, UEFI may not be available for bootdisk' % subnet.name)
28
+ end
29
+ end
30
+
35
31
  private
36
32
 
33
+ def stub_host(subnet)
34
+ if subnet.present?
35
+ # rendering a subnet-level bootdisk requires tricking the renderer into thinking it has a
36
+ # valid host, without a token, but with a tftp proxy
37
+ Struct.new(:token, :provision_interface, :content_source).new(
38
+ nil,
39
+ Struct.new(:subnet).new(subnet),
40
+ nil
41
+ )
42
+ else
43
+ Struct.new(:token, :subnet, :content_source).new(nil, nil, nil)
44
+ end
45
+ end
46
+
37
47
  def renderer_scope
38
48
  ForemanBootdisk::Scope::Bootdisk
39
49
  end
@@ -44,5 +54,12 @@ module ForemanBootdisk
44
54
 
45
55
  template
46
56
  end
57
+
58
+ def generic_efi_host_template
59
+ template = ProvisioningTemplate.unscoped.find_by(name: Setting[:bootdisk_generic_efi_host_template])
60
+ raise ::Foreman::Exception.new(N_('Unable to find template specified by %s setting'), 'bootdisk_generic_efi_host_template') unless template
61
+
62
+ template
63
+ end
47
64
  end
48
65
  end
@@ -9,7 +9,12 @@
9
9
  <%= _('All images are usable as either ISOs or as disk images, including being written to a USB disk with `dd`.') %>
10
10
  </p>
11
11
 
12
- <h2><%= _('Host image') %></h2>
12
+ <h2><%= _('Host images') %></h2>
13
+ <p>
14
+ <%= _('These images are used for host. You can find them at host detail page.') %>
15
+ </p>
16
+
17
+ <h3><%= _('Host image') %><%= mark_disabled_bootdisk_type('host') %></h3>
13
18
  <p>
14
19
  <%= _("Per-host images contain data about a particular host registered in Foreman and set up fully static networking, avoiding the requirement for DHCP. After networking is configured, they chainload from Foreman, picking up the current OS configuration and build state from the server.") %>
15
20
  </p>
@@ -17,12 +22,17 @@
17
22
  <%= _("Once chainloaded, the OS bootloader and installer are downloaded directly from the installation media configured in Foreman, and the provisioning script (kickstart/preseed) is downloaded from Foreman.") %>
18
23
  </p>
19
24
 
20
- <h2><%= _('Full host image') %></h2>
25
+ <h3><%= _('Full host image') %><%= mark_disabled_bootdisk_type('full_host') %></h3>
21
26
  <p>
22
27
  <%= _('A variant of the per-host image which contains the OS bootloader embedded inside the disk. This may be useful if chainloading fails on certain hardware, but has the downside that the image must be regenerated for any change in the OS, bootloader or PXELinux templates.') %>
23
28
  </p>
24
29
 
25
- <h2><%= _('Generic image') %></h2>
30
+ <h2><%= _('Generic images') %></h2>
31
+ <p>
32
+ <%= _('These images are more generic than previous images. You can find them at subnet index page.') %>
33
+ </p>
34
+
35
+ <h3><%= _('Generic image') %><%= mark_disabled_bootdisk_type('generic') %></h3>
26
36
  <p>
27
37
  <%= _('Generic images are a reusable disk image that works for any host registered in Foreman. It requires a basic DHCP and DNS service to function and contact the server, but does not require DHCP reservations or static IP addresses.') %>
28
38
  </p>
@@ -30,11 +40,13 @@
30
40
  <%= _('The OS install continues using the installation media configured in Foreman, and it will typically configure static networking, depending on how the OS iPXE template is configured.') %>
31
41
  </p>
32
42
 
33
- <h2><%= _('Subnet image') %></h2>
43
+ <h3><%= _('Subnet image') %><%= mark_disabled_bootdisk_type('subnet') %></h3>
34
44
  <p>
35
45
  <%= _('Subnet images are similar to generic images, but chain-loading is done via the TFTP Smart Proxy assigned to the Subnet of the host. The smart proxy must have the "Templates" module enabled and configured.') %>
36
46
  </p>
37
47
  <p>
38
48
  <%= _('This image is generic for all hosts with a provisioning NIC on that subnet.') %>
39
49
  </p>
50
+
51
+ <p><%= _('* - These bootdisk types were disabled, you can enable them in Administer - Settings.') %></p>
40
52
  </div>