d-installer 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +75 -0
- data/bin/d-installer +74 -0
- data/etc/d-installer.yaml +284 -0
- data/lib/dinstaller/can_ask_question.rb +48 -0
- data/lib/dinstaller/cmdline_args.rb +80 -0
- data/lib/dinstaller/cockpit_manager.rb +176 -0
- data/lib/dinstaller/config.rb +128 -0
- data/lib/dinstaller/config_reader.rb +164 -0
- data/lib/dinstaller/dbus/base_object.rb +58 -0
- data/lib/dinstaller/dbus/clients/base.rb +71 -0
- data/lib/dinstaller/dbus/clients/language.rb +86 -0
- data/lib/dinstaller/dbus/clients/manager.rb +76 -0
- data/lib/dinstaller/dbus/clients/software.rb +185 -0
- data/lib/dinstaller/dbus/clients/users.rb +112 -0
- data/lib/dinstaller/dbus/clients/with_progress.rb +56 -0
- data/lib/dinstaller/dbus/clients/with_service_status.rb +75 -0
- data/lib/dinstaller/dbus/clients.rb +34 -0
- data/lib/dinstaller/dbus/interfaces/progress.rb +113 -0
- data/lib/dinstaller/dbus/interfaces/service_status.rb +89 -0
- data/lib/dinstaller/dbus/language.rb +93 -0
- data/lib/dinstaller/dbus/language_service.rb +92 -0
- data/lib/dinstaller/dbus/manager.rb +147 -0
- data/lib/dinstaller/dbus/manager_service.rb +132 -0
- data/lib/dinstaller/dbus/question.rb +176 -0
- data/lib/dinstaller/dbus/questions.rb +124 -0
- data/lib/dinstaller/dbus/service_runner.rb +97 -0
- data/lib/dinstaller/dbus/service_status.rb +87 -0
- data/lib/dinstaller/dbus/software/manager.rb +131 -0
- data/lib/dinstaller/dbus/software/proposal.rb +82 -0
- data/lib/dinstaller/dbus/software.rb +31 -0
- data/lib/dinstaller/dbus/software_service.rb +86 -0
- data/lib/dinstaller/dbus/storage/proposal.rb +170 -0
- data/lib/dinstaller/dbus/storage.rb +30 -0
- data/lib/dinstaller/dbus/users.rb +132 -0
- data/lib/dinstaller/dbus/users_service.rb +92 -0
- data/lib/dinstaller/dbus/with_service_status.rb +48 -0
- data/lib/dinstaller/dbus/y2dir/manager/modules/Package.rb +51 -0
- data/lib/dinstaller/dbus/y2dir/manager/modules/PackagesProposal.rb +62 -0
- data/lib/dinstaller/dbus/y2dir/modules/Autologin.rb +214 -0
- data/lib/dinstaller/dbus/y2dir/software/modules/SpaceCalculation.rb +44 -0
- data/lib/dinstaller/dbus.rb +11 -0
- data/lib/dinstaller/errors.rb +28 -0
- data/lib/dinstaller/installation_phase.rb +106 -0
- data/lib/dinstaller/language.rb +73 -0
- data/lib/dinstaller/luks_activation_question.rb +92 -0
- data/lib/dinstaller/manager.rb +261 -0
- data/lib/dinstaller/network.rb +53 -0
- data/lib/dinstaller/package_callbacks.rb +69 -0
- data/lib/dinstaller/progress.rb +149 -0
- data/lib/dinstaller/question.rb +103 -0
- data/lib/dinstaller/questions_manager.rb +145 -0
- data/lib/dinstaller/security.rb +96 -0
- data/lib/dinstaller/service_status_recorder.rb +58 -0
- data/lib/dinstaller/software.rb +232 -0
- data/lib/dinstaller/storage/actions.rb +90 -0
- data/lib/dinstaller/storage/callbacks/activate.rb +93 -0
- data/lib/dinstaller/storage/callbacks/activate_luks.rb +93 -0
- data/lib/dinstaller/storage/callbacks/activate_multipath.rb +77 -0
- data/lib/dinstaller/storage/callbacks.rb +30 -0
- data/lib/dinstaller/storage/manager.rb +104 -0
- data/lib/dinstaller/storage/proposal.rb +197 -0
- data/lib/dinstaller/storage.rb +30 -0
- data/lib/dinstaller/users.rb +156 -0
- data/lib/dinstaller/with_progress.rb +63 -0
- data/lib/dinstaller.rb +5 -0
- data/share/dbus.conf +38 -0
- data/share/dbus.service +5 -0
- data/share/systemd.service +14 -0
- metadata +295 -0
@@ -0,0 +1,261 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "yast"
|
23
|
+
require "bootloader/proposal_client"
|
24
|
+
require "bootloader/finish_client"
|
25
|
+
require "dinstaller/config"
|
26
|
+
require "dinstaller/network"
|
27
|
+
require "dinstaller/security"
|
28
|
+
require "dinstaller/storage"
|
29
|
+
require "dinstaller/questions_manager"
|
30
|
+
require "dinstaller/with_progress"
|
31
|
+
require "dinstaller/installation_phase"
|
32
|
+
require "dinstaller/service_status_recorder"
|
33
|
+
require "dinstaller/dbus/clients/language"
|
34
|
+
require "dinstaller/dbus/clients/software"
|
35
|
+
require "dinstaller/dbus/clients/users"
|
36
|
+
|
37
|
+
Yast.import "Stage"
|
38
|
+
|
39
|
+
module DInstaller
|
40
|
+
# This class represents the top level installer manager.
|
41
|
+
#
|
42
|
+
# It is responsible for orchestrating the installation process. For module
|
43
|
+
# specific stuff it delegates it to the corresponding module class (e.g.,
|
44
|
+
# {DInstaller::Network}, {DInstaller::Storage::Proposal}, etc.) or asks
|
45
|
+
# other services via D-Bus (e.g., `org.opensuse.DInstaller.Software`).
|
46
|
+
class Manager
|
47
|
+
include WithProgress
|
48
|
+
|
49
|
+
# @return [Logger]
|
50
|
+
attr_reader :logger
|
51
|
+
|
52
|
+
# @return [QuestionsManager]
|
53
|
+
attr_reader :questions_manager
|
54
|
+
|
55
|
+
# @return [InstallationPhase]
|
56
|
+
attr_reader :installation_phase
|
57
|
+
|
58
|
+
# Constructor
|
59
|
+
#
|
60
|
+
# @param logger [Logger]
|
61
|
+
def initialize(config, logger)
|
62
|
+
@config = config
|
63
|
+
@logger = logger
|
64
|
+
@questions_manager = QuestionsManager.new(logger)
|
65
|
+
@installation_phase = InstallationPhase.new
|
66
|
+
@service_status_recorder = ServiceStatusRecorder.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# Runs the startup phase
|
70
|
+
def startup_phase
|
71
|
+
installation_phase.startup
|
72
|
+
|
73
|
+
probe_single_product unless config.multi_product?
|
74
|
+
|
75
|
+
logger.info("Startup phase done")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Runs the config phase
|
79
|
+
def config_phase
|
80
|
+
installation_phase.config
|
81
|
+
|
82
|
+
storage.probe(questions_manager)
|
83
|
+
security.probe
|
84
|
+
network.probe
|
85
|
+
|
86
|
+
logger.info("Config phase done")
|
87
|
+
rescue StandardError => e
|
88
|
+
logger.error "Startup error: #{e.inspect}. Backtrace: #{e.backtrace}"
|
89
|
+
# TODO: report errors
|
90
|
+
end
|
91
|
+
|
92
|
+
# Runs the install phase
|
93
|
+
# rubocop:disable Metrics/AbcSize
|
94
|
+
def install_phase
|
95
|
+
installation_phase.install
|
96
|
+
|
97
|
+
start_progress(9)
|
98
|
+
|
99
|
+
progress.step("Reading software repositories") do
|
100
|
+
software.probe
|
101
|
+
Yast::Installation.destdir = "/mnt"
|
102
|
+
end
|
103
|
+
|
104
|
+
progress.step("Partitioning") do
|
105
|
+
# lets propose it here to be sure that software proposal reflects product selection
|
106
|
+
# FIXME: maybe repropose after product selection change?
|
107
|
+
# first make bootloader proposal to be sure that required packages are installed
|
108
|
+
proposal = ::Bootloader::ProposalClient.new.make_proposal({})
|
109
|
+
logger.info "Bootloader proposal #{proposal.inspect}"
|
110
|
+
storage.install
|
111
|
+
# propose software after /mnt is already separated, so it uses proper
|
112
|
+
# target
|
113
|
+
software.propose
|
114
|
+
|
115
|
+
# call inst bootloader to get properly initialized bootloader
|
116
|
+
# sysconfig before package installation
|
117
|
+
Yast::WFM.CallFunction("inst_bootloader", [])
|
118
|
+
end
|
119
|
+
|
120
|
+
progress.step("Installing Software") { software.install }
|
121
|
+
|
122
|
+
on_target do
|
123
|
+
progress.step("Writing Users") { users.write }
|
124
|
+
|
125
|
+
progress.step("Writing Network Configuration") { network.install }
|
126
|
+
|
127
|
+
progress.step("Installing Bootloader") do
|
128
|
+
security.write
|
129
|
+
::Bootloader::FinishClient.new.write
|
130
|
+
end
|
131
|
+
|
132
|
+
progress.step("Saving Language Settings") { language.finish }
|
133
|
+
|
134
|
+
progress.step("Writing repositories information") { software.finish }
|
135
|
+
|
136
|
+
progress.step("Finishing installation") { finish_installation }
|
137
|
+
end
|
138
|
+
|
139
|
+
logger.info("Install phase done")
|
140
|
+
end
|
141
|
+
# rubocop:enable Metrics/AbcSize
|
142
|
+
|
143
|
+
# Software client
|
144
|
+
#
|
145
|
+
# @return [DBus::Clients::Software]
|
146
|
+
def software
|
147
|
+
@software ||= DBus::Clients::Software.new.tap do |client|
|
148
|
+
client.on_service_status_change do |status|
|
149
|
+
service_status_recorder.save(client.service.name, status)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Language manager
|
155
|
+
#
|
156
|
+
# @return [DBus::Clients::Language]
|
157
|
+
def language
|
158
|
+
@language ||= DBus::Clients::Language.new
|
159
|
+
end
|
160
|
+
|
161
|
+
# Users client
|
162
|
+
#
|
163
|
+
# @return [DBus::Clients::Users]
|
164
|
+
def users
|
165
|
+
@users ||= DBus::Clients::Users.new.tap do |client|
|
166
|
+
client.on_service_status_change do |status|
|
167
|
+
service_status_recorder.save(client.service.name, status)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Network manager
|
173
|
+
#
|
174
|
+
# @return [Network]
|
175
|
+
def network
|
176
|
+
@network ||= Network.new(logger)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Storage manager
|
180
|
+
#
|
181
|
+
# @return [Storage::Manager]
|
182
|
+
def storage
|
183
|
+
@storage ||= Storage::Manager.new(logger, config)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Security manager
|
187
|
+
#
|
188
|
+
# @return [Security]
|
189
|
+
def security
|
190
|
+
@security ||= Security.new(logger, config)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Actions to perform when a product is selected
|
194
|
+
#
|
195
|
+
# @note The config phase is executed.
|
196
|
+
def select_product(product)
|
197
|
+
config.pick_product(product)
|
198
|
+
config_phase
|
199
|
+
end
|
200
|
+
|
201
|
+
# Name of busy services
|
202
|
+
#
|
203
|
+
# @see ServiceStatusRecorder
|
204
|
+
#
|
205
|
+
# @return [Array<String>]
|
206
|
+
def busy_services
|
207
|
+
service_status_recorder.busy_services
|
208
|
+
end
|
209
|
+
|
210
|
+
# Registers a callback to be called when the status of a service changes
|
211
|
+
#
|
212
|
+
# @see ServiceStatusRecorder
|
213
|
+
def on_services_status_change(&block)
|
214
|
+
service_status_recorder.on_service_status_change(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
attr_reader :config
|
220
|
+
|
221
|
+
# @return [ServiceStatusRecorder]
|
222
|
+
attr_reader :service_status_recorder
|
223
|
+
|
224
|
+
# Performs required steps after installing the system
|
225
|
+
#
|
226
|
+
# For now, this only unmounts the installed system and copies installation logs. Note that YaST
|
227
|
+
# performs many more steps like copying configuration files, creating snapshots, etc. Adding
|
228
|
+
# more features to D-Installer could require to recover some of that YaST logic.
|
229
|
+
def finish_installation
|
230
|
+
Yast::WFM.CallFunction("copy_logs_finish", ["Write"])
|
231
|
+
storage.finish
|
232
|
+
end
|
233
|
+
|
234
|
+
# Runs a block in the target system
|
235
|
+
def on_target(&block)
|
236
|
+
old_handle = Yast::WFM.SCRGetDefault
|
237
|
+
handle = Yast::WFM.SCROpen("chroot=#{Yast::Installation.destdir}:scr", false)
|
238
|
+
Yast::WFM.SCRSetDefault(handle)
|
239
|
+
|
240
|
+
begin
|
241
|
+
block.call
|
242
|
+
rescue StandardError => e
|
243
|
+
logger.error "Error while running on target tasks: #{e.inspect}"
|
244
|
+
ensure
|
245
|
+
Yast::WFM.SCRSetDefault(old_handle)
|
246
|
+
Yast::WFM.SCRClose(handle)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Runs the config phase for the first product found
|
251
|
+
#
|
252
|
+
# Adjust the configuration and run the config phase.
|
253
|
+
#
|
254
|
+
# This method is expected to be used on single-product scenarios.
|
255
|
+
def probe_single_product
|
256
|
+
selected = config.data["products"].keys.first
|
257
|
+
config.pick_product(selected)
|
258
|
+
config_phase
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "singleton"
|
23
|
+
require "yast"
|
24
|
+
require "y2network/proposal_settings"
|
25
|
+
Yast.import "Lan"
|
26
|
+
|
27
|
+
module DInstaller
|
28
|
+
# Backend class to handle network configuration
|
29
|
+
class Network
|
30
|
+
def initialize(logger)
|
31
|
+
@logger = logger
|
32
|
+
end
|
33
|
+
|
34
|
+
# Probes the network configuration
|
35
|
+
def probe
|
36
|
+
logger.info "Probing network"
|
37
|
+
Yast::Lan.read_config
|
38
|
+
settings = Y2Network::ProposalSettings.instance
|
39
|
+
settings.refresh_packages
|
40
|
+
settings.apply_defaults
|
41
|
+
end
|
42
|
+
|
43
|
+
# Writes the network configuration to the installed system
|
44
|
+
def install
|
45
|
+
Yast::WFM.CallFunction("save_network", [])
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @return [Logger]
|
51
|
+
attr_reader :logger
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2021] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "yast"
|
23
|
+
|
24
|
+
Yast.import "Pkg"
|
25
|
+
|
26
|
+
# YaST specific code lives under this namespace
|
27
|
+
module DInstaller
|
28
|
+
# This class represents the installer status
|
29
|
+
class PackageCallbacks
|
30
|
+
class << self
|
31
|
+
def setup(pkg_count, progress)
|
32
|
+
new(pkg_count, progress).setup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(pkg_count, progress)
|
37
|
+
@total = pkg_count
|
38
|
+
@installed = 0
|
39
|
+
@progress = progress
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup
|
43
|
+
Yast::Pkg.CallbackDonePackage(
|
44
|
+
fun_ref(method(:package_installed), "string (integer, string)")
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @return [DInstaller::Progress]
|
51
|
+
attr_reader :progress
|
52
|
+
|
53
|
+
def fun_ref(method, signature)
|
54
|
+
Yast::FunRef.new(method, signature)
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: error handling
|
58
|
+
def package_installed(_error, _reason)
|
59
|
+
@installed += 1
|
60
|
+
progress.step(msg)
|
61
|
+
|
62
|
+
""
|
63
|
+
end
|
64
|
+
|
65
|
+
def msg
|
66
|
+
"Installing packages (#{@total - @installed} remains)"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
module DInstaller
|
23
|
+
# Class to manage progress
|
24
|
+
#
|
25
|
+
# It allows to configure callbacks to be called on each step and also when the progress finishes.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
#
|
29
|
+
# progress = Progress.new(3) # 3 steps
|
30
|
+
# progress.on_change { puts progress.message } # configures callbacks
|
31
|
+
# progress.on_finish { puts "finished" } # configures callbacks
|
32
|
+
#
|
33
|
+
# progress.step("Doing step1") { step1 } # calls on_change callbacks and executes step1
|
34
|
+
# progress.step("Doing step2") { step2 } # calls on_change callbacks and executes step2
|
35
|
+
#
|
36
|
+
# progress.current_step #=> <Step>
|
37
|
+
# progress.current_step.id #=> 2
|
38
|
+
# progress.current_step.description #=> "Doing step2"
|
39
|
+
#
|
40
|
+
# progress.finished? #=> false
|
41
|
+
|
42
|
+
# progress.step("Doing step3") do # calls on_change callbacks, executes the given
|
43
|
+
# progress.current_step.description # block and calls on_finish callbacks
|
44
|
+
# end #=> "Doing step3"
|
45
|
+
#
|
46
|
+
# progress.finished? #=> true
|
47
|
+
# progress.current_step #=> nil
|
48
|
+
class Progress
|
49
|
+
# Step of the progress
|
50
|
+
class Step
|
51
|
+
# Id of the step
|
52
|
+
#
|
53
|
+
# @return [Integer]
|
54
|
+
attr_reader :id
|
55
|
+
|
56
|
+
# Description of the step
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
attr_reader :description
|
60
|
+
|
61
|
+
# Constructor
|
62
|
+
#
|
63
|
+
# @param id [Integer]
|
64
|
+
# @param description [String]
|
65
|
+
def initialize(id, description)
|
66
|
+
@id = id
|
67
|
+
@description = description
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Total number of steps
|
72
|
+
#
|
73
|
+
# @return [Integer]
|
74
|
+
attr_reader :total_steps
|
75
|
+
|
76
|
+
# Constructor
|
77
|
+
#
|
78
|
+
# @param toal_steps [Integer] total number of steps
|
79
|
+
def initialize(total_steps)
|
80
|
+
@total_steps = total_steps
|
81
|
+
@current_step = nil
|
82
|
+
@counter = 0
|
83
|
+
@finished = false
|
84
|
+
@on_change_callbacks = []
|
85
|
+
@on_finish_callbacks = []
|
86
|
+
end
|
87
|
+
|
88
|
+
# Current progress step, if any
|
89
|
+
#
|
90
|
+
# @return [Step, nil] nil if the progress is already finished or not stated yet.
|
91
|
+
def current_step
|
92
|
+
return nil if finished?
|
93
|
+
|
94
|
+
@current_step
|
95
|
+
end
|
96
|
+
|
97
|
+
# Runs a progress step
|
98
|
+
#
|
99
|
+
# It calls the `on_change` callbacks and then runs the given block, if any. It also calls
|
100
|
+
# `on_finish` callbacks after the last step.
|
101
|
+
#
|
102
|
+
# @param description [String] description of the step
|
103
|
+
# @param block [Proc]
|
104
|
+
#
|
105
|
+
# @return [Object, nil] result of the given block or nil if no block is given
|
106
|
+
def step(description, &block)
|
107
|
+
return if finished?
|
108
|
+
|
109
|
+
@counter += 1
|
110
|
+
@current_step = Step.new(@counter, description)
|
111
|
+
@on_change_callbacks.each(&:call)
|
112
|
+
|
113
|
+
result = block_given? ? block.call : nil
|
114
|
+
|
115
|
+
finish if @counter == total_steps
|
116
|
+
|
117
|
+
result
|
118
|
+
end
|
119
|
+
|
120
|
+
# Whether the last step was already done
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
123
|
+
def finished?
|
124
|
+
total_steps == 0 || @finished
|
125
|
+
end
|
126
|
+
|
127
|
+
# Finishes the progress and runs the callbacks
|
128
|
+
#
|
129
|
+
# This method can be called to force the progress to finish before of running all the steps.
|
130
|
+
def finish
|
131
|
+
@finished = true
|
132
|
+
@on_finish_callbacks.each(&:call)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Adds a callback to be called when progress changes
|
136
|
+
#
|
137
|
+
# @param block [Proc]
|
138
|
+
def on_change(&block)
|
139
|
+
@on_change_callbacks << block
|
140
|
+
end
|
141
|
+
|
142
|
+
# Adds a callback to be called when progress finishes
|
143
|
+
#
|
144
|
+
# @param block [Proc]
|
145
|
+
def on_finish(&block)
|
146
|
+
@on_finish_callbacks << block
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
module DInstaller
|
23
|
+
# This class represents a question
|
24
|
+
#
|
25
|
+
# Questions are used when some information needs to be asked. For example, a question could be
|
26
|
+
# created for asking whether to continue or not when an error is detected.
|
27
|
+
#
|
28
|
+
# Questions are managed by a questions manager, see {QuestionsManager}.
|
29
|
+
class Question
|
30
|
+
# Each question is identified by an unique id
|
31
|
+
#
|
32
|
+
# @return [Integer]
|
33
|
+
attr_reader :id
|
34
|
+
|
35
|
+
# Text of the question
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
attr_reader :text
|
39
|
+
|
40
|
+
# Options the question offers
|
41
|
+
#
|
42
|
+
# The question must be answered with one of that options.
|
43
|
+
#
|
44
|
+
# @return [Array<Symbol>]
|
45
|
+
attr_reader :options
|
46
|
+
|
47
|
+
# Default option to use as answer
|
48
|
+
#
|
49
|
+
# @return [Symbol, nil]
|
50
|
+
attr_reader :default_option
|
51
|
+
|
52
|
+
# Answer of the question
|
53
|
+
#
|
54
|
+
# @return [Symbol, nil] nil if the question is not answered yet
|
55
|
+
attr_reader :answer
|
56
|
+
|
57
|
+
def initialize(text, options: [], default_option: nil)
|
58
|
+
@id = IdGenerator.next
|
59
|
+
@text = text
|
60
|
+
@options = options
|
61
|
+
@default_option = default_option
|
62
|
+
end
|
63
|
+
|
64
|
+
# Answers the question with an option
|
65
|
+
#
|
66
|
+
# @raise [ArgumentError] if the given value is not a valid answer.
|
67
|
+
#
|
68
|
+
# @param value [Symbol]
|
69
|
+
def answer=(value)
|
70
|
+
raise ArgumentError, "Invalid answer. Options: #{options}" unless valid_answer?(value)
|
71
|
+
|
72
|
+
@answer = value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Whether the question is already answered
|
76
|
+
#
|
77
|
+
# @return [Boolean]
|
78
|
+
def answered?
|
79
|
+
!answer.nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Checks whether the given value is a valid answer
|
85
|
+
#
|
86
|
+
# @param value [Symbol]
|
87
|
+
# @return [Boolean]
|
88
|
+
def valid_answer?(value)
|
89
|
+
options.include?(value)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Helper class for generating unique ids
|
93
|
+
class IdGenerator
|
94
|
+
# Generates the next id to be used
|
95
|
+
#
|
96
|
+
# @return [Integer]
|
97
|
+
def self.next
|
98
|
+
@last_id ||= 0
|
99
|
+
@last_id += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|