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

yast / yast-autoinstallation / 7347071288

28 Dec 2023 11:13AM UTC coverage: 69.009% (+0.4%) from 68.607%
7347071288

push

github

web-flow
Merge pull request #874 from yast/rubocop_update

Rubocop update

94 of 254 new or added lines in 51 files covered. (37.01%)

22 existing lines in 13 files now uncovered.

6373 of 9235 relevant lines covered (69.01%)

10.29 hits per line

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

72.6
/src/modules/AutoInstallRules.rb
1
# File:  modules/AutoInstallRules.ycp
2
# Package:  Auto-installation
3
# Summary:  Process Auto-Installation Rules
4
# Author:  Anas Nashif <nashif@suse.de>
5
#
6
# $Id$
7
require "yast"
1✔
8
require "autoinstall/xml_checks"
1✔
9
require "yast2/popup"
1✔
10
require "y2storage"
1✔
11

12
module Yast
1✔
13
  class AutoInstallRulesClass < Module
1✔
14
    include Yast::Logger
1✔
15

16
    def main
1✔
17
      Yast.import "UI"
2✔
18
      textdomain "autoinst"
2✔
19

20
      Yast.import "Arch"
2✔
21
      Yast.import "Stage"
2✔
22
      Yast.import "Installation"
2✔
23
      Yast.import "AutoinstConfig"
2✔
24
      Yast.import "XML"
2✔
25
      Yast.import "Kernel"
2✔
26
      Yast.import "Mode"
2✔
27
      Yast.import "Linuxrc"
2✔
28
      Yast.import "Profile"
2✔
29
      Yast.import "Label"
2✔
30
      Yast.import "Report"
2✔
31
      Yast.import "Popup"
2✔
32
      Yast.import "URL"
2✔
33
      Yast.import "IP"
2✔
34
      Yast.import "Product"
2✔
35
      Yast.import "Hostname"
2✔
36
      Yast.import "OSRelease"
2✔
37

38
      Yast.include self, "autoinstall/io.rb"
2✔
39

40
      reset
2✔
41
    end
42

43
    # Reset the module's state
44
    #
45
    # @return nil
46
    #
47
    # @see #AutoInstallRules
48
    def reset
1✔
49
      @userrules = false
12✔
50
      @dontmergeIsDefault = true
12✔
51
      @dontmergeBackup = []
12✔
52

53
      @Behaviour = :many
12✔
54

55
      # ///////////////////////////////////////////
56
      # Pre-defined Rules
57
      # ///////////////////////////////////////////
58

59
      # All system attributes;
60
      @ATTR = {}
12✔
61

62
      @installed_product = ""
12✔
63
      @installed_product_version = ""
12✔
64
      @hostname = ""
12✔
65
      @hostaddress = nil
12✔
66
      @network = ""
12✔
67
      @domain = ""
12✔
68
      @arch = ""
12✔
69
      @karch = ""
12✔
70

71
      # Taken from smbios
72
      @product = ""
12✔
73

74
      # Taken from smbios
75
      @product_vendor = ""
12✔
76

77
      # Taken from smbios
78
      @board_vendor = ""
12✔
79

80
      # Taken from smbios
81
      @board = ""
12✔
82

83
      @memsize = 0
12✔
84
      @disksize = []
12✔
85
      @totaldisk = 0
12✔
86
      @hostid = ""
12✔
87
      @mac = ""
12✔
88
      @efi = "no"
12✔
89
      @linux = 0
12✔
90
      @others = 0
12✔
91
      @xserver = ""
12✔
92

93
      # ///////////////////////////////////////////
94
      # ///////////////////////////////////////////
95
      @NonLinuxPartitions = []
12✔
96
      @LinuxPartitions = []
12✔
97
      @UserRules = {}
12✔
98

99
      # Local Variables
100
      @shell = ""
12✔
101
      @env = {}
12✔
102

103
      @tomerge = []
12✔
104
      @element2file = {}
12✔
105
      AutoInstallRules()
12✔
106
    end
107

108
    # Cleanup XML file from namespaces put by xslt
109
    #
110
    # @param in_ [String] Path to the profile to clean-up
111
    # @param out [String] Path to the output file
112
    def XML_cleanup(in_, out)
1✔
113
      ycpin = XML.XMLToYCPFile(in_)
1✔
114
      Builtins.y2debug("Writing clean XML file to  %1, YCP is (%2)", out, ycpin)
1✔
115
      XML.YCPToXMLFile(:profile, ycpin, out)
1✔
116
    end
117

118
    # StdErrLog()
119
    # Dialog for error messages
120
    def StdErrLog(stderr)
1✔
121
      UI.OpenDialog(
1✔
122
        Opt(:decorated),
123
        VBox(
124
          VSpacing(0.5),
125
          HSpacing(50),
126
          HBox(
127
            HSpacing(0.5),
128
            LogView(Id(:log), Label.ErrorMsg, 10, 100),
129
            HSpacing(0.5)
130
          ),
131
          VSpacing(0.2),
132
          PushButton(Id(:ok), Opt(:default), Label.OKButton),
133
          VSpacing(0.5)
134
        )
135
      )
136

137
      UI.ChangeWidget(Id(:log), :Value, stderr)
1✔
138
      UI.UserInput
1✔
139
      UI.CloseDialog
1✔
140

141
      nil
142
    end
143

144
    # getMAC()
145
    # Return MAC address of active device
146
    # @return [String] mac address
147
    def getMAC
1✔
148
      tmpmac = ""
12✔
149
      if Stage.initial
12✔
150
        cmd = 'ip link show | grep link/ether | head -1 | sed -e "s:^.*link/ether.::" -e "s: .*::"'
5✔
151
        ret = SCR.Execute(path(".target.bash_output"), cmd)
5✔
152
        Builtins.y2milestone("mac Addr ret:%1", ret)
5✔
153
        tmpmac = ret.fetch("stdout", "")
5✔
154
      end
155
      Builtins.y2milestone("mac Addr tmp:%1", tmpmac)
12✔
156
      cleanmac = Builtins.deletechars(tmpmac || "", ":\n")
12✔
157
      Builtins.y2milestone("mac Addr mac:%1", cleanmac)
12✔
158
      cleanmac
12✔
159
    end
160

161
    # Return the network part of the hostaddress
162
    #
163
    # @example
164
    #   AutoInstallRules.getNetwork #=> "192.168.122.0"
165
    #
166
    # @return [String] Network part of the hostaddress
167
    #
168
    # @see hostaddress
169
    def getNetwork
1✔
170
      ip_route = SCR.Execute(path(".target.bash_output"), "/usr/sbin/ip route")
3✔
171

172
      # Regexp to fetch match the network address.
173
      regexp = /([\h:.]+)\/\d+ .+src #{hostaddress}/
3✔
174
      ret = ip_route["stdout"][regexp, 1]
3✔
175
      log.warn "Cannot find network address through 'ip': #{ip_route}" unless ret
3✔
176

177
      ret
3✔
178
    end
179

180
    # Return host id (hex ip)
181
    #
182
    # @return [String] host ID
183
    def getHostid
1✔
184
      IP.ToHex(hostaddress)
14✔
185
    end
186

187
    # Return host name
188
    # @return [String] host name
189
    def getHostname
1✔
190
      ret = Convert.to_map(
3✔
191
        SCR.Execute(path(".target.bash_output"), "/bin/hostname")
192
      )
193
      Builtins.y2milestone("getHostname ret:%1", ret)
3✔
194
      name = ""
3✔
195
      if Ops.get_integer(ret, "exit", -1) == 0
3✔
196
        name = Ops.get(
1✔
197
          Builtins.splitstring(Ops.get_string(ret, "stdout", ""), "\n"),
198
          0,
199
          ""
200
        )
201
      end
202
      if Builtins.isempty(name)
3✔
203
        name = Convert.to_string(SCR.Read(path(".etc.install_inf.Hostname")))
2✔
204
      end
205
      Builtins.y2milestone("getHostname name:%1", name)
3✔
206
      name
3✔
207
    end
208

209
    # Probe all system data to build  a set of rules
210
    # @return [void]
211
    def ProbeRules
1✔
212
      return if !@ATTR.empty?
4✔
213

214
      # SMBIOS Data
215
      bios = Convert.to_list(SCR.Read(path(".probe.bios")))
1✔
216

217
      if Builtins.size(bios) != 1
1✔
218
        Builtins.y2warning("Warning: BIOS list size is %1", Builtins.size(bios))
1✔
219
      end
220

221
      biosinfo = Ops.get_map(bios, 0, {})
1✔
222
      smbios = Ops.get_list(biosinfo, "smbios", [])
1✔
223

224
      sysinfo = {}
1✔
225
      boardinfo = {}
1✔
226

227
      Builtins.foreach(smbios) do |inf|
1✔
NEW
228
        case Ops.get_string(inf, "type", "")
×
229
        when "sysinfo"
230
          sysinfo = deep_copy(inf)
×
231
        when "boardinfo"
232
          boardinfo = deep_copy(inf)
×
233
        end
234
      end
235

236
      if Ops.greater_than(Builtins.size(sysinfo), 0)
1✔
237
        @product = Ops.get_string(sysinfo, "product", "default")
×
238
        @product_vendor = Ops.get_string(sysinfo, "manufacturer", "default")
×
239
      end
240

241
      if Ops.greater_than(Builtins.size(boardinfo), 0)
1✔
242
        @board = Ops.get_string(boardinfo, "product", "default")
×
243
        @board_vendor = Ops.get_string(boardinfo, "manufacturer", "default")
×
244
      end
245

246
      Ops.set(@ATTR, "product", @product)
1✔
247
      Ops.set(@ATTR, "product_vendor", @product_vendor)
1✔
248
      Ops.set(@ATTR, "board", @board)
1✔
249
      Ops.set(@ATTR, "board_vendor", @board_vendor)
1✔
250

251
      #
252
      # Architecture
253
      #
254

255
      @arch = Arch.architecture
1✔
256
      @karch = Ops.get(Kernel.GetPackages, 0, "kernel-default")
1✔
257

258
      Ops.set(@ATTR, "arch", @arch)
1✔
259
      Ops.set(@ATTR, "karch", @karch)
1✔
260

261
      #
262
      # Memory
263
      #
264

265
      memories = Convert.to_list(SCR.Read(path(".probe.memory")))
1✔
266
      memory = Ops.get_integer(
1✔
267
        memories,
268
        [0, "resource", "phys_mem", 0, "range"],
269
        0
270
      )
271
      @memsize = Ops.divide(memory, 1024 * 1024)
1✔
272
      Ops.set(@ATTR, "memsize", @memsize)
1✔
273

274
      #
275
      # Disk sizes
276
      #
277
      @totaldisk = 0
1✔
278
      @disksize = []
1✔
279
      one_mega = Y2Storage::DiskSize.MiB(1)
1✔
280
      Y2Storage::StorageManager.instance.probed.disks.each do |disk|
1✔
281
        size = disk.size.ceil(one_mega).to_i / one_mega.to_i
1✔
282
        @totaldisk += size
1✔
283
        @disksize << { "device" => disk.name, "size" => size }
1✔
284
      end
285
      Builtins.y2milestone("disksize: %1", @disksize)
1✔
286
      Ops.set(@ATTR, "totaldisk", @totaldisk)
1✔
287
      #
288
      # MAC
289
      #
290
      Ops.set(@ATTR, "mac", @mac)
1✔
291

292
      #
293
      # EFI Boot
294
      @efi = boot_efi?
1✔
295
      @ATTR["efi"] = @efi
1✔
296

297
      #
298
      # Network
299
      #
300
      Ops.set(@ATTR, "hostaddress", hostaddress)
1✔
301

302
      #
303
      # Hostid (i.e. a8c00101);
304
      #
305
      Ops.set(@ATTR, "hostid", @hostid)
1✔
306

307
      Ops.set(@ATTR, "hostname", getHostname)
1✔
308
      @domain = Hostname.CurrentDomain
1✔
309
      Ops.set(@ATTR, "domain", @domain)
1✔
310
      @network = getNetwork
1✔
311
      Ops.set(@ATTR, "network", @network)
1✔
312
      @xserver = Convert.to_string(SCR.Read(path(".etc.install_inf.XServer")))
1✔
313
      Ops.set(@ATTR, "xserver", @xserver)
1✔
314

315
      probed_disks = Y2Storage::StorageManager.instance.probed_disk_analyzer
1✔
316
      @NonLinuxPartitions = probed_disks.windows_partitions
1✔
317
      @others = @NonLinuxPartitions.size
1✔
318
      Builtins.y2milestone("Other primaries: %1", @NonLinuxPartitions)
1✔
319
      @LinuxPartitions = probed_disks.linux_partitions
1✔
320
      @linux = @LinuxPartitions.size
1✔
321
      Builtins.y2milestone("Other linux parts: %1", @LinuxPartitions)
1✔
322

323
      @installed_product = Yast::OSRelease.ReleaseInformation
1✔
324
      @installed_product_version = Yast::OSRelease.ReleaseVersion
1✔
325
      Ops.set(@ATTR, "installed_product", @installed_product)
1✔
326
      Ops.set(@ATTR, "installed_product_version", @installed_product_version)
1✔
327

328
      log.info "Installing #{@installed_product}, " \
1✔
329
               "version: #{@installed_product_version}"
330
      log.info "ATTR=#{@ATTR}"
1✔
331

332
      nil
333
    end
334

335
    # Create shell command for rule verification
336
    # @param [Boolean] match
337
    # @param [String] var
338
    # @param [Object] val
339
    # @param [String] op
340
    # @param [String] matchtype
341
    # @return [void]
342
    def shellseg(match, var, val, op, matchtype)
1✔
343
      val = deep_copy(val)
13✔
344
      case op
13✔
345
      when "and"
346
        op = " && "
9✔
347
      when "or"
348
        op = " || "
4✔
349
      end
350

351
      tmpshell = " ( ["
13✔
352
      Builtins.y2debug("Match type: %1", matchtype)
13✔
353
      if Ops.is_string?(val) && Convert.to_string(val) == "*"
13✔
354
        # match anything
355
        tmpshell = Ops.add(tmpshell, " \"1\" = \"1\" ")
×
356
      elsif matchtype == "exact"
13✔
357
        tmpshell = Ops.add(
9✔
358
          tmpshell,
359
          Builtins.sformat(" \"$%1\" = \"%2\" ", var, val)
360
        )
361
      elsif matchtype == "greater"
4✔
362
        tmpshell = Ops.add(
1✔
363
          tmpshell,
364
          Builtins.sformat(" \"$%1\" -gt \"%2\" ", var, val)
365
        )
366
      elsif matchtype == "lower"
3✔
367
        tmpshell = Ops.add(
1✔
368
          tmpshell,
369
          Builtins.sformat(" \"$%1\" -lt \"%2\" ", var, val)
370
        )
371
      elsif matchtype == "range"
2✔
372
        range = Builtins.splitstring(Builtins.tostring(val), "-")
1✔
373
        Builtins.y2debug("Range: %1", range)
1✔
374
        tmpshell = Ops.add(
1✔
375
          tmpshell,
376
          Builtins.sformat(
377
            " \"$%1\" -ge \"%2\" -a \"$%1\" -le \"%3\" ",
378
            var,
379
            Ops.get(range, 0, "0"),
380
            Ops.get(range, 1, "0")
381
          )
382
        )
383
      elsif matchtype == "regex"
1✔
384
        tmpshell = Ops.add(
1✔
385
          tmpshell,
386
          Builtins.sformat("[ \"$%1\" =~ %2 ]", var, val)
387
        )
388
      end
389

390
      @shell = if match
13✔
391
        Ops.add(@shell, Builtins.sformat(" %1 %2] )", op, tmpshell))
4✔
392
      else
393
        Ops.add(tmpshell, "] ) ")
9✔
394
      end
395

396
      Builtins.y2milestone("var: %1, val: %2", var, val)
13✔
397
      Builtins.y2milestone("shell: %1", @shell)
13✔
398
      nil
399
    end
400

401
    # Verify rules using the shell
402
    # @return [Fixnum]
403
    def verifyrules
1✔
404
      script = Builtins.sformat("if %1; then exit 0; else exit 1; fi", @shell)
3✔
405
      ret = Convert.to_map(
3✔
406
        SCR.Execute(path(".target.bash_output"), script, @env)
407
      )
408

409
      Builtins.y2milestone("Bash return: %1 (%2) (%3)", script, ret, @env)
3✔
410

411
      Ops.get_integer(ret, "exit", -1)
3✔
412
    end
413

414
    def SubVars(file)
1✔
415
      Builtins.y2milestone("file: %1", file)
3✔
416
      var = ""
3✔
417
      first = Builtins.findfirstof(file, "@")
3✔
418
      last = Builtins.findlastof(file, "@")
3✔
419
      if !first.nil? && !last.nil?
3✔
420
        ffirst = Ops.add(Convert.to_integer(first), 1)
×
421
        llast = Convert.to_integer(last)
×
422
        var = Builtins.substring(file, ffirst, Ops.subtract(llast, ffirst)) if first != last
×
423
      end
424
      Builtins.y2milestone("var: %1", var)
3✔
425
      if var != ""
3✔
426
        val = Ops.get_string(@ATTR, var, "")
×
427
        new = Builtins.regexpsub(
×
428
          file,
429
          "(.*)@.*@(.*)",
430
          Builtins.sformat("\\1%1\\2", val)
431
        )
432
        return new if new != ""
×
433
      end
434
      Builtins.y2milestone("val: %1", file)
3✔
435
      file
3✔
436
    end
437

438
    # Read rules file
439
    # @return [void]
440
    def Read
1✔
441
      begin
442
        @UserRules = XML.XMLToYCPFile(AutoinstConfig.local_rules_file)
4✔
443
      rescue XMLDeserializationError => e
444
        message = _("Parsing the rules file failed. XML parser reports:\n")
1✔
445
        Popup.Error(message + e.message)
1✔
446
        @UserRules = nil
1✔
447
      end
448
      Builtins.y2milestone("Rules: %1", @UserRules)
4✔
449

450
      rulelist = Ops.get_list(@UserRules, "rules", [])
4✔
451
      if rulelist.nil? # check result of implicit type conversion
4✔
452
        Builtins.y2error("Key 'rules' has wrong type")
×
453
        rulelist = []
×
454
      end
455

456
      ismatch = false
4✔
457
      go_on = true
4✔
458
      AutoInstallRules.ProbeRules if !rulelist.empty?
4✔
459
      Builtins.foreach(rulelist) do |ruleset|
4✔
460
        Builtins.y2milestone("Ruleset: %1", ruleset)
3✔
461
        rls = ruleset.keys
3✔
462
        if rls.include?("result")
3✔
463
          rls.reject! { |r| r == "result" }
14✔
464
          rls.push("result")
3✔
465
        end
466
        op = Ops.get_string(ruleset, "operator", "and")
3✔
467
        rls.reject! { |r| r == "op" }
14✔
468
        Builtins.y2milestone("Orderes Rules: %1", rls)
3✔
469
        if go_on
3✔
470
          Builtins.foreach(rls) do |rule|
3✔
471
            ruledef = ruleset.fetch(rule, {})
11✔
472
            Builtins.y2milestone("Rule: %1", rule)
11✔
473
            Builtins.y2milestone("Ruledef: %1", ruledef)
11✔
474
            match = Ops.get_string(ruledef, "match", "undefined")
11✔
475
            matchtype = Ops.get_string(ruledef, "match_type", "exact")
11✔
476
            easy_rules = [
477
              "hostname",
11✔
478
              "hostaddress",
479
              "installed_product_version",
480
              "installed_product",
481
              "domain",
482
              "efi",
483
              "network",
484
              "mac",
485
              "karch",
486
              "hostid",
487
              "arch",
488
              "board",
489
              "board_vendor",
490
              "product_vendor",
491
              "product"
492
            ]
493
            if Builtins.contains(easy_rules, rule)
11✔
494
              shellseg(ismatch, rule, match, op, matchtype)
6✔
495
              ismatch = true
6✔
496
              Ops.set(@env, rule, Ops.get_string(@ATTR, rule, ""))
6✔
497
            elsif rule == "custom1" || rule == "custom2" || rule == "custom3" ||
5✔
498
                rule == "custom4" ||
499
                rule == "custom5"
500
              script = Ops.get_string(ruledef, "script", "exit -1")
×
501
              tmpdir = AutoinstConfig.tmpDir
×
502

503
              scriptPath = Builtins.sformat(
×
504
                "%1/%2",
505
                tmpdir,
506
                Ops.add("rule_", rule)
507
              )
508

509
              Builtins.y2milestone("Writing rule script into %1", scriptPath)
×
510
              SCR.Write(path(".target.string"), scriptPath, script)
×
511

512
              out = Convert.to_map(
×
513
                SCR.Execute(
514
                  path(".target.bash_output"),
515
                  Ops.add("/bin/sh ", scriptPath),
516
                  {}
517
                )
518
              )
519
              script_result = Ops.get_string(out, "stdout", "")
×
520
              shellseg(ismatch, rule, match, op, matchtype)
×
521
              ismatch = true
×
522
              Ops.set(@ATTR, rule, script_result)
×
523
              Ops.set(@env, rule, script_result)
×
524
            elsif rule == "linux"
5✔
525
              shellseg(ismatch, rule, match, op, matchtype)
×
526
              ismatch = true
×
527
              Ops.set(@env, rule, @linux)
×
528
            elsif rule == "others"
5✔
529
              shellseg(ismatch, rule, match, op, matchtype)
×
530
              ismatch = true
×
531
              Ops.set(@env, rule, @others)
×
532
            elsif rule == "xserver"
5✔
533
              shellseg(ismatch, rule, match, op, matchtype)
×
534
              ismatch = true
×
535
              Ops.set(@env, rule, @xserver)
×
536
            elsif rule == "memsize"
5✔
537
              shellseg(ismatch, rule, match, op, matchtype)
×
538
              ismatch = true
×
539
              Ops.set(@env, rule, @memsize)
×
540
            elsif rule == "totaldisk"
5✔
541
              shellseg(ismatch, rule, match, op, matchtype)
×
542
              ismatch = true
×
543
              Ops.set(@env, rule, @totaldisk)
×
544
            elsif rule == "disksize"
5✔
545
              Builtins.y2debug("creating rule check for disksize")
×
546
              disk = Builtins.splitstring(match, " ")
×
547
              i = 0
×
548
              t = ""
×
NEW
549
              t = if @shell == ""
×
NEW
550
                Ops.add(@shell, Builtins.sformat(" ( "))
×
551
              else
UNCOV
552
                Ops.add(
×
553
                  @shell,
554
                  Builtins.sformat(" %1 ( ", (op == "and") ? "&&" : "||")
×
555
                )
556
              end
557
              Builtins.foreach(@disksize) do |dev|
×
558
                var1 = Builtins.sformat("disksize_size%1", i)
×
559
                var2 = Builtins.sformat("disksize_device%1", i)
×
NEW
560
                case matchtype
×
561
                when "exact"
UNCOV
562
                  t = Ops.add(
×
563
                    t,
564
                    Builtins.sformat(
565
                      " [ \"$%1\" = \"%2\" -a \"$%3\" = \"%4\" ] ",
566
                      var1,
567
                      Ops.get(disk, 1, ""),
568
                      var2,
569
                      Ops.get(disk, 0, "")
570
                    )
571
                  )
572
                when "greater"
573
                  t = Ops.add(
×
574
                    t,
575
                    Builtins.sformat(
576
                      " [ \"$%1\" -gt \"%2\"  -a \"$%3\" = \"%4\" ] ",
577
                      var1,
578
                      Ops.get(disk, 1, ""),
579
                      var2,
580
                      Ops.get(disk, 0, "")
581
                    )
582
                  )
583
                when "lower"
584
                  t = Ops.add(
×
585
                    t,
586
                    Builtins.sformat(
587
                      " [ \"$%1\" -lt \"%2\" -a \"$%3\" = \"%4\" ] ",
588
                      var1,
589
                      Ops.get(disk, 1, ""),
590
                      var2,
591
                      Ops.get(disk, 0, "")
592
                    )
593
                  )
594
                end
595
                Ops.set(@env, var1, Ops.get_integer(dev, "size", -1))
×
596
                Ops.set(@env, var2, Ops.get_string(dev, "device", ""))
×
597
                i = Ops.add(i, 1)
×
598
                t = Ops.add(t, " || ") if Ops.greater_than(Builtins.size(@disksize), i)
×
599
              end
600
              t = Ops.add(t, " ) ")
×
601
              @shell = t
×
602
              Builtins.y2debug("shell: %1", @shell)
×
603
              ismatch = true
×
604
            elsif rule == "result"
5✔
605
              profile_name = Ops.get_string(ruledef, "profile", "")
3✔
606
              profile_name = SubVars(profile_name)
3✔
607
              if Builtins.haskey(ruleset, "dialog")
3✔
608
                Ops.set(
×
609
                  @element2file,
610
                  Ops.get_integer(ruleset, ["dialog", "element"], 0),
611
                  profile_name
612
                )
613
              end
614
              if verifyrules == 0
3✔
615
                Builtins.y2milestone("Final Profile name: %1", profile_name)
3✔
616
                if Ops.get_boolean(ruledef, "match_with_base", true)
3✔
617
                  @tomerge = Builtins.add(@tomerge, profile_name)
3✔
618
                end
619
                # backdoor for merging problems.
620
                if Builtins.haskey(ruledef, "dont_merge")
3✔
621
                  if @dontmergeIsDefault
×
622
                    @dontmergeBackup = deep_copy(AutoinstConfig.dontmerge)
×
623
                    AutoinstConfig.dontmerge = []
×
624
                  end
625
                  AutoinstConfig.dontmerge = Convert.convert(
×
626
                    Builtins.union(
627
                      AutoinstConfig.dontmerge,
628
                      Ops.get_list(ruledef, "dont_merge", [])
629
                    ),
630
                    from: "list",
631
                    to:   "list <string>"
632
                  )
633
                  @dontmergeIsDefault = false
×
634
                  Builtins.y2milestone(
×
635
                    "user defined dont_merge for rules found. dontmerge is %1",
636
                    AutoinstConfig.dontmerge
637
                  )
638
                end
639
                go_on = Ops.get_boolean(ruledef, "continue", false)
3✔
640
              else
641
                go_on = true
×
642
              end
643
              @shell = ""
3✔
644
              ismatch = false
3✔
645
            end
646
          end
647
        end
648
      end
649

650
      dialogOrder = []
4✔
651
      Builtins.y2milestone("element2file=%1", @element2file)
4✔
652
      Builtins.foreach(rulelist) do |rule|
4✔
653
        if Builtins.haskey(rule, "dialog") &&
3✔
654
            !Builtins.contains(
655
              dialogOrder,
656
              Ops.get_integer(rule, ["dialog", "dialog_nr"], 0)
657
            )
658
          dialogOrder = Builtins.add(
×
659
            dialogOrder,
660
            Ops.get_integer(rule, ["dialog", "dialog_nr"], 0)
661
          )
662
        end
663
      end
664
      dialogOrder = Builtins.sort(dialogOrder)
4✔
665

666
      dialogIndex = 0
4✔
667
      while Ops.less_or_equal(
4✔
668
        dialogIndex,
669
        Ops.subtract(Builtins.size(dialogOrder), 1)
670
      )
671
        dialogNr = Ops.get(dialogOrder, dialogIndex, 0)
×
672
        dialog_term = VBox()
×
673
        element_nr = 0
×
674
        timeout = 0
×
675
        title = "Choose XML snippets to merge"
×
676
        conflictsCounter = {}
×
677
        Builtins.foreach(rulelist) do |rule|
×
678
          if Builtins.haskey(rule, "dialog")
×
679
            element_nr = Ops.get_integer(
×
680
              rule,
681
              ["dialog", "element"],
682
              element_nr
683
            )
684
            file = Ops.get(@element2file, element_nr, "")
×
685
            element_nr = Ops.add(element_nr, 1)
×
686
            if Builtins.contains(@tomerge, file)
×
687
              Builtins.foreach(Ops.get_list(rule, ["dialog", "conflicts"], [])) do |c|
×
688
                conflictsCounter[c] = 0
×
689
              end
690
            end
691
          end
692
        end
693

694
        Builtins.foreach(rulelist) do |rule|
×
695
          if Builtins.haskey(rule, "dialog") &&
×
696
              Ops.get_integer(rule, ["dialog", "dialog_nr"], 0) == dialogNr
697
            element_nr = Ops.get_integer(
×
698
              rule,
699
              ["dialog", "element"],
700
              element_nr
701
            )
702
            title = Ops.get_string(rule, ["dialog", "title"], title)
×
703
            file = Ops.get(@element2file, element_nr, "")
×
704
            on = Builtins.contains(@tomerge, file) ? true : false
×
705
            button = Left(
×
706
              CheckBox(
707
                Id(element_nr),
708
                Opt(:notify),
709
                Ops.get_string(rule, ["dialog", "question"], file),
710
                on
711
              )
712
            )
713
            if Builtins.haskey(Ops.get(rule, "dialog", {}), "timeout")
×
714
              timeout = Ops.get_integer(rule, ["dialog", "timeout"], 0)
×
715
            end
716
            dialog_term = Builtins.add(dialog_term, button)
×
717
            element_nr = Ops.add(element_nr, 1)
×
718
          end
719
        end
720

721
        if Ops.greater_than(element_nr, 0)
×
722
          UI.OpenDialog(
×
723
            Opt(:decorated),
724
            VBox(
725
              Label(title),
726
              VSpacing(1),
727
              dialog_term,
728
              VSpacing(1),
729
              HBox(
730
                HStretch(),
731
                PushButton(Id(:back), Label.BackButton),
732
                PushButton(Id(:ok), Label.OKButton)
733
              )
734
            )
735
          )
736
          UI.ChangeWidget(Id(:back), :Enabled, false) if dialogIndex == 0
×
737

738
          # If there are conflicting items set them all to enabled/not_selected
739
          # in order to let the user decide at first.
740
          conflictsCounter.each do |c, _n|
×
741
            UI.ChangeWidget(Id(c), :Enabled, true)
×
742
            UI.ChangeWidget(Id(c), :Value, false)
×
743
          end
744

745
          loop do
×
746
            ret = nil
×
747
            ret = if timeout == 0
×
748
              UI.UserInput
×
749
            else
750
              UI.TimeoutUserInput(Ops.multiply(timeout, 1000))
×
751
            end
752
            timeout = 0
×
753
            element_nr = 0
×
754
            if ret == :ok || ret == :timeout || ret == :back
×
755
              dialogIndex = Ops.subtract(dialogIndex, 2) if ret == :back
×
756
              break
×
757
            else
758
              if Convert.to_boolean(UI.QueryWidget(Id(ret), :Value))
×
759
                @tomerge = Builtins.add(
×
760
                  @tomerge,
761
                  Ops.get(@element2file, Builtins.tointeger(ret), "")
762
                )
763
              else
764
                file = Ops.get(@element2file, Builtins.tointeger(ret), "")
×
765
                @tomerge = Builtins.filter(@tomerge) { |f| file != f }
×
766
              end
767
              conflicts = []
×
768
              Builtins.foreach(rulelist) do |r|
×
769
                if Ops.get_integer(r, ["dialog", "element"], -1) ==
×
770
                    Builtins.tointeger(ret)
771
                  conflicts = Ops.get_list(r, ["dialog", "conflicts"], [])
×
772
                  raise Break
×
773
                end
774
              end
775
              Builtins.foreach(conflicts) do |element|
×
776
                if Convert.to_boolean(UI.QueryWidget(Id(ret), :Value))
×
777
                  Ops.set(
×
778
                    conflictsCounter,
779
                    element,
780
                    Ops.add(Ops.get(conflictsCounter, element, 0), 1)
781
                  )
782
                elsif Ops.greater_than(Ops.get(conflictsCounter, element, 0), 0)
×
783
                  Ops.set(
×
784
                    conflictsCounter,
785
                    element,
786
                    Ops.subtract(Ops.get(conflictsCounter, element, 0), 1)
787
                  )
788
                end
789
              end
790
              Builtins.foreach(conflictsCounter) do |key, v|
×
791
                if Ops.greater_than(v, 0)
×
792
                  UI.ChangeWidget(Id(key), :Enabled, false)
×
793
                  UI.ChangeWidget(Id(key), :Value, false)
×
794
                else
795
                  UI.ChangeWidget(Id(key), :Enabled, true)
×
796
                end
797
              end
798
            end
799
            Builtins.y2milestone("tomerge is now = %1", @tomerge)
×
800
            Builtins.y2milestone(
×
801
              "conflictsCounter is now = %1",
802
              conflictsCounter
803
            )
804
          end
805
          UI.CloseDialog
×
806
          dialogIndex = Ops.add(dialogIndex, 1)
×
807
        end
808
        Builtins.y2milestone(
×
809
          "changing rules to merge to %1 because of user selection",
810
          @tomerge
811
        )
812
      end
813
      nil
814
    end
815

816
    # Return list of file to merge (Order matters)
817
    # @return [Array] list of files
818
    def Files
1✔
819
      deep_copy(@tomerge)
3✔
820
    end
821

822
    # Retrieves the rules files to merge
823
    #
824
    # @return [Boolean] true if the files were retrieved; false otherwise
825
    def GetRules
1✔
826
      Builtins.y2milestone("Getting Rules: %1", @tomerge)
6✔
827

828
      scheme = AutoinstConfig.scheme
6✔
829
      host = AutoinstConfig.host
6✔
830
      directory = AutoinstConfig.directory
6✔
831

832
      valid = []
6✔
833
      stop = false
6✔
834
      Builtins.foreach(@tomerge) do |file|
6✔
835
        if !stop
8✔
836
          dir = dirname(file)
8✔
837
          if dir != ""
8✔
838
            SCR.Execute(
8✔
839
              path(".target.mkdir"),
840
              Ops.add(Ops.add(AutoinstConfig.local_rules_location, "/"), dir)
841
            )
842
          end
843

844
          localfile = Ops.add(
8✔
845
            Ops.add(AutoinstConfig.local_rules_location, "/"),
846
            file
847
          )
848
          if !::File.exist?(localfile) && !Get(
8✔
849
            scheme,
850
            host,
851
            Ops.add(Ops.add(directory, "/"), file),
852
            localfile
853
          )
854
            Builtins.y2error(
2✔
855
              "Error while fetching file:  %1",
856
              Ops.add(Ops.add(directory, "/"), file)
857
            )
858
            # Download has produced an error which is stored in the target
859
            # file --> delete it.
860
            ::FileUtils.rm(localfile) if ::File.exist?(localfile)
2✔
861
          else
862
            stop = true if @Behaviour == :one
6✔
863
            valid = Builtins.add(valid, file)
6✔
864
          end
865
        end
866
      end
867
      @tomerge = deep_copy(valid)
6✔
868
      if Builtins.size(@tomerge) == 0
6✔
869
        Builtins.y2milestone("No files from rules found")
3✔
870
        false
3✔
871
      else
872
        true
3✔
873
      end
874
    end
875

876
    # TODO: Move the responsibility of merging profiles to a specific class
877
    # removing also the duplication of code between this module and the
878
    # AutoinstClass one.
879

880
    MERGE_CMD = "/usr/bin/xsltproc".freeze
1✔
881
    MERGE_DEFAULTS = "--novalid --maxdepth 10000 --param replace \"'false'\"".freeze
1✔
882
    MERGE_XSLT_PATH = "/usr/share/autoinstall/xslt/merge.xslt".freeze
1✔
883

884
    # Merges the given profiles
885
    #
886
    # @param base_profile [String] the base profile file path
887
    # @param with [String] the profile to be merged file path
888
    # @param to [String] the resulting control file path
889
    # @return [Hash] stdout and stderr output
890
    def merge_profiles(base_profile, with, to)
1✔
891
      dontmerge_str = ""
4✔
892
      AutoinstConfig.dontmerge.each_with_index do |dm, i|
4✔
893
        dontmerge_str << " --param dontmerge#{i + 1} \"'#{dm}'\""
1✔
894
      end
895
      merge_command =
896
        "#{MERGE_CMD} #{MERGE_DEFAULTS}" \
4✔
897
        "#{dontmerge_str} --param with \"'#{with}'\" " \
898
        "--output \"#{to}\" " \
899
        "#{MERGE_XSLT_PATH} #{base_profile}"
900

901
      out = SCR.Execute(path(".target.bash_output"), merge_command, {})
4✔
902
      log.info("Merge command: #{merge_command}")
4✔
903
      log.info("Merge stdout: #{out["stdout"]}, stderr: #{out["stderr"]}")
4✔
904
      out
4✔
905
    end
906

907
    # Merge Rule results
908
    # @param [String] result_profile the resulting control file path
909
    # @return [Boolean] true on success
910
    def Merge(result_profile)
1✔
911
      base_profile = File.join(AutoinstConfig.tmpDir, "base_profile.xml")
5✔
912
      merge_profile = File.join(AutoinstConfig.tmpDir, "result.xml")
5✔
913
      cleaned_profile = File.join(AutoinstConfig.tmpDir, "current.xml")
5✔
914
      ok = true
5✔
915
      error = false
5✔
916
      @tomerge.each_with_index do |file, iter|
5✔
917
        log.info("Working on file: #{file}")
6✔
918
        current_profile = File.join(AutoinstConfig.local_rules_location, file)
6✔
919

920
        if !Y2Autoinstallation::XmlChecks.instance.valid_profile?(current_profile)
6✔
921
          error = true
1✔
922
          next
1✔
923
        end
924

925
        dest_profile = (iter == 0) ? base_profile : cleaned_profile
5✔
926
        begin
927
          XML_cleanup(current_profile, dest_profile)
5✔
928
        rescue XMLDeserializationError => e
929
          log.error("Error reading XML file: #{e.inspect}")
×
930
          message = _(
×
931
            "The XML parser reported an error while parsing the autoyast profile. " \
932
            "The error message is:\n"
933
          )
934
          message += e.message
×
935
          Yast2::Popup.show(message, headline: :error)
×
936
          error = true
×
937
        end
938

939
        if error
5✔
940
          log.error("Error while merging control files")
×
941
        else
942
          next if iter == 0
5✔
943

944
          xsltret = merge_profiles(base_profile, cleaned_profile, merge_profile)
2✔
945

946
          log.info("Merge result: #{xsltret}")
2✔
947
          if xsltret["exit"] != 0 || xsltret.fetch("stderr", "") != ""
2✔
948
            log.error("Merge Failed")
1✔
949
            StdErrLog(xsltret.fetch("stderr", ""))
1✔
950
            ok = false
1✔
951
          end
952

953
          XML_cleanup(merge_profile, base_profile)
2✔
954
        end
955
      end
956

957
      return !error if error
5✔
958

959
      SCR.Execute(path(".target.bash"), "cp #{base_profile} #{result_profile}")
4✔
960
      Builtins.y2milestone("Ok=%1", ok)
4✔
961
      @dontmergeIsDefault = true
4✔
962
      AutoinstConfig.dontmerge = deep_copy(@dontmergeBackup)
4✔
963
      ok
4✔
964
    end
965

966
    def read_xml(profile)
1✔
967
      if !Profile.ReadXML(profile)
×
968
        Popup.Error(
×
969
          _(
970
            "Error while parsing the control file.\n" \
971
            "Check the log files for more details or fix the\n" \
972
            "control file and try again.\n"
973
          )
974
        )
975
        return false
×
976
      end
977

978
      true
×
979
    end
980

981
    # When there are classes defined in the profile it adds mergeable
982
    # configurations to the list of files to be merged.
983
    #
984
    # @return [Boolean] true when there are configurations to be merged; false
985
    #   otherwise
986
    def classes_to_merge
1✔
987
      # Now check if there are any classes defined in the pre final control file
988
      log.info("Checking classes...")
3✔
989
      return false unless (Profile.current || {}).keys.include?("classes")
3✔
990

991
      log.info("User defined classes available, processing....")
2✔
992
      classes = Profile.current["classes"]
2✔
993
      classes.each do |profile_class|
2✔
994
        # backdoor for merging problems.
995
        if profile_class.keys.include?("dont_merge")
2✔
996
          AutoinstConfig.dontmerge = [] if @dontmergeIsDefault
2✔
997
          not_mergeable = profile_class.fetch("dont_merge", [])
2✔
998
          AutoinstConfig.dontmerge = Builtins.union(AutoinstConfig.dontmerge || [], not_mergeable)
2✔
999
          @dontmergeIsDefault = false
2✔
1000
          log.info("user defined dont_merge for class found. " \
2✔
1001
                   "dontmerge is #{AutoinstConfig.dontmerge}")
1002
        end
1003
        class_name = profile_class.fetch("class_name", "none")
2✔
1004
        file_name  = profile_class.fetch("configuration", "none")
2✔
1005
        @tomerge << File.join("classes", class_name, file_name)
2✔
1006
      end
1007

1008
      log.info("New files to process: #{@tomerge.inspect}")
2✔
1009
      @Behaviour = :multiple
2✔
1010

1011
      true
2✔
1012
    end
1013

1014
    # Process Rules
1015
    # @param [String] result_profile
1016
    # @return [Boolean]
1017
    def Process(result_profile)
1✔
1018
      prefinal = File.join(AutoinstConfig.local_rules_location, "prefinal_autoinst.xml")
8✔
1019
      return false if !Merge(prefinal)
8✔
1020
      return false if !read_xml(prefinal)
8✔
1021

1022
      ok = true
8✔
1023
      @tomerge = []
8✔
1024
      if classes_to_merge
8✔
1025
        if GetRules()
7✔
1026
          @tomerge.prepend("prefinal_autoinst.xml")
2✔
1027
          ok = Merge(result_profile)
2✔
1028
        else
1029
          Report.Error(
5✔
1030
            _(
1031
              "\n" \
1032
              "User-defined classes could not be retrieved.  Make sure all classes \n" \
1033
              "are defined correctly and available for this system via the network\n" \
1034
              "or locally. The system cannot be installed with the original control \n" \
1035
              "file without using classes.\n"
1036
            )
1037
          )
1038

1039
          ok = false
5✔
1040
          SCR.Execute(path(".target.bash"), "cp #{prefinal} #{result_profile}")
5✔
1041
        end
1042
      else
1043
        SCR.Execute(path(".target.bash"), "cp #{prefinal} #{result_profile}")
1✔
1044
      end
1045

1046
      log.info("returns=#{ok}")
8✔
1047
      ok
8✔
1048
    end
1049

1050
    # Create default rule in case no rules file is available
1051
    # This adds a list of file starting from full hex ip representation to
1052
    # only the first letter. Then default and finally mac address.
1053
    # @return [void]
1054
    def CreateDefault
1✔
1055
      @Behaviour = :one
×
1056
      if @hostid
×
1057
        tmp_hex_ip = @hostid
×
1058
        @tomerge << tmp_hex_ip
×
1059
        while tmp_hex_ip.size > 1
×
1060
          tmp_hex_ip = tmp_hex_ip[0..-2]
×
1061
          @tomerge << tmp_hex_ip
×
1062
        end
1063
      end
1064
      @tomerge << Builtins.toupper(@mac)
×
1065
      @tomerge << Builtins.tolower(@mac)
×
1066
      @tomerge << "default"
×
1067
      Builtins.y2milestone("Created default rules=%1", @tomerge)
×
1068
      nil
1069
    end
1070

1071
    # Create default rule in case no rules file is available
1072
    # (Only one file which is given by the user)
1073
    # @param [String] filename file name
1074
    # @return [void]
1075
    def CreateFile(filename)
1✔
1076
      @tomerge = Builtins.add(@tomerge, filename)
6✔
1077
      Builtins.y2milestone("Created default rules: %1", @tomerge)
6✔
1078
      nil
1079
    end
1080

1081
    # Constructor
1082
    #
1083
    def AutoInstallRules
1✔
1084
      @mac = getMAC
12✔
1085
      @hostid = getHostid
12✔
1086
      @efi = boot_efi?
12✔
1087
      log.info "init mac:#{@mac} hostid: #{@hostid} efi: #{@efi}"
12✔
1088
      nil
1089
    end
1090

1091
    # @return [String] "yes" when the system is booted using EFI or "no" when not
1092
    def boot_efi?
1✔
1093
      Y2Storage::Arch.new.efiboot? ? "yes" : "no"
13✔
1094
    end
1095

1096
    # Regexp to extract the IP from the routes table
1097
    HOSTADDRESS_REGEXP = /src ([\w.]+) /.freeze
1✔
1098

1099
    # Return the IP through iproute2 tools
1100
    #
1101
    # @return [String] IP address
1102
    def hostaddress
1✔
1103
      return @hostaddress unless @hostaddress.nil?
20✔
1104

1105
      ip_route = SCR.Execute(path(".target.bash_output"), "/usr/sbin/ip route")
20✔
1106
      ret = ip_route["stdout"][HOSTADDRESS_REGEXP, 1]
20✔
1107
      if ret
20✔
1108
        log.info "Found IP address: #{ret}"
19✔
1109
      else
1110
        log.warn "Cannot evaluate IP address: #{ip_route}"
1✔
1111
      end
1112

1113
      ret
20✔
1114
    end
1115

1116
    publish variable: :userrules, type: "boolean"
1✔
1117
    publish variable: :dontmergeIsDefault, type: "boolean"
1✔
1118
    publish variable: :dontmergeBackup, type: "list <string>"
1✔
1119
    publish variable: :Behaviour, type: "symbol"
1✔
1120
    publish variable: :installed_product, type: "string"
1✔
1121
    publish variable: :installed_product_version, type: "string"
1✔
1122
    publish variable: :hostname, type: "string"
1✔
1123
    publish variable: :network, type: "string"
1✔
1124
    publish variable: :domain, type: "string"
1✔
1125
    publish variable: :arch, type: "string"
1✔
1126
    publish variable: :karch, type: "string"
1✔
1127
    publish variable: :product, type: "string"
1✔
1128
    publish variable: :product_vendor, type: "string"
1✔
1129
    publish variable: :board_vendor, type: "string"
1✔
1130
    publish variable: :board, type: "string"
1✔
1131
    publish variable: :memsize, type: "integer"
1✔
1132
    publish variable: :disksize, type: "list <map <string, any>>"
1✔
1133
    publish variable: :totaldisk, type: "integer"
1✔
1134
    publish variable: :hostid, type: "string"
1✔
1135
    publish variable: :mac, type: "string"
1✔
1136
    publish variable: :linux, type: "integer"
1✔
1137
    publish variable: :others, type: "integer"
1✔
1138
    publish variable: :efi, type: "string"
1✔
1139
    publish variable: :xserver, type: "string"
1✔
1140
    publish variable: :NonLinuxPartitions, type: "list"
1✔
1141
    publish variable: :LinuxPartitions, type: "list"
1✔
1142
    publish variable: :UserRules, type: "map <string, any>"
1✔
1143
    publish variable: :tomerge, type: "list <string>"
1✔
1144
    publish function: :hostaddress, type: "string ()"
1✔
1145
    publish function: :XML_cleanup, type: "boolean (string, string)"
1✔
1146
    publish function: :StdErrLog, type: "void (string)"
1✔
1147
    publish function: :getMAC, type: "string ()"
1✔
1148
    publish function: :getHostid, type: "string ()"
1✔
1149
    publish function: :getHostname, type: "string ()"
1✔
1150
    publish function: :ProbeRules, type: "void ()"
1✔
1151
    publish function: :Read, type: "void ()"
1✔
1152
    publish function: :Files, type: "list <string> ()"
1✔
1153
    publish function: :GetRules, type: "boolean ()"
1✔
1154
    publish function: :Merge, type: "boolean (string)"
1✔
1155
    publish function: :Process, type: "boolean (string)"
1✔
1156
    publish function: :CreateDefault, type: "void ()"
1✔
1157
    publish function: :CreateFile, type: "void (string)"
1✔
1158
    publish function: :AutoInstallRules, type: "void ()"
1✔
1159
  end
1160

1161
  AutoInstallRules = AutoInstallRulesClass.new
1✔
1162
  AutoInstallRules.main
1✔
1163
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

© 2026 Coveralls, Inc