• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-bootloader / 12048550996

27 Nov 2024 10:37AM UTC coverage: 87.484% (+0.1%) from 87.378%
12048550996

Pull #708

github

schubi2
cleanup
Pull Request #708: Supporting Grub2-BLS

210 of 240 new or added lines in 13 files covered. (87.5%)

88 existing lines in 7 files now uncovered.

3362 of 3843 relevant lines covered (87.48%)

13.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.76
/src/lib/bootloader/autoyast_converter.rb
1
# frozen_string_literal: true
2

3
require "yast"
1✔
4

5
require "bootloader/bootloader_factory"
1✔
6
require "bootloader/cpu_mitigations"
1✔
7

8
Yast.import "BootStorage"
1✔
9
Yast.import "Arch"
1✔
10

11
module Bootloader
1✔
12
  # Represents unsupported bootloader type error
13
  class UnsupportedBootloader < RuntimeError
1✔
14
    attr_reader :bootloader_name
1✔
15

16
    def initialize(bootloader_name)
1✔
17
      @bootloader_name = bootloader_name
4✔
18

19
      super "Unsupported bootloader '#{bootloader_name}'"
4✔
20
    end
21
  end
22

23
  # Converter between internal configuration model and autoyast serialization of configuration.
24
  # rubocop:disable Metrics/ClassLength converting autoyast profiles is just a lot of data
25
  class AutoyastConverter
1✔
26
    class << self
1✔
27
      include Yast::Logger
1✔
28

29
      # @param data [AutoinstProfile::BootloaderSection] Bootloader section from a profile
30
      def import(data)
1✔
31
        log.info "import data #{data.inspect}"
10✔
32

33
        bootloader = bootloader_from_data(data)
10✔
34
        return bootloader if bootloader.name == "none"
7✔
35

36
        case bootloader.name
7✔
37
        when "grub2", "grub2-efi", "grub2-bls"
38
          if ["grub2", "grub2-efi"].include?(bootloader.name)
6✔
39
            import_grub2(data, bootloader)
6✔
40
            import_grub2efi(data, bootloader)
6✔
41
            import_stage1(data, bootloader)
6✔
42
            import_device_map(data, bootloader)
6✔
43
            import_password(data, bootloader)
6✔
44
            # always nil pmbr as autoyast does not support it yet,
45
            # so use nil to always use proposed value (bsc#1081967)
46
            bootloader.pmbr_action = nil
6✔
47
          end
48
          import_default(data, bootloader.grub_default)
6✔
49
          cpu_mitigations = data.global.cpu_mitigations
6✔
50
          if cpu_mitigations
6✔
51
            bootloader.cpu_mitigations = CpuMitigations.from_string(cpu_mitigations)
×
52
          end
53
        when "systemd-boot"
54
          bootloader.menu_timeout = data.global.timeout
1✔
55
          bootloader.secure_boot = data.global.secure_boot
1✔
56
        else
57
          raise UnsupportedBootloader, bootloader.name
×
58
        end
59
        # TODO: import Initrd
60
        log.warn "autoyast profile contain sections which won't be processed" if data.sections
7✔
61

62
        bootloader
7✔
63
      end
64

65
      # FIXME: use AutoinstProfile classes
66
      def export(config)
1✔
67
        log.info "exporting config #{config.inspect}"
6✔
68

69
        bootloader_type = config.name
6✔
70
        res = { "loader_type" => bootloader_type }
6✔
71

72
        return res if bootloader_type == "none"
6✔
73

74
        res["global"] = {}
6✔
75

76
        case config.name
6✔
77
        when "grub2", "grub2-efi", "grub2-bls"
78
          global = res["global"]
4✔
79
          export_grub2(global, config) if config.name == "grub2"
4✔
80
          export_grub2efi(global, config) if config.name == "grub2-efi"
4✔
81
          export_password(global, config.password) if ["grub2", "grub2-efi"].include?(config.name)
4✔
82
          export_default(global, config.grub_default)
4✔
83
          res["global"]["cpu_mitigations"] = config.cpu_mitigations.value.to_s
4✔
84
        when "systemd-boot"
85
          res["global"]["timeout"] = config.menu_timeout
2✔
86
          res["global"]["secure_boot"] = config.secure_boot
2✔
87
        else
NEW
88
          raise UnsupportedBootloader, config.name
×
89
        end
90
        # Do not export device map as device name are very unpredictable and is used only as
91
        # work-around when automatic ones do not work for what-ever reasons ( it can really safe
92
        # your day in L3 )
93

94
        res
6✔
95
      end
96

97
    private
1✔
98

99
      def import_grub2(data, bootloader)
1✔
100
        return unless bootloader.name == "grub2"
6✔
101

102
        GRUB2_BOOLEAN_MAPPING.each do |key, method|
5✔
103
          val = data.global.public_send(key)
15✔
104
          next unless val
15✔
105

106
          bootloader.public_send(:"#{method}=", val == "true")
2✔
107
        end
108
      end
109

110
      def import_grub2efi(data, bootloader)
1✔
111
        return unless bootloader.name == "grub2-efi"
6✔
112

113
        GRUB2EFI_BOOLEAN_MAPPING.each do |key, method|
1✔
114
          val = data.global.public_send(key)
2✔
115
          next unless val
2✔
116

117
          bootloader.public_send(:"#{method}=", val == "true")
×
118
        end
119
      end
120

121
      def import_password(data, bootloader)
1✔
122
        password = data.global.password
6✔
123
        return unless password
6✔
124

125
        pwd_object = bootloader.password
1✔
126
        pwd_object.used = true
1✔
127
        # default for encrypted is false, so use it only when exacly true
128
        if password.encrypted == "true"
1✔
129
          pwd_object.encrypted_password = password.value
1✔
130
        else
131
          pwd_object.password = password.value
×
132
        end
133

134
        # default for unrestricted is true, so disable it only when exactly false
135
        pwd_object.unrestricted = password.unrestricted != "false"
1✔
136
      end
137

138
      def import_default(data, default)
1✔
139
        # import first kernel params as cpu_mitigations can later modify it
140
        import_kernel_params(data, default)
6✔
141

142
        DEFAULT_BOOLEAN_MAPPING.each do |key, method|
6✔
143
          val = data.global.public_send(key)
6✔
144
          next unless val
6✔
145

146
          default.public_send(method).value = val == "true"
1✔
147
        end
148

149
        DEFAULT_STRING_MAPPING.each do |key, method|
6✔
150
          val = data.global.public_send(key)
12✔
151
          next unless val
12✔
152

153
          default.public_send(:"#{method}=", val)
×
154
        end
155

156
        DEFAULT_ARRAY_MAPPING.each do |key, method|
6✔
157
          val = data.global.public_send(key)
6✔
158
          next unless val
6✔
159

160
          default.public_send(:"#{method}=", val.split.map { |v| v.to_sym })
2✔
161
        end
162

163
        import_timeout(data, default)
6✔
164
      end
165

166
      def import_kernel_params(data, default)
1✔
167
        DEFAULT_KERNEL_PARAMS_MAPPING.each do |key, method|
6✔
168
          val = data.global.public_send(key)
18✔
169
          next unless val
18✔
170

171
          # import resume only if device exists (bsc#1187690)
172
          resume = val[/(?:\s|\A)resume=(\S+)/, 1]
2✔
173
          if resume && !Yast::BootStorage.staging.find_by_any_name(resume)
2✔
174
            log.warn "Remove 'resume' parameter due to usage of non existing device '#{resume}'"
1✔
175
            val = val.gsub(/(?:\s|\A)resume=#{Regexp.escape(resume)}/, "")
1✔
176
          end
177

178
          default.public_send(method).replace(val)
2✔
179
        end
180
      end
181

182
      def import_timeout(data, default)
1✔
183
        return unless data.global.timeout
6✔
184

185
        global = data.global
1✔
186
        if global.hiddenmenu == "true"
1✔
187
          default.timeout = "0"
1✔
188
          default.hidden_timeout = global.timeout.to_s if global.timeout
1✔
189
        else
190
          default.timeout = global.timeout.to_s if global.timeout
×
191
          default.hidden_timeout = "0"
×
192
        end
193
      end
194

195
      def import_device_map(data, bootloader)
1✔
196
        return unless bootloader.name == "grub2"
6✔
197
        return if !Yast::Arch.x86_64 && !Yast::Arch.i386
5✔
198

199
        dev_map = data.device_map
5✔
200
        return unless dev_map
5✔
201

202
        bootloader.device_map.clear_mapping
5✔
203
        dev_map.each do |entry|
5✔
204
          bootloader.device_map.add_mapping(entry.firmware, entry.linux)
2✔
205
        end
206
      end
207

208
      STAGE1_DEVICES_MAPPING = {
1✔
209
        "boot_root"     => :boot_partition_names,
210
        "boot_boot"     => :boot_partition_names,
211
        "boot_mbr"      => :boot_disk_names,
212
        "boot_extended" => :boot_partition_names
213
      }.freeze
214
      def import_stage1(data, bootloader)
1✔
215
        return unless bootloader.name == "grub2"
6✔
216

217
        stage1 = bootloader.stage1
5✔
218
        global = data.global
5✔
219

220
        stage1.generic_mbr = global.generic_mbr == "true" unless global.generic_mbr.nil?
5✔
221

222
        if !global.activate.nil?
5✔
223
          stage1.activate = global.activate == "true"
1✔
224
        # old one from SLE9 ages, it uses boolean and not string
225
        elsif !data.activate.nil?
4✔
226
          stage1.activate = data.activate
1✔
227
        end
228

229
        import_stage1_devices(data, stage1)
5✔
230
      end
231

232
      def import_stage1_devices(data, stage1)
1✔
233
        STAGE1_DEVICES_MAPPING.each do |key, method|
5✔
234
          next if data.global.public_send(key) != "true"
20✔
235

236
          stage1.public_send(method).each do |dev_name|
1✔
237
            stage1.add_udev_device(dev_name)
1✔
238
          end
239
        end
240

241
        import_custom_devices(data, stage1)
5✔
242
      end
243

244
      def import_custom_devices(data, stage1)
1✔
245
        # SLE9 way to define boot device
246
        if data.loader_device && !data.loader_device.empty?
5✔
247
          stage1.add_udev_device(data.loader_device)
1✔
248
        end
249

250
        global = data.global
5✔
251
        return if !global.boot_custom || global.boot_custom.empty?
5✔
252

253
        global.boot_custom.split(",").each do |dev|
×
254
          stage1.add_udev_device(dev.strip)
×
255
        end
256
      end
257

258
      def bootloader_from_data(data)
1✔
259
        loader_type = data.loader_type || BootloaderFactory::DEFAULT_KEYWORD
10✔
260
        allowed = BootloaderFactory.supported_names + [BootloaderFactory::DEFAULT_KEYWORD]
10✔
261

262
        raise UnsupportedBootloader, loader_type if !allowed.include?(loader_type)
10✔
263

264
        # ensure it is clear bootloader config
265
        BootloaderFactory.clear_cache
7✔
266

267
        if loader_type == "default"
7✔
268
          BootloaderFactory.proposed
4✔
269
        else
270
          BootloaderFactory.bootloader_by_name(loader_type)
3✔
271
        end
272
      end
273

274
      # only for grub2, not for others
275
      GRUB2EFI_BOOLEAN_MAPPING = {
1✔
276
        "secure_boot"  => :secure_boot,
277
        "update_nvram" => :update_nvram
278
      }.freeze
279
      private_constant :GRUB2EFI_BOOLEAN_MAPPING
1✔
280
      def export_grub2efi(res, bootloader)
1✔
281
        GRUB2EFI_BOOLEAN_MAPPING.each do |key, method|
2✔
282
          val = bootloader.public_send(method)
4✔
283
          res[key] = val ? "true" : "false" unless val.nil?
4✔
284
        end
285
      end
286

287
      # only for grub2, not for others
288
      GRUB2_BOOLEAN_MAPPING = {
1✔
289
        "secure_boot"  => :secure_boot,
290
        "trusted_grub" => :trusted_boot,
291
        "update_nvram" => :update_nvram
292
      }.freeze
293
      private_constant :GRUB2_BOOLEAN_MAPPING
1✔
294
      def export_grub2(res, bootloader)
1✔
295
        GRUB2_BOOLEAN_MAPPING.each do |key, method|
2✔
296
          val = bootloader.public_send(method)
6✔
297
          res[key] = val ? "true" : "false" unless val.nil?
6✔
298
        end
299
      end
300

301
      DEFAULT_BOOLEAN_MAPPING = {
1✔
302
        "os_prober" => :os_prober
303
      }.freeze
304
      private_constant :DEFAULT_BOOLEAN_MAPPING
1✔
305

306
      DEFAULT_STRING_MAPPING = {
1✔
307
        "gfxmode" => :gfxmode,
308
        "serial"  => :serial_console
309
      }.freeze
310
      private_constant :DEFAULT_STRING_MAPPING
1✔
311

312
      DEFAULT_ARRAY_MAPPING = {
1✔
313
        "terminal" => :terminal
314
      }.freeze
315

316
      DEFAULT_KERNEL_PARAMS_MAPPING = {
1✔
317
        "append"            => :kernel_params,
318
        "xen_append"        => :xen_kernel_params,
319
        "xen_kernel_append" => :xen_hypervisor_params
320
      }.freeze
321
      private_constant :DEFAULT_KERNEL_PARAMS_MAPPING
1✔
322

323
      def export_default(res, default)
1✔
324
        DEFAULT_BOOLEAN_MAPPING.each do |key, method|
4✔
325
          val = default.public_send(method)
4✔
326
          res[key] = val.enabled? ? "true" : "false" if val.defined?
4✔
327
        end
328

329
        DEFAULT_KERNEL_PARAMS_MAPPING.each do |key, method|
4✔
330
          val = default.public_send(method)
12✔
331
          result = val.serialize
12✔
332
          # Do not export the 'resume' parameter as it depends on storage, which is not
333
          # cloned by default. The only exception is partition label which is cloned,
334
          # but we decided to be consistent and also remove it.
335
          # Anyways, 'resume' will be proposed if it's missing (bsc#1187690).
336
          result.gsub!(/(?:\s|\A)resume=\S+/, "")
12✔
337
          res[key] = result unless result.empty?
12✔
338
        end
339

340
        DEFAULT_STRING_MAPPING.each do |key, method|
4✔
341
          val = default.public_send(method)
8✔
342
          res[key] = val.to_s if val
8✔
343
        end
344

345
        DEFAULT_ARRAY_MAPPING.each do |key, method|
4✔
346
          val = default.public_send(method)
4✔
347
          res[key] = val.join(" ") if val
4✔
348
        end
349

350
        export_timeout(res, default)
4✔
351
      end
352

353
      def export_password(res, password)
1✔
354
        return unless password.used?
4✔
355

356
        res["password"] = {
1✔
357
          "unrestricted" => password.unrestricted? ? "true" : "false",
1✔
358
          "encrypted"    => "true",
359
          "value"        => password.encrypted_password
360
        }
361
      end
362

363
      def export_timeout(res, default)
1✔
364
        if default.hidden_timeout.to_s.to_i > 0
4✔
365
          res["hiddenmenu"] = "true"
1✔
366
          res["timeout"] = default.hidden_timeout.to_s.to_i
1✔
367
        else
368
          res["hiddenmenu"] = "false"
3✔
369
          res["timeout"] = default.timeout.to_s.to_i
3✔
370
        end
371
      end
372
    end
373
  end
374
  # rubocop:enable Metrics/ClassLength
375
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc