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

yast / yast-yast2 / 13440235285

20 Feb 2025 04:40PM UTC coverage: 41.869% (-0.02%) from 41.889%
13440235285

push

github

web-flow
Merge pull request #1316 from yast/agama_kernel_conf

Respect Agama kernel parameters

2 of 4 new or added lines in 1 file covered. (50.0%)

265 existing lines in 40 files now uncovered.

12605 of 30106 relevant lines covered (41.87%)

10.76 hits per line

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

27.25
/library/control/src/modules/ProductControl.rb
1
# ***************************************************************************
2
#
3
# Copyright (c) 2002 - 2012 Novell, Inc.
4
# All Rights Reserved.
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of version 2 of the GNU General Public License as
8
# published by the Free Software Foundation.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail,
19
# you may find current contact information at www.novell.com
20
#
21
# ***************************************************************************
22
# File:  modules/ProductControl.rb
23
# Package:  installation
24
# Summary:  Product Control routines
25
# Authors:  Anas Nashif <nashif@suse.de>
26
#    Stanislav Visnovsky <visnov@suse.cz>
27
#    Jiri Srain <jsrain@suse.cz>
28
#    Lukas Ocilka <locilka@suse.cz>
29
#
30
require "yast"
1✔
31
require "yast2/control_log_dir_rotator"
1✔
32
require "yast2/popup"
1✔
33

34
module Yast
1✔
35
  class ProductControlClass < Module
1✔
36
    include Yast::Logger
1✔
37

38
    # Product control system roles key
39
    # @return [String] System roles
40
    SYSTEM_ROLES_KEY = "system_roles".freeze
1✔
41

42
    def main
1✔
43
      Yast.import "UI"
1✔
44
      textdomain "base"
1✔
45

46
      Yast.import "XML"
1✔
47
      Yast.import "ProductFeatures"
1✔
48
      Yast.import "Mode"
1✔
49
      Yast.import "Arch"
1✔
50
      Yast.import "Stage"
1✔
51
      Yast.import "Directory"
1✔
52
      Yast.import "Label"
1✔
53
      Yast.import "Wizard"
1✔
54
      Yast.import "Report"
1✔
55
      Yast.import "Popup"
1✔
56
      Yast.import "FileUtils"
1✔
57
      Yast.import "Installation"
1✔
58
      Yast.import "Hooks"
1✔
59

60
      # The complete parsed control file
61
      @productControl = {}
1✔
62

63
      # all workflows
64
      @workflows = []
1✔
65

66
      # all proposals
67
      @proposals = []
1✔
68

69
      # inst_finish steps
70
      @inst_finish = []
1✔
71

72
      # modules to be offered to clone configuration at the end of installation
73
      @clone_modules = []
1✔
74

75
      # roles
76
      @system_roles = []
1✔
77

78
      # additional workflow parameters
79
      # workflow doesn't only match mode and stage but also these params
80
      # bnc #427002
81
      @_additional_workflow_params = {}
1✔
82

83
      # Location of a custom control file
84
      @custom_control_file = ""
1✔
85

86
      # Control file in service packs
87
      @y2update_control_file = "/y2update/control.xml"
1✔
88

89
      # The custom control file location, usually copied from
90
      # the root of the CD to the installation directory by linuxrc
91
      @installation_control_file = "/control.xml"
1✔
92

93
      # The file above get saved into the installed system for later
94
      # processing
95
      @saved_control_file = Ops.add(Directory.etcdir, "/control.xml")
1✔
96

97
      # The control file we are using for this session.
98
      @current_control_file = nil
1✔
99

100
      # Current Wizard Step
101
      @CurrentWizardStep = ""
1✔
102

103
      # Last recently used stage_mode for RetranslateWizardSteps
104
      @last_stage_mode = []
1✔
105

106
      # List of module to disable in the current run
107
      @DisabledModules = []
1✔
108

109
      # List of proposals to disable in the current run
110
      @DisabledProposals = []
1✔
111

112
      @DisabledSubProposals = {}
1✔
113

114
      # Log files for hooks
115
      @logfiles = []
1✔
116

117
      @first_step = nil
1✔
118

119
      @restarting_step = nil
1✔
120

121
      @_client_prefix = "inst_"
1✔
122

123
      @first_id = ""
1✔
124

125
      @current_step = 0
1✔
126

127
      @localDisabledProposals = []
1✔
128

129
      @localDisabledModules = []
1✔
130

131
      @already_disabled_workflows = []
1✔
132

133
      # Forces UpdateWizardSteps to redraw steps even if nothing seem to be changed
134
      @force_UpdateWizardSteps = false
1✔
135

136
      @lastDisabledModules = deep_copy(@DisabledModules)
1✔
137

138
      ProductControl()
1✔
139
    end
140

141
    def CurrentStep
1✔
142
      @current_step
×
143
    end
144

145
    # Set Client Prefix
146
    def setClientPrefix(prefix)
1✔
147
      @_client_prefix = prefix
×
UNCOV
148
      nil
×
149
    end
150

151
    # Enable given disabled module
152
    # @return current list of disabled modules
153
    def EnableModule(modname)
1✔
154
      @DisabledModules = Builtins.filter(@DisabledModules) do |mod|
×
155
        mod != modname
×
156
      end
157

158
      deep_copy(@DisabledModules)
×
159
    end
160

161
    # Disable given module in installation workflow
162
    # @return current list of disabled modules
163
    def DisableModule(modname)
1✔
164
      if modname.nil? || modname == ""
×
165
        Builtins.y2error("Module to disable is '%1'", modname)
×
166
      else
167
        @DisabledModules = Convert.convert(
×
168
          Builtins.union(@DisabledModules, [modname]),
169
          from: "list",
170
          to:   "list <string>"
171
        )
172
      end
173

174
      deep_copy(@DisabledModules)
×
175
    end
176

177
    # Returns list of modules disabled in workflow
178
    #
179
    # @return [Array<String>] DisabledModules
180
    def GetDisabledModules
1✔
181
      deep_copy(@DisabledModules)
×
182
    end
183

184
    # Enable given disabled proposal
185
    # @return current list of disabled proposals
186
    def EnableProposal(enable_proposal)
1✔
187
      @DisabledProposals = Builtins.filter(@DisabledProposals) do |one_proposal|
×
188
        one_proposal != enable_proposal
×
189
      end
190

191
      deep_copy(@DisabledProposals)
×
192
    end
193

194
    # Disable given proposal in installation workflow
195
    # @return current list of disabled proposals
196
    def DisableProposal(disable_proposal)
1✔
197
      if disable_proposal.nil? || disable_proposal == ""
×
198
        Builtins.y2error("Module to disable is '%1'", disable_proposal)
×
199
      else
200
        @DisabledProposals = Convert.convert(
×
201
          Builtins.union(@DisabledProposals, [disable_proposal]),
202
          from: "list",
203
          to:   "list <string>"
204
        )
205
      end
206

207
      deep_copy(@DisabledProposals)
×
208
    end
209

210
    # Returns list of proposals disabled in workflow
211
    #
212
    # @return [Array<String>] DisabledProposals
213
    def GetDisabledProposals
1✔
214
      deep_copy(@DisabledProposals)
×
215
    end
216

217
    def EnableSubProposal(unique_id, enable_subproposal)
1✔
218
      if Builtins.haskey(@DisabledSubProposals, unique_id)
×
219
        Ops.set(
×
220
          @DisabledSubProposals,
221
          unique_id,
222
          Builtins.filter(Ops.get(@DisabledSubProposals, unique_id, [])) do |one_subproposal|
223
            one_subproposal != enable_subproposal
×
224
          end
225
        )
226
      end
227
      deep_copy(@DisabledSubProposals)
×
228
    end
229

230
    def DisableSubProposal(unique_id, disable_subproposal)
1✔
231
      if Builtins.haskey(@DisabledSubProposals, unique_id)
×
232
        Ops.set(
×
233
          @DisabledSubProposals,
234
          unique_id,
235
          Convert.convert(
236
            Builtins.union(
237
              Ops.get(@DisabledSubProposals, unique_id, []),
238
              [disable_subproposal]
239
            ),
240
            from: "list",
241
            to:   "list <string>"
242
          )
243
        )
244
      else
245
        Ops.set(@DisabledSubProposals, unique_id, [disable_subproposal])
×
246
      end
247

248
      deep_copy(@DisabledSubProposals)
×
249
    end
250

251
    def GetDisabledSubProposals
1✔
252
      deep_copy(@DisabledSubProposals)
×
253
    end
254

255
    # Check if a module is disabled
256
    # @param map module map
257
    # @return [Boolean]
258
    def checkDisabled(mod)
1✔
259
      mod = deep_copy(mod)
×
260
      if mod.nil?
×
261
        Builtins.y2error("Unknown module %1", mod)
×
262
        return nil
×
263
      end
264

265
      # Proposal
266
      if !Ops.get_string(mod, "proposal", "").nil? &&
×
267
          Ops.get_string(mod, "proposal", "") != ""
268
        if Builtins.contains(
×
269
          @DisabledProposals,
270
          Ops.get_string(mod, "proposal", "")
271
        )
272
          return true
×
273
        end
274
        # Normal step
275
      elsif !Ops.get_string(mod, "name", "").nil? &&
×
276
          Ops.get_string(mod, "name", "") != ""
277
        return true if Builtins.contains(@DisabledModules, Ops.get_string(mod, "name", ""))
×
278
      end
279

280
      false
×
281
    end
282

283
    def checkHeading(mod)
1✔
284
      mod = deep_copy(mod)
×
285
      Builtins.haskey(mod, "heading")
×
286
    end
287

288
    # Read XML Control File
289
    # @param string control file
290
    # @return [Boolean]
291
    def ReadControlFile(controlfile)
1✔
292
      begin
293
        @productControl = XML.XMLToYCPFile(controlfile)
1✔
294
      rescue RuntimeError => e
295
        log.error "Failed to read control file: #{e.inspect}"
×
296
        return false
×
297
      end
298

299
      # log the read control.xml
300

301
      control_log_dir_rotator = Yast2::ControlLogDirRotator.new
1✔
302
      control_log_dir_rotator.prepare
1✔
303
      control_log_dir_rotator.copy(controlfile, "control.xml")
1✔
304

305
      control_log_dir_rotator.write("README", "The first number in the filename is the merge "\
1✔
306
                                              "counter, the second number\nis the add-on counter.\n")
307

308
      @workflows = Ops.get_list(@productControl, "workflows", [])
1✔
309
      @proposals = Ops.get_list(@productControl, "proposals", [])
1✔
310
      @inst_finish = Ops.get_list(@productControl, "inst_finish_stages", [])
1✔
311
      @clone_modules = Ops.get_list(@productControl, "clone_modules", [])
1✔
312
      @system_roles = @productControl.fetch(SYSTEM_ROLES_KEY, [])
1✔
313

314
      Builtins.foreach(
1✔
315
        ["software", "globals", "network", "partitioning", "texts", "configuration_management"]
316
      ) do |section|
317
        if Builtins.haskey(@productControl, section)
6✔
318
          ProductFeatures.SetSection(
5✔
319
            section,
320
            Ops.get_map(@productControl, section, {})
321
          )
322
        end
323
      end
324

325
      # FIXME: would be nice if it could be done generic way
326
      if Ops.greater_than(
1✔
327
        Builtins.size(
328
          Ops.get_list(@productControl, ["partitioning", "partitions"], [])
329
        ),
330
        0
331
      )
332
        partitioning = Ops.get_map(@productControl, "partitioning", {})
×
333
        ProductFeatures.SetBooleanFeature(
×
334
          "partitioning",
335
          "flexible_partitioning",
336
          true
337
        )
338
        ProductFeatures.SetFeature(
×
339
          "partitioning",
340
          "FlexiblePartitioning",
341
          partitioning
342
        )
343
      end
344

345
      true
1✔
346
    end
347

348
    def Check(allowed, current)
1✔
349
      # create allowed list
350
      allowedlist = Builtins.filter(
×
351
        Builtins.splitstring(Builtins.deletechars(allowed, " "), ",")
352
      ) { |s| s != "" }
×
353
      Builtins.y2debug("allowedlist: %1", allowedlist)
×
354
      Builtins.y2debug("current: %1", current)
×
355

356
      Builtins.size(allowedlist) == 0 || Builtins.contains(allowedlist, current)
×
357
    end
358

359
    # Check if valid architecture
360
    # @param map module data
361
    # @param map default data
362
    # @return [Boolean] true if arch match
363
    def checkArch(mod, def_)
1✔
364
      mod = deep_copy(mod)
×
365
      def_ = deep_copy(def_)
×
366
      Builtins.y2debug("Checking architecture: %1", mod)
×
367
      archs = Ops.get_string(mod, "archs", "")
×
368
      archs = Ops.get_string(def_, "archs", "all") if archs == ""
×
369

370
      return true if archs == "all"
×
371

372
      Builtins.y2milestone("short arch desc: %1", Arch.arch_short)
×
373
      Builtins.y2milestone("supported archs: %1", archs)
×
374
      return true if Builtins.issubstring(archs, Arch.arch_short)
×
375

376
      false
×
377
    end
378

379
    # Returns name of the script to call. If 'execute' is defined,
380
    # the client name is taken from there. Then, if a custom control
381
    # file is defined, client name is defined as 'name'. Then, inst_'name'
382
    # or just 'name' is returned if it does not match the 'inst_' regexp.
383
    #
384
    # @param [String] name
385
    # @param [String] execute
386
    # @see #custom_control_file
387
    def getClientName(name, execute)
1✔
388
      return "inst_test_workflow" if Mode.test
×
389

390
      # BNC #401319
391
      # 'execute; is defined and thus returned
392
      if !execute.nil? && execute != ""
×
393
        Builtins.y2milestone("Step name '%1' executes '%2'", name, execute)
×
394
        return execute
×
395
      end
396

397
      # Defined custom control file
398
      return name if @custom_control_file != ""
×
399

400
      # All standard clients start with "inst_"
401
      Builtins.issubstring(name, @_client_prefix) ? name : Ops.add(@_client_prefix, name)
×
402
    end
403

404
    # Return term to be used to run module with CallFunction
405
    # @param map module data
406
    # @param map default data
407
    # @return [Yast::Term] module data with params
408
    def getClientTerm(step, def_, former_result)
1✔
409
      step = deep_copy(step)
×
410
      def_ = deep_copy(def_)
×
411
      former_result = deep_copy(former_result)
×
412
      client = getClientName(
×
413
        Ops.get_string(step, "name", "dummy"),
414
        Ops.get_string(step, "execute", "")
415
      )
416
      result = Builtins.toterm(client)
×
417
      arguments = {}
×
418

419
      Builtins.foreach(["enable_back", "enable_next"]) do |button|
×
420
        default_setting = Ops.get_string(def_, button, "yes")
×
421
        Ops.set(
×
422
          arguments,
423
          button,
424
          Ops.get_string(step, button, default_setting) == "yes"
425
        )
426
      end
427

428
      Ops.set(arguments, "proposal", Ops.get_string(step, "proposal", "")) if Builtins.haskey(step, "proposal")
×
429
      other_args = Ops.get_map(step, "arguments", {})
×
430

431
      if Ops.greater_than(Builtins.size(other_args), 0)
×
432
        arguments = Convert.convert(
×
433
          Builtins.union(arguments, other_args),
434
          from: "map",
435
          to:   "map <string, any>"
436
        )
437
      end
438

439
      Ops.set(arguments, "going_back", true) if Ops.is_symbol?(former_result) && former_result == :back
×
440

441
      if Mode.test
×
442
        Ops.set(arguments, "step_name", Ops.get_string(step, "name", ""))
×
443
        Ops.set(arguments, "step_id", Ops.get_string(step, "id", ""))
×
444
      end
445
      result = Builtins.add(result, arguments)
×
446
      deep_copy(result)
×
447
    end
448

449
    # Checks all params set by SetAdditionalWorkflowParams() whether they match the
450
    # workfow got as parameter.
451
    #
452
    # @param [map &] check_workflow
453
    # @see #SetAdditionalWorkflowParams()
454
    def CheckAdditionalParams(check_workflow)
1✔
455
      if @_additional_workflow_params.nil? ||
×
456
          @_additional_workflow_params == {}
457
        return true
×
458
      end
459

460
      ret = true
×
461

462
      Builtins.foreach(@_additional_workflow_params) do |key_to_check, value_to_check|
×
463
        # exception
464
        # If 'add_on_mode' key is not set in the workflow at all
465
        # it is considered to be matching that parameter
466
        if key_to_check == "add_on_mode" &&
×
467
            !Builtins.haskey(check_workflow.value, key_to_check)
468
          Builtins.y2debug(
×
469
            "No 'add_on_mode' defined, matching %1",
470
            value_to_check
471
          )
472
        elsif Ops.get(check_workflow.value, key_to_check) != value_to_check
×
473
          ret = false
×
474
          raise Break
×
475
        end
476
      end
477

478
      ret
×
479
    end
480

481
    # Returns workflow matching the selected stage and mode and additiona parameters
482
    # if set by SetAdditionalWorkflowParams()
483
    #
484
    # @param [String] stage
485
    # @param [String] mode
486
    # @return [Hash] workflow
487
    def FindMatchingWorkflow(stage, mode)
1✔
488
      Builtins.y2debug("workflows: %1", @workflows)
×
489

490
      tmp = Builtins.filter(@workflows) do |wf|
×
491
        Check(Ops.get_string(wf, "stage", ""), stage) &&
×
492
          Check(Ops.get_string(wf, "mode", ""), mode) &&
493
          (
494
            wf_ref = arg_ref(wf)
×
495
            CheckAdditionalParams(wf_ref)
×
496
          )
497
      end
498

499
      Builtins.y2debug("Workflow: %1", Ops.get(tmp, 0, {}))
×
500

501
      Ops.get(tmp, 0, {})
×
502
    end
503

504
    # Get workflow defaults
505
    # @param [String] stage
506
    # @param [String] mode
507
    # @return [Hash] defaults
508
    def getModeDefaults(stage, mode)
1✔
509
      workflow = FindMatchingWorkflow(stage, mode)
×
510
      Ops.get_map(workflow, "defaults", {})
×
511
    end
512

513
    # Prepare Workflow Scripts
514
    # @param [Hash] workflow Workflow module map
515
    # @return [void]
516
    def PrepareScripts(workflow)
1✔
517
      workflow = deep_copy(workflow)
×
518
      tmp_dir = Convert.to_string(WFM.Read(path(".local.tmpdir"), []))
×
519
      if Builtins.haskey(workflow, "prescript")
×
520
        interpreter = Ops.get_string(workflow, ["prescript", "interpreter"], "shell")
×
521
        source = Ops.get_string(workflow, ["prescript", "source"], "")
×
522
        type = (interpreter == "shell") ? "sh" : "pl"
×
523
        f = Builtins.sformat(
×
524
          "%1/%2_pre.%3",
525
          tmp_dir,
526
          Ops.get_string(workflow, "name", ""),
527
          type
528
        )
529
        WFM.Write(path(".local.string"), f, source)
×
530
        @logfiles = Builtins.add(
×
531
          @logfiles,
532
          Builtins.sformat(
533
            "%1.log",
534
            Builtins.sformat("%1_pre.%2", Ops.get_string(workflow, "name", ""), type)
535
          )
536
        )
537
      end
538
      if Builtins.haskey(workflow, "postscript")
×
539
        interpreter = Ops.get_string(workflow, ["postscript", "interpreter"], "shell")
×
540
        source = Ops.get_string(workflow, ["postscript", "source"], "")
×
541
        type = (interpreter == "shell") ? "sh" : "pl"
×
542
        f = Builtins.sformat(
×
543
          "%1/%2_post.%3",
544
          tmp_dir,
545
          Ops.get_string(workflow, "name", ""),
546
          type
547
        )
548
        WFM.Write(path(".local.string"), f, source)
×
549
        @logfiles = Builtins.add(
×
550
          @logfiles,
551
          Builtins.sformat(
552
            "%1.log",
553
            Builtins.sformat("%1_post.%2", Ops.get_string(workflow, "name", ""), type)
554
          )
555
        )
556
      end
UNCOV
557
      nil
×
558
    end
559

560
    # Get Workflow
561
    # @param [String] stage Stage
562
    # @param [String] mode Mode
563
    # @return [Hash] Workflow map
564
    def getCompleteWorkflow(stage, mode)
1✔
565
      FindMatchingWorkflow(stage, mode)
×
566
    end
567

568
    # Get modules of current Workflow
569
    # @param [String] stage
570
    # @param [String] mode
571
    # @param boolean all enabled and disabled or enabled only
572
    # @return [Array<Hash>] modules
573
    def getModules(stage, mode, which)
1✔
574
      workflow = FindMatchingWorkflow(stage, mode)
×
575

576
      modules = Ops.get_list(workflow, "modules", [])
×
577
      Builtins.y2debug("M1: %1", modules)
×
578

579
      # Unique IDs have to always keep the same because some steps
580
      # can be disabled while YaST is running
581
      # @see BNC #575092
582
      id = 1
×
583
      modules = Builtins.maplist(modules) do |m|
×
584
        Ops.set(m, "id", Builtins.sformat("%1_%2", stage, id))
×
585
        id = Ops.add(id, 1)
×
586
        deep_copy(m)
×
587
      end
588

589
      if which == :enabled
×
590
        modules = Builtins.filter(modules) do |m|
×
591
          Ops.get_boolean(m, "enabled", true) && !checkDisabled(m)
×
592
        end
593
      end
594

595
      Builtins.y2debug("M2: %1", modules)
×
596

597
      modules = Builtins.maplist(modules) do |m|
×
598
        PrepareScripts(m)
×
599
        deep_copy(m)
×
600
      end
601

602
      Builtins.y2debug("M3: %1", modules)
×
603
      Builtins.y2debug("Log files: %1", @logfiles)
×
604
      deep_copy(modules)
×
605
    end
606

607
    # Returns whether is is required to run YaST in the defined
608
    # stage and mode
609
    #
610
    # @param [String] stage
611
    # @param [String] mode
612
    # @return [Boolean] if needed
613
    def RunRequired(stage, mode)
1✔
614
      modules = getModules(stage, mode, :enabled)
×
615

616
      if modules.nil?
×
617
        Builtins.y2error("Undefined %1/%2", stage, mode)
×
618
        return nil
×
619
      end
620

621
      modules = Builtins.filter(modules) do |one_module|
×
622
        name = one_module["name"]
×
623
        proposal = one_module["proposal"]
×
624

625
        next true if name && !name.empty?
×
626
        next true if proposal && !proposal.empty?
×
627

628
        # the rest
629
        false
×
630
      end
631

632
      # for debugging purposes
633
      Builtins.y2milestone("Enabled: (%1) %2", Builtins.size(modules), modules)
×
634

635
      Ops.greater_than(Builtins.size(modules), 0)
×
636
    end
637

638
    # Get Workflow Label
639
    # @param [String] stage
640
    # @param [String] mode
641
    # @return [String]
642
    def getWorkflowLabel(stage, mode, wz_td)
1✔
643
      workflow = FindMatchingWorkflow(stage, mode)
×
644

645
      label = Ops.get_string(workflow, "label", "")
×
646
      return "" if label == ""
×
647

648
      if Builtins.haskey(workflow, "textdomain")
×
649
        return Builtins.dgettext(
×
650
          Ops.get_string(workflow, "textdomain", ""),
651
          label
652
        )
653
      end
654

655
      Builtins.dgettext(wz_td, label)
×
656
    end
657

658
    def DisableAllModulesAndProposals(mode, stage)
1✔
659
      this_workflow = { "mode" => mode, "stage" => stage }
×
660

661
      if Builtins.contains(@already_disabled_workflows, this_workflow)
×
662
        Builtins.y2milestone("Workflow %1 already disabled", this_workflow)
×
663
        return
×
664
      end
665

666
      # stores modules and proposals disabled before
667
      # this 'general' disabling
668
      @localDisabledProposals = deep_copy(@DisabledProposals)
×
669
      @localDisabledModules = deep_copy(@DisabledModules)
×
670

671
      Builtins.y2milestone(
×
672
        "localDisabledProposals: %1",
673
        @localDisabledProposals
674
      )
675
      Builtins.y2milestone("localDisabledModules: %1", @localDisabledModules)
×
676

677
      Builtins.foreach(getModules(stage, mode, :all)) do |m|
×
678
        if !Ops.get_string(m, "proposal").nil? &&
×
679
            Ops.get_string(m, "proposal", "") != ""
680
          Builtins.y2milestone("Disabling proposal: %1", m)
×
681
          @DisabledProposals = Convert.convert(
×
682
            Builtins.union(
683
              @DisabledProposals,
684
              [Ops.get_string(m, "proposal", "")]
685
            ),
686
            from: "list",
687
            to:   "list <string>"
688
          )
689
        elsif !Ops.get_string(m, "name").nil? &&
×
690
            Ops.get_string(m, "name", "") != ""
691
          Builtins.y2milestone("Disabling module: %1", m)
×
692
          @DisabledModules = Convert.convert(
×
693
            Builtins.union(@DisabledModules, [Ops.get_string(m, "name", "")]),
694
            from: "list",
695
            to:   "list <string>"
696
          )
697
        end
698
      end
699

700
      @already_disabled_workflows = Convert.convert(
×
701
        Builtins.union(@already_disabled_workflows, [this_workflow]),
702
        from: "list",
703
        to:   "list <map>"
704
      )
705

UNCOV
706
      nil
×
707
    end
708

709
    def UnDisableAllModulesAndProposals(mode, stage)
1✔
710
      this_workflow = { "mode" => mode, "stage" => stage }
×
711

712
      # Such mode/stage not disabled
713
      if !Builtins.contains(@already_disabled_workflows, this_workflow)
×
714
        Builtins.y2milestone(
×
715
          "Not yet disabled, not un-disabling: %1",
716
          this_workflow
717
        )
718
        return
×
719
      end
720

721
      Builtins.y2milestone("Un-Disabling workflow %1", this_workflow)
×
722
      @already_disabled_workflows = Builtins.filter(@already_disabled_workflows) do |one_workflow|
×
723
        one_workflow != this_workflow
×
724
      end
725

726
      # NOTE: This might be done by a simple reverting with 'X = localX'
727
      #       but some of these modules don't need to be in a defined mode and stage
728

729
      Builtins.foreach(getModules(stage, mode, :all)) do |m|
×
730
        # A proposal
731
        # Enable it only if it was enabled before
732
        if !Ops.get_string(m, "proposal").nil? &&
×
733
            Ops.get_string(m, "proposal", "") != "" &&
734
            !Builtins.contains(
735
              @localDisabledProposals,
736
              Ops.get_string(m, "proposal", "")
737
            )
738
          Builtins.y2milestone("Enabling proposal: %1", m)
×
739
          @DisabledProposals = Builtins.filter(@DisabledProposals) do |one_proposal|
×
740
            Ops.get_string(m, "proposal", "") != one_proposal
×
741
          end
742
          # A module
743
          # Enable it only if it was enabled before
744
        elsif !Ops.get_string(m, "name").nil? &&
×
745
            Ops.get_string(m, "name", "") != "" &&
746
            !Builtins.contains(
747
              @localDisabledModules,
748
              Ops.get_string(m, "name", "")
749
            )
750
          Builtins.y2milestone("Enabling module: %1", m)
×
751
          @DisabledModules = Builtins.filter(@DisabledModules) do |one_module|
×
752
            Ops.get_string(m, "name", "") != one_module
×
753
          end
754
        end
755
      end
756

UNCOV
757
      nil
×
758
    end
759

760
    # Add Wizard Steps
761
    # @param [Array<Hash>] stagemode A List of maps containing info about complete
762
    #                  installation workflow.
763
    # @return [void]
764
    def AddWizardSteps(stagemode)
1✔
765
      stagemode = deep_copy(stagemode)
×
766
      debug_workflow = ProductFeatures.GetBooleanFeature(
×
767
        "globals",
768
        "debug_workflow"
769
      )
770

771
      @last_stage_mode = deep_copy(stagemode)
×
772

773
      # UI::WizardCommand() can safely be called even if the respective UI
774
      # doesn't support the wizard widget, but for optimization it makes sense
775
      # to do expensive operations only when it is available.
776

777
      # if ( ! UI::HasSpecialWidget(`Wizard ) )
778
      # return;
779

780
      wizard_textdomain = Ops.get_string(
×
781
        @productControl,
782
        "textdomain",
783
        "control"
784
      )
785
      Builtins.y2debug(
×
786
        "Using textdomain '%1' for wizard steps",
787
        wizard_textdomain
788
      )
789

790
      first_id = ""
×
791
      # UI::WizardCommand(`SetVerboseCommands( true ) );
792
      Builtins.foreach(stagemode) do |sm|
×
793
        Builtins.y2debug("Adding wizard steps for %1", sm)
×
794
        # only for debugging
795
        Builtins.y2milestone("Adding wizard steps for %1", sm)
×
796
        slabel = getWorkflowLabel(
×
797
          Ops.get_string(sm, "stage", ""),
798
          Ops.get_string(sm, "mode", ""),
799
          wizard_textdomain
800
        )
801
        UI.WizardCommand(term(:AddStepHeading, slabel)) if slabel != ""
×
802
        # just to check whether there are some steps to display
803
        enabled_modules = getModules(
×
804
          Ops.get_string(sm, "stage", ""),
805
          Ops.get_string(sm, "mode", ""),
806
          :enabled
807
        )
808
        enabled_modules = Builtins.filter(enabled_modules) do |m|
×
809
          Ops.get_string(m, "heading", "") == ""
×
810
        end
811
        if Builtins.size(enabled_modules) == 0
×
812
          Builtins.y2milestone(
×
813
            "There are no (more) steps for %1, section will be disabled",
814
            sm
815
          )
816
          next
×
817
        end
818
        last_label = ""
×
819
        last_domain = ""
×
820
        Builtins.foreach(
×
821
          getModules(
822
            Ops.get_string(sm, "stage", ""),
823
            Ops.get_string(sm, "mode", ""),
824
            :enabled
825
          )
826
        ) do |m|
827
          # only for debugging
828
          Builtins.y2debug("Adding wizard step: %1", m)
×
829
          heading = ""
×
830
          label = ""
×
831
          id = ""
×
832
          # Heading
833
          if Builtins.haskey(m, "heading") &&
×
834
              Ops.get_string(m, "label", "") != ""
835
            heading = if Builtins.haskey(m, "textdomain")
×
836
              Builtins.dgettext(
×
837
                Ops.get_string(m, "textdomain", ""),
838
                Ops.get_string(m, "label", "")
839
              )
840
            else
841
              Builtins.dgettext(
×
842
                wizard_textdomain,
843
                Ops.get_string(m, "label", "")
844
              )
845
            end
846

847
          # Label
848
          elsif Ops.get_string(m, "label", "") != ""
×
849
            first_id = Ops.get_string(m, "id", "") if first_id == ""
×
850

851
            label = if Builtins.haskey(m, "textdomain")
×
852
              Builtins.dgettext(
×
853
                Ops.get_string(m, "textdomain", ""),
854
                Ops.get_string(m, "label", "")
855
              )
856
            else
857
              Builtins.dgettext(
×
858
                wizard_textdomain,
859
                Ops.get_string(m, "label", "")
860
              )
861
            end
862

863
            id = Ops.get_string(m, "id", "")
×
864
            last_label = Ops.get_string(m, "label", "")
×
865
            last_domain = Ops.get_string(m, "textdomain", "")
×
866

867
            # The rest
868
          else
869
            first_id = Ops.get_string(m, "id", "") if first_id == ""
×
870

871
            if last_label != ""
×
872
              if last_domain == ""
×
873
                label = Builtins.dgettext(wizard_textdomain, last_label)
×
874
              else
875
                label = Builtins.dgettext(last_domain, last_label)
×
876
                id = Ops.get_string(m, "id", "")
×
877
              end
878
              id = Ops.get_string(m, "id", "")
×
879
            end
880
          end
881
          UI.WizardCommand(term(:AddStepHeading, heading)) if !heading.nil? && heading != ""
×
882
          if !label.nil? && label != ""
×
883
            if debug_workflow == true
×
884
              label = Ops.add(
×
885
                label,
886
                Builtins.sformat(" [%1]", Ops.get_string(m, "name", ""))
887
              )
888
            end
889
            Builtins.y2debug("AddStep: %1/%2", label, id)
×
890
            UI.WizardCommand(term(:AddStep, label, id))
×
891
          end
892
        end
893
      end
894

895
      UI.WizardCommand(term(:SetCurrentStep, @CurrentWizardStep))
×
896

UNCOV
897
      nil
×
898
    end
899

900
    # Update Steps
901
    def UpdateWizardSteps(stagemode)
1✔
902
      stagemode = deep_copy(stagemode)
×
903
      if @force_UpdateWizardSteps == true
×
904
        Builtins.y2milestone("UpdateWizardSteps forced")
×
905
        @force_UpdateWizardSteps = false
×
906
      elsif @DisabledModules != @lastDisabledModules
×
907
        Builtins.y2milestone("Disabled modules were changed")
×
908
      elsif @last_stage_mode == stagemode
×
909
        Builtins.y2milestone("No changes in Wizard steps")
×
910
        return
×
911
      end
912

913
      @last_stage_mode = deep_copy(stagemode)
×
914
      @lastDisabledModules = deep_copy(@DisabledModules)
×
915

916
      UI.WizardCommand(term(:DeleteSteps))
×
917
      # Also redraws the wizard and sets the current step
918
      AddWizardSteps(stagemode)
×
919

UNCOV
920
      nil
×
921
    end
922

923
    # Retranslate Wizard Steps
924
    def RetranslateWizardSteps
1✔
925
      if Ops.greater_than(Builtins.size(@last_stage_mode), 0)
×
926
        Builtins.y2debug("Retranslating wizard steps")
×
927
        @force_UpdateWizardSteps = true
×
928
        UpdateWizardSteps(@last_stage_mode)
×
929
      end
930

UNCOV
931
      nil
×
932
    end
933

934
    def getMatchingProposal(stage, mode, proptype)
1✔
935
      Builtins.y2milestone(
×
936
        "Stage: %1 Mode: %2, Type: %3",
937
        stage,
938
        mode,
939
        proptype
940
      )
941

942
      # First we search for proposals for current stage if there are
943
      # any.
944
      props = Builtins.filter(@proposals) do |p|
×
945
        Check(Ops.get_string(p, "stage", ""), stage)
×
946
      end
947
      Builtins.y2debug("1. proposals: %1", props)
×
948

949
      # Then we check for mode: installation or update
950
      props = Builtins.filter(props) do |p|
×
951
        Check(Ops.get_string(p, "mode", ""), mode)
×
952
      end
953

954
      Builtins.y2debug("2. proposals: %1", props)
×
955

956
      # Now we check for architecture
957
      Builtins.y2debug(
×
958
        "Architecture: %1, Proposals: %2",
959
        Arch.architecture,
960
        props
961
      )
962

963
      arch_proposals = Builtins.filter(props) do |p|
×
964
        Ops.get_string(p, "name", "") == proptype &&
×
965
          Builtins.issubstring(
966
            Ops.get_string(p, "archs", "dummy"),
967
            Arch.arch_short
968
          )
969
      end
970

971
      Builtins.y2debug("3. arch proposals: %1", arch_proposals)
×
972

973
      props = Builtins.filter(props) do |p|
×
974
        Ops.get_string(p, "archs", "") == "" ||
×
975
          Ops.get_string(p, "archs", "") == "all"
976
      end
977

978
      Builtins.y2debug("4. other proposals: %1", props)
×
979
      # If architecture specific proposals are available, we continue with those
980
      # and check for proposal type, else we continue with pre arch proposal
981
      # list
982
      if Ops.greater_than(Builtins.size(arch_proposals), 0)
×
983
        props = Builtins.filter(arch_proposals) do |p|
×
984
          Ops.get_string(p, "name", "") == proptype
×
985
        end
986
        Builtins.y2debug("5. arch proposals: %1", props)
×
987
      else
988
        props = Builtins.filter(props) do |p|
×
989
          Ops.get_string(p, "name", "") == proptype
×
990
        end
991
        Builtins.y2debug("5. other proposals: %1", props)
×
992
      end
993

994
      if Ops.greater_than(Builtins.size(props), 1)
×
995
        Builtins.y2error(
×
996
          "Something Wrong happened, more than one proposal after filter:\n                %1",
997
          props
998
        )
999
      end
1000

1001
      # old style proposal
1002
      Builtins.y2milestone(
×
1003
        "Proposal modules: %1",
1004
        Ops.get(props, [0, "proposal_modules"])
1005
      )
1006
      deep_copy(props)
×
1007
    end
1008

1009
    # Get modules of current Workflow
1010
    # @param [String] stage
1011
    # @param [String] mode
1012
    # @param [String] proptype eg. "initial", "service", network"...
1013
    # @return [Array<Array(String,Integer)>] modules,
1014
    #   pairs of ("foo_proposal", presentation_order)
1015
    def getProposals(stage, mode, proptype)
1✔
1016
      props = getMatchingProposal(stage, mode, proptype)
×
1017
      unique_id = Ops.get_string(props, [0, "unique_id"], "")
×
1018
      disabled_subprops = GetDisabledSubProposals()
×
1019

1020
      final_proposals = []
×
1021
      Builtins.foreach(Ops.get_list(props, [0, "proposal_modules"], [])) do |p|
×
1022
        proposal_name = ""
×
1023
        order_value = 50
×
1024
        if Ops.is_string?(p)
×
1025
          proposal_name = Convert.to_string(p)
×
1026
        else
1027
          pm = Convert.convert(p, from: "any", to: "map <string, string>")
×
1028
          proposal_name = Ops.get(pm, "name", "")
×
1029
          proposal_order = Ops.get(pm, "presentation_order", "50")
×
1030

1031
          order_value = Builtins.tointeger(proposal_order)
×
1032
          if order_value.nil?
×
1033
            Builtins.y2error(
×
1034
              "Unable to use '%1' as proposal order, using %2 instead",
1035
              proposal_order,
1036
              50
1037
            )
1038
            order_value = 50
×
1039
          end
1040
        end
1041
        is_disabled = Builtins.haskey(disabled_subprops, unique_id) &&
×
1042
          Builtins.contains(
1043
            Ops.get(disabled_subprops, unique_id, []),
1044
            proposal_name
1045
          )
1046
        # All proposal file names end with _proposal
1047
        if is_disabled
×
1048
          Builtins.y2milestone(
×
1049
            "Proposal module %1 found among disabled subproposals",
1050
            proposal_name
1051
          )
1052
        else
1053
          final_proposals = if Builtins.issubstring(proposal_name, "_proposal")
×
1054
            Builtins.add(
×
1055
              final_proposals,
1056
              [proposal_name, order_value]
1057
            )
1058
          else
1059
            Builtins.add(
×
1060
              final_proposals,
1061
              [Ops.add(proposal_name, "_proposal"), order_value]
1062
            )
1063
          end
1064
        end
1065
      end
1066

1067
      Builtins.y2debug("final proposals: %1", final_proposals)
×
1068
      deep_copy(final_proposals)
×
1069
    end
1070

1071
    # Return text domain
1072
    def getProposalTextDomain
1✔
1073
      current_proposal_textdomain = Ops.get_string(
×
1074
        @productControl,
1075
        "textdomain",
1076
        "control"
1077
      )
1078

1079
      Builtins.y2debug(
×
1080
        "Using textdomain '%1' for proposals",
1081
        current_proposal_textdomain
1082
      )
1083
      current_proposal_textdomain
×
1084
    end
1085

1086
    # @param [String] stage
1087
    # @param [String] mode
1088
    # @param [String] proptype eg. "initial", "service", network"...
1089
    # @return [Hash] one "proposal" element of control.rnc
1090
    #   where /label is not translated yet but //proposal_tab/label are.
1091
    def getProposalProperties(stage, mode, proptype)
1✔
1092
      got_proposals = getMatchingProposal(stage, mode, proptype)
×
1093
      proposal = Ops.get(got_proposals, 0, {})
×
1094

1095
      if Builtins.haskey(proposal, "proposal_tabs")
×
1096
        text_domain = Ops.get_string(@productControl, "textdomain", "control")
×
1097
        Ops.set(
×
1098
          proposal,
1099
          "proposal_tabs",
1100
          Builtins.maplist(Ops.get_list(proposal, "proposal_tabs", [])) do |tab|
1101
            domain = Ops.get_string(tab, "textdomain", text_domain)
×
1102
            Ops.set(
×
1103
              tab,
1104
              "label",
1105
              Builtins.dgettext(domain, Ops.get_string(tab, "label", ""))
1106
            )
1107
            deep_copy(tab)
×
1108
          end
1109
        )
1110
      end
1111

1112
      deep_copy(proposal)
×
1113
    end
1114

1115
    def GetTranslatedText(key)
1✔
1116
      controlfile_texts = ProductFeatures.GetSection("texts")
×
1117

1118
      if !Builtins.haskey(controlfile_texts, key)
×
1119
        Builtins.y2error("No such text %1", key)
×
1120
        return ""
×
1121
      end
1122

1123
      text = Ops.get_map(controlfile_texts, key, {})
×
1124

1125
      label = Ops.get(text, "label", "")
×
1126

1127
      # an empty string doesn't need to be translated
1128
      return "" if label == ""
×
1129

1130
      domain = Ops.get(
×
1131
        text,
1132
        "textdomain",
1133
        Ops.get_string(@productControl, "textdomain", "control")
1134
      )
1135
      if domain == ""
×
1136
        Builtins.y2warning("The text domain for label %1 not set", key)
×
1137
        return label
×
1138
      end
1139

1140
      Builtins.dgettext(domain, label)
×
1141
    end
1142

1143
    # Initialize Product Control
1144
    # @return [Boolean] True on success
1145
    def Init
1✔
1146
      # Ordered list
1147
      control_file_candidates = [
1148
        @y2update_control_file,     # /y2update/control.xml
1✔
1149
        @installation_control_file, # /control.xml
1150
        @saved_control_file # /etc/YaST2/control.xml
1151
      ]
1152

1153
      if @custom_control_file.nil?
1✔
1154
        Bultins.y2error("Incorrectly set custom control file: #{@custom_control_file}")
×
1155
        return false
×
1156
      end
1157

1158
      control_file_candidates.unshift(@custom_control_file) if !@custom_control_file.empty?
1✔
1159

1160
      Builtins.y2milestone("Candidates: #{control_file_candidates.inspect}")
1✔
1161
      @current_control_file = control_file_candidates.find { |f| FileUtils.Exists(f) }
4✔
1162

1163
      if @current_control_file.nil?
1✔
1164
        Builtins.y2error("No control file found within #{control_file_candidates.inspect}")
×
1165
        return false
×
1166
      end
1167

1168
      Builtins.y2milestone("Reading control file: #{@current_control_file}")
1✔
1169
      ReadControlFile(@current_control_file)
1✔
1170

1171
      true
1✔
1172
    end
1173

1174
    # Re-translate static part of wizard dialog and other predefined messages
1175
    # after language change
1176
    def retranslateWizardDialog
1✔
1177
      Builtins.y2milestone("Retranslating messages, redrawing wizard steps")
×
1178

1179
      # Make sure the labels for default function keys are retranslated, too.
1180
      # Using Label::DefaultFunctionKeyMap() from Label module.
1181
      UI.SetFunctionKeys(Label.DefaultFunctionKeyMap)
×
1182

1183
      # Activate language changes on static part of wizard dialog
1184
      RetranslateWizardSteps()
×
1185
      Wizard.RetranslateButtons
×
1186
      Wizard.SetFocusToNextButton
×
UNCOV
1187
      nil
×
1188
    end
1189

1190
    def RunFrom(from, allow_back)
1✔
1191
      former_result = :next
×
1192
      final_result = nil
×
1193
      @current_step = from # current module
×
1194

1195
      Wizard.SetFocusToNextButton
×
1196

1197
      Builtins.y2debug(
×
1198
        "Starting Workflow with  \"%1\" \"%2\"",
1199
        Stage.stage,
1200
        Mode.mode
1201
      )
1202

1203
      modules = getModules(Stage.stage, Mode.mode, :enabled)
×
1204

1205
      defaults = getModeDefaults(Stage.stage, Mode.mode)
×
1206

1207
      Builtins.y2debug("modules: %1", modules)
×
1208

1209
      if Builtins.size(modules) == 0
×
1210
        Builtins.y2error("No workflow found: %1", modules)
×
1211
        # error report
1212
        Report.Error(_("No workflow defined for this installation mode."))
×
1213
        return :abort
×
1214
      end
1215

1216
      minimum_step = allow_back ? 0 : from
×
1217

1218
      if Ops.less_than(minimum_step, from)
×
1219
        Builtins.y2warning(
×
1220
          "Minimum step set to: %1 even if running from %2, fixing",
1221
          minimum_step,
1222
          from
1223
        )
1224
        minimum_step = from
×
1225
      end
1226

1227
      while Ops.greater_or_equal(@current_step, 0) &&
×
1228
          Ops.less_than(@current_step, Builtins.size(modules))
1229
        step = Ops.get(modules, @current_step, {})
×
1230
        step_name = Ops.get_string(step, "name", "")
×
1231
        # BNC #401319
1232
        # if "execute" is defined, it's called without modifications
1233
        step_execute = Ops.get_string(step, "execute", "")
×
1234
        step_id = Ops.get_string(step, "id", "")
×
1235
        run_in_update_mode = Ops.get_boolean(step, "update", true) # default is true
×
1236
        retranslate = Ops.get_boolean(step, "retranslate", false)
×
1237

1238
        # The very first dialog has back button disabled
1239
        if Ops.less_or_equal(@current_step, minimum_step) && !Builtins.haskey(step, "enable_back")
×
1240
          Ops.set(step, "enable_back", "no")
×
1241
          Builtins.y2milestone(
×
1242
            "Disabling back: %1 %2 %3",
1243
            @current_step,
1244
            minimum_step,
1245
            Ops.get(step, "enable_back")
1246
          )
1247
        end
1248

1249
        do_continue = false
×
1250

1251
        do_continue = true if !checkArch(step, defaults)
×
1252

1253
        do_continue = true if checkDisabled(step)
×
1254

1255
        do_continue = true if checkHeading(step)
×
1256

1257
        do_continue = true if !run_in_update_mode && Mode.update
×
1258

1259
        if do_continue
×
1260
          if former_result == :next
×
1261
            minimum_step = Ops.add(minimum_step, 1) if Ops.less_or_equal(@current_step, minimum_step) && !allow_back
×
1262
            @current_step = Ops.add(@current_step, 1)
×
1263
          else
1264
            @current_step = Ops.subtract(@current_step, 1)
×
1265
          end
1266
        end
1267
        # Continue in 'while' means 'next step'
1268
        if do_continue
×
1269
          log.info "Skipping step #{step.inspect}"
×
1270
          next
×
1271
        end
1272

1273
        argterm = getClientTerm(step, defaults, former_result)
×
1274

1275
        result = nil
×
1276
        log.group("#{step["label"]} #{step["name"].inspect}") do
×
1277
          Builtins.y2milestone("Running module: %1 (%2)", argterm, @current_step)
×
1278

1279
          Builtins.y2milestone("Calling %1", argterm)
×
1280

1281
          args = []
×
1282
          i = 0
×
1283
          while Ops.less_than(i, Builtins.size(argterm))
×
1284
            Ops.set(args, i, Ops.get(argterm, i))
×
1285
            i = Ops.add(i, 1)
×
1286
          end
1287

1288
          UI.WizardCommand(term(:SetCurrentStep, step_id))
×
1289
          @CurrentWizardStep = step_id
×
1290

1291
          # Register what step we are going to run
1292
          if !Stage.initial && !SCR.Write(
×
1293
            path(".target.string"),
1294
            Installation.current_step,
1295
            step_id
1296
          )
1297
            Builtins.y2error("Error writing step identifier")
×
1298
          end
1299

1300
          client_name = getClientName(step_name, step_execute)
×
1301

1302
          # Check if client exist before continuing
1303
          if WFM.ClientExists(client_name)
×
1304
            Hooks.run("before_#{step_name}")
×
1305

1306
            result = WFM.CallFunction(client_name, args)
×
1307

1308
            # This code will be triggered before the red pop window appears on the user's screen
1309
            Hooks.run("installation_failure") if result == false
×
1310

1311
            result = Convert.to_symbol(result)
×
1312

1313
            Hooks.run("after_#{step_name}")
×
1314
          else
1315
            # Client not found. Ask the user if want to continue (related to bsc#1180954)
1316
            log.error("Client '#{client_name}' not found")
×
1317

1318
            text = format(
×
1319
              # TRANSLATORS: Message warning the user that a client is missing where %{client} is
1320
              # replaced by the client name (e.g. "registration", "user")
1321
              _(
1322
                "Something went wrong and the expected '%{client}' dialog was not found.\n\n" \
1323
                "Would you like to skip the dialog and continue anyway?"
1324
              ),
1325
              client: client_name
1326
            )
1327

1328
            options = { yes: Label.ContinueButton, no: Label.AbortButton }
×
1329
            continue = Yast2::Popup.show(text, buttons: options) == :yes
×
1330

1331
            if continue
×
1332
              log.warn("Continuing after skipping the '#{client_name}' missing client")
×
1333
              # If user decided to continue, uses the former_result (:next or :back)
1334
              result = former_result
×
1335
            else
1336
              # :abort because user decided to not continue
1337
              result = :abort
×
1338
            end
1339
          end
1340

1341
          Builtins.y2milestone("Calling %1 returned %2", argterm, result)
×
1342

1343
          # bnc #369846
1344
          if [:access, :ok].include?(result)
×
1345
            Builtins.y2milestone("Evaluating %1 as it was `next", result)
×
1346
            result = :next
×
1347
          end
1348

1349
          # Clients can break the installation/workflow
1350
          Wizard.RestoreNextButton
×
1351
          Wizard.RestoreAbortButton
×
1352
          Wizard.RestoreBackButton
×
1353

1354
          # Remove file if step was run and returned (without a crash);
1355
          if Ops.less_than(@current_step, Ops.subtract(Builtins.size(modules), 1)) &&
×
1356
              !Stage.initial && !Convert.to_boolean(
1357
              SCR.Execute(path(".target.remove"), Installation.current_step)
1358
            )
1359
            Builtins.y2error("Error removing step identifier")
×
1360
          end
1361

1362
          if retranslate
×
1363
            Builtins.y2milestone("retranslate")
×
1364
            retranslateWizardDialog
×
1365
            retranslate = false
×
1366
          end
1367

1368
          result
×
1369
        end
1370

1371
        # If the module return nil, something basic went wrong.
1372
        # We show a stub dialog instead.
1373
        if result.nil?
×
1374
          # If workflow module is marked as optional, skip if it returns nil,
1375
          # For example, if it is not installed.
1376
          if Ops.get_boolean(step, "optional", false)
×
1377
            Builtins.y2milestone(
×
1378
              "Skipping optional %1",
1379
              Builtins.symbolof(argterm)
1380
            )
1381
            @current_step = Ops.add(@current_step, 1)
×
1382
            next
×
1383
          end
1384

1385
          r = nil
×
1386
          r = Popup.ModuleError(
×
1387
            Builtins.sformat(
1388
              # TRANSLATORS: an error message
1389
              # this should not happen, but life is cruel...
1390
              # %1 - (failed) module name
1391
              # %2 - logfile, possibly with errors
1392
              # %3 - link to our bugzilla
1393
              # %4 - directory where YaST logs are stored
1394
              # %5 - link to the Yast Bug Reporting HOWTO Web page
1395
              "Calling the YaST module %1 has failed.\n" \
1396
              "More information can be found near the end of the '%2' file.\n" \
1397
              "\n" \
1398
              "This is worth reporting a bug at %3.\n" \
1399
              "Please, attach also all YaST logs stored in the '%4' directory.\n" \
1400
              "See %5 for more information about YaST logs.",
1401
              Builtins.symbolof(argterm),
1402
              "/var/log/YaST2/y2log",
1403
              "http://bugzilla.suse.com/",
1404
              "/var/log/YaST2/",
1405
              # link to the Yast Bug Reporting HOWTO
1406
              # for translators: use the localized page for your language if it exists,
1407
              # check the combo box "In other laguages" on top of the page
1408
              _("http://en.opensuse.org/Bugs/YaST")
1409
            )
1410
          )
1411

1412
          if r == :next
×
1413
            @current_step = Ops.add(@current_step, 1)
×
1414
          elsif r == :back
×
1415
            @current_step = Ops.subtract(@current_step, 1)
×
1416
          elsif r != :again
×
1417
            UI.CloseDialog
×
1418
            return nil
×
1419
          end
1420
          next
×
1421
        end
1422

1423
        # BNC #468677
1424
        # The very first dialog must not exit with `back
1425
        # or `auto
1426
        if @current_step == 0 &&
×
1427
            (result == :back || (result == :auto && former_result == :back))
×
1428
          Builtins.y2warning(
×
1429
            "Returned %1, Current step %2 (%3). The current step will be called again...",
1430
            result,
1431
            @current_step,
1432
            step_name
1433
          )
1434
          former_result = :next
×
1435
          result = :again
×
1436
        end
1437

1438
        case result
×
1439
        when :next
1440
          @current_step = Ops.add(@current_step, 1)
×
1441
        when :back
1442
          @current_step = Ops.subtract(@current_step, 1)
×
1443
        when :cancel, :finish
1444
          break
×
1445
        when :abort
1446
          # handling when user aborts the workflow (FATE #300422, bnc #406401, bnc #247552)
1447
          final_result = result
×
1448
          Hooks.run("installation_aborted")
×
1449

1450
          break
×
1451
        when :again
1452
          next # Show same dialog again
×
1453
        # BNC #475650: Adding `reboot_same_step
1454
        when :restart_yast, :restart_same_step, :reboot, :reboot_same_step
1455
          final_result = result
×
1456
          break
×
1457
        when :auto
1458
          if !former_result.nil?
×
1459
            case former_result
×
1460
            when :next
1461
              # if the first client just returns `auto, the back button
1462
              # of the next client must be disabled
1463
              minimum_step = Ops.add(minimum_step, 1) if Ops.less_or_equal(@current_step, minimum_step) && !allow_back
×
1464
              @current_step = Ops.add(@current_step, 1)
×
1465
            when :back
1466
              @current_step = Ops.subtract(@current_step, 1)
×
1467
            end
1468
          end
1469
          next
×
1470
        end
1471
        former_result = result
×
1472
      end
1473

1474
      final_result = :abort if former_result == :abort
×
1475

1476
      Builtins.y2milestone(
×
1477
        "Former result: %1, Final result: %2",
1478
        former_result,
1479
        final_result
1480
      )
1481

1482
      if !final_result.nil?
×
1483
        Builtins.y2milestone("Final result already set.")
×
1484
      elsif Ops.less_or_equal(@current_step, -1)
×
1485
        final_result = :back
×
1486
      else
1487
        final_result = :next
×
1488
      end
1489

1490
      Builtins.y2milestone(
×
1491
        "Current step: %1, Returning: %2",
1492
        @current_step,
1493
        final_result
1494
      )
1495
      final_result
×
1496
    end
1497

1498
    # Run Workflow
1499
    #
1500
    def Run
1✔
1501
      ret = RunFrom(0, false)
×
1502
      Builtins.y2milestone("Run() returning %1", ret)
×
1503
      ret
×
1504
    end
1505

1506
    # Functions to access restart information
1507

1508
    # List steps which were skipped since last restart of YaST
1509
    # @return a list of maps describing the steps
1510
    def SkippedSteps
1✔
1511
      modules = getModules(Stage.stage, Mode.mode, :enabled)
×
1512
      return nil if @first_step.nil?
×
1513
      return nil if Ops.greater_or_equal(@first_step, Builtins.size(modules))
×
1514

1515
      index = 0
×
1516
      ret = []
×
1517
      while Ops.less_than(index, @first_step)
×
1518
        ret = Builtins.add(ret, Ops.get(modules, index, {}))
×
1519
        index = Ops.add(index, 1)
×
1520
      end
1521
      deep_copy(ret)
×
1522
    end
1523

1524
    # Return step which restarted YaST (or rebooted the system)
1525
    # @return a map describing the step
1526
    def RestartingStep
1✔
1527
      return nil if @restarting_step.nil?
×
1528

1529
      modules = getModules(Stage.stage, Mode.mode, :enabled)
×
1530
      Ops.get(modules, @restarting_step, {})
×
1531
    end
1532

1533
    # ProductControl Constructor
1534
    # @return [void]
1535
    def ProductControl
1✔
1536
      Builtins.y2error("control file missing") if !Init()
1✔
1537
      nil
1✔
1538
    end
1539

1540
    # Sets additional params for selecting the workflow
1541
    #
1542
    # @param [Hash{String => Object}] params
1543
    # @example SetAdditionalWorkflowParams ($["add_on_mode":"update"]);
1544
    # @example SetAdditionalWorkflowParams ($["add_on_mode":"installation"]);
1545
    def SetAdditionalWorkflowParams(params)
1✔
1546
      params = deep_copy(params)
×
1547
      Builtins.y2milestone(
×
1548
        "Adjusting new additional workflow params: %1",
1549
        params
1550
      )
1551

1552
      @_additional_workflow_params = deep_copy(params)
×
1553

UNCOV
1554
      nil
×
1555
    end
1556

1557
    # Resets all additional params for selecting the workflow
1558
    # @see #SetAdditionalWorkflowParams()
1559
    def ResetAdditionalWorkflowParams
1✔
1560
      @_additional_workflow_params = {}
×
1561

UNCOV
1562
      nil
×
1563
    end
1564

1565
    # Add new system roles
1566
    #
1567
    # For the time being, new roles are appended to the list of roles.
1568
    #
1569
    # @example Adding a simple role
1570
    #   ProductControl.system_roles #=> [{"id" => "normal_role"}]
1571
    #   ProductControl.add_system_roles([{"id" => "new_role"}])
1572
    #   ProductControl.system_roles #=> [{"id" => "normal_roles"}, {"id" => "new_role"}]
1573
    #
1574
    # @param [Array<Hash>] new_roles Roles to add
1575
    def add_system_roles(new_roles)
1✔
1576
      log.info "Adding roles to product control: #{new_roles.inspect}"
1✔
1577
      system_roles.concat(new_roles)
1✔
1578
    end
1579

1580
    publish variable: :productControl, type: "map"
1✔
1581
    publish variable: :workflows, type: "list <map>"
1✔
1582
    publish variable: :proposals, type: "list <map>"
1✔
1583
    publish variable: :inst_finish, type: "list <map <string, any>>"
1✔
1584
    publish variable: :clone_modules, type: "list <string>"
1✔
1585
    publish variable: :system_roles, type: "list <map>"
1✔
1586
    publish variable: :custom_control_file, type: "string"
1✔
1587
    publish variable: :y2update_control_file, type: "string"
1✔
1588
    publish variable: :default_control_file, type: "string"
1✔
1589
    publish variable: :saved_control_file, type: "string"
1✔
1590
    publish variable: :packaged_control_file, type: "string"
1✔
1591
    publish variable: :current_control_file, type: "string"
1✔
1592
    publish variable: :CurrentWizardStep, type: "string"
1✔
1593
    publish variable: :last_stage_mode, type: "list <map>"
1✔
1594
    publish variable: :logfiles, type: "list <string>"
1✔
1595
    publish variable: :first_step, type: "integer"
1✔
1596
    publish variable: :restarting_step, type: "integer"
1✔
1597
    publish function: :CurrentStep, type: "integer ()"
1✔
1598
    publish function: :setClientPrefix, type: "void (string)"
1✔
1599
    publish function: :EnableModule, type: "list <string> (string)"
1✔
1600
    publish function: :DisableModule, type: "list <string> (string)"
1✔
1601
    publish function: :GetDisabledModules, type: "list <string> ()"
1✔
1602
    publish function: :EnableProposal, type: "list <string> (string)"
1✔
1603
    publish function: :DisableProposal, type: "list <string> (string)"
1✔
1604
    publish function: :GetDisabledProposals, type: "list <string> ()"
1✔
1605
    publish function: :EnableSubProposal, type: "map <string, list <string>> (string, string)"
1✔
1606
    publish function: :DisableSubProposal, type: "map <string, list <string>> (string, string)"
1✔
1607
    publish function: :GetDisabledSubProposals, type: "map <string, list <string>> ()"
1✔
1608
    publish function: :checkDisabled, type: "boolean (map)"
1✔
1609
    publish function: :checkHeading, type: "boolean (map)"
1✔
1610
    publish function: :ReadControlFile, type: "boolean (string)"
1✔
1611
    publish function: :checkArch, type: "boolean (map, map)"
1✔
1612
    publish function: :getClientTerm, type: "term (map, map, any)"
1✔
1613
    publish function: :getModeDefaults, type: "map (string, string)"
1✔
1614
    publish function: :RequiredFiles, type: "list <string> (string, string)"
1✔
1615
    publish function: :getCompleteWorkflow, type: "map (string, string)"
1✔
1616
    publish function: :getModules, type: "list <map> (string, string, symbol)"
1✔
1617
    publish function: :RunRequired, type: "boolean (string, string)"
1✔
1618
    publish function: :getWorkflowLabel, type: "string (string, string, string)"
1✔
1619
    publish function: :DisableAllModulesAndProposals, type: "void (string, string)"
1✔
1620
    publish function: :UnDisableAllModulesAndProposals, type: "void (string, string)"
1✔
1621
    publish function: :AddWizardSteps, type: "void (list <map>)"
1✔
1622
    publish function: :UpdateWizardSteps, type: "void (list <map>)"
1✔
1623
    publish function: :RetranslateWizardSteps, type: "void ()"
1✔
1624
    publish function: :getProposals, type: "list <list> (string, string, string)"
1✔
1625
    publish function: :getProposalTextDomain, type: "string ()"
1✔
1626
    publish function: :getProposalProperties, type: "map (string, string, string)"
1✔
1627
    publish function: :GetTranslatedText, type: "string (string)"
1✔
1628
    publish function: :Init, type: "boolean ()"
1✔
1629
    publish function: :RunFrom, type: "symbol (integer, boolean)"
1✔
1630
    publish function: :Run, type: "symbol ()"
1✔
1631
    publish function: :SkippedSteps, type: "list <map> ()"
1✔
1632
    publish function: :RestartingStep, type: "map ()"
1✔
1633
    publish function: :ProductControl, type: "void ()"
1✔
1634
    publish function: :SetAdditionalWorkflowParams, type: "void (map <string, any>)"
1✔
1635
    publish function: :ResetAdditionalWorkflowParams, type: "void ()"
1✔
1636
    publish function: :add_system_roles, type: "void (list <map>)"
1✔
1637
  end
1638

1639
  ProductControl = ProductControlClass.new
1✔
1640
  ProductControl.main
1✔
1641
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