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

yast / yast-autoinstallation / 10734206680

06 Sep 2024 07:20AM UTC coverage: 68.702% (-0.01%) from 68.713%
10734206680

push

github

lslezak
Adapt files for the SLE-15-SP7 branch

6401 of 9317 relevant lines covered (68.7%)

9.8 hits per line

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

71.69
/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: .*::"'
×
151
        ret = SCR.Execute(path(".target.bash_output"), cmd)
×
152
        Builtins.y2milestone("mac Addr ret:%1", ret)
×
153
        tmpmac = ret.fetch("stdout", "")
×
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✔
228
        if Ops.get_string(inf, "type", "") == "sysinfo"
×
229
          sysinfo = deep_copy(inf)
×
230
        elsif Ops.get_string(inf, "type", "") == "boardinfo"
×
231
          boardinfo = deep_copy(inf)
×
232
        end
233
      end
234

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

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

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

250
      #
251
      # Architecture
252
      #
253

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

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

260
      #
261
      # Memory
262
      #
263

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

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

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

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

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

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

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

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

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

331
      nil
332
    end
333

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

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

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

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

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

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

409
      Ops.get_integer(ret, "exit", -1)
3✔
410
    end
411

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

825
      scheme = AutoinstConfig.scheme
6✔
826
      host = AutoinstConfig.host
6✔
827
      directory = AutoinstConfig.directory
6✔
828

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

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

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

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

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

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

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

917
        if !Y2Autoinstallation::XmlChecks.instance.valid_profile?(current_profile)
6✔
918
          error = true
1✔
919
          next
1✔
920
        end
921

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

936
        if error
5✔
937
          log.error("Error while merging control files")
×
938
        else
939
          next if iter == 0
5✔
940

941
          xsltret = merge_profiles(base_profile, cleaned_profile, merge_profile)
2✔
942

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

950
          XML_cleanup(merge_profile, base_profile)
2✔
951
        end
952
      end
953

954
      return !error if error
5✔
955

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

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

975
      true
×
976
    end
977

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

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

1005
      log.info("New files to process: #{@tomerge.inspect}")
2✔
1006
      @Behaviour = :multiple
2✔
1007

1008
      true
2✔
1009
    end
1010

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

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

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

1043
      log.info("returns=#{ok}")
8✔
1044
      ok
8✔
1045
    end
1046

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

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

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

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

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

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

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

1110
      ret
20✔
1111
    end
1112

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

1158
  AutoInstallRules = AutoInstallRulesClass.new
1✔
1159
  AutoInstallRules.main
1✔
1160
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