d-installer 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +29 -0
  3. data/Gemfile.lock +75 -0
  4. data/bin/d-installer +74 -0
  5. data/etc/d-installer.yaml +284 -0
  6. data/lib/dinstaller/can_ask_question.rb +48 -0
  7. data/lib/dinstaller/cmdline_args.rb +80 -0
  8. data/lib/dinstaller/cockpit_manager.rb +176 -0
  9. data/lib/dinstaller/config.rb +128 -0
  10. data/lib/dinstaller/config_reader.rb +164 -0
  11. data/lib/dinstaller/dbus/base_object.rb +58 -0
  12. data/lib/dinstaller/dbus/clients/base.rb +71 -0
  13. data/lib/dinstaller/dbus/clients/language.rb +86 -0
  14. data/lib/dinstaller/dbus/clients/manager.rb +76 -0
  15. data/lib/dinstaller/dbus/clients/software.rb +185 -0
  16. data/lib/dinstaller/dbus/clients/users.rb +112 -0
  17. data/lib/dinstaller/dbus/clients/with_progress.rb +56 -0
  18. data/lib/dinstaller/dbus/clients/with_service_status.rb +75 -0
  19. data/lib/dinstaller/dbus/clients.rb +34 -0
  20. data/lib/dinstaller/dbus/interfaces/progress.rb +113 -0
  21. data/lib/dinstaller/dbus/interfaces/service_status.rb +89 -0
  22. data/lib/dinstaller/dbus/language.rb +93 -0
  23. data/lib/dinstaller/dbus/language_service.rb +92 -0
  24. data/lib/dinstaller/dbus/manager.rb +147 -0
  25. data/lib/dinstaller/dbus/manager_service.rb +132 -0
  26. data/lib/dinstaller/dbus/question.rb +176 -0
  27. data/lib/dinstaller/dbus/questions.rb +124 -0
  28. data/lib/dinstaller/dbus/service_runner.rb +97 -0
  29. data/lib/dinstaller/dbus/service_status.rb +87 -0
  30. data/lib/dinstaller/dbus/software/manager.rb +131 -0
  31. data/lib/dinstaller/dbus/software/proposal.rb +82 -0
  32. data/lib/dinstaller/dbus/software.rb +31 -0
  33. data/lib/dinstaller/dbus/software_service.rb +86 -0
  34. data/lib/dinstaller/dbus/storage/proposal.rb +170 -0
  35. data/lib/dinstaller/dbus/storage.rb +30 -0
  36. data/lib/dinstaller/dbus/users.rb +132 -0
  37. data/lib/dinstaller/dbus/users_service.rb +92 -0
  38. data/lib/dinstaller/dbus/with_service_status.rb +48 -0
  39. data/lib/dinstaller/dbus/y2dir/manager/modules/Package.rb +51 -0
  40. data/lib/dinstaller/dbus/y2dir/manager/modules/PackagesProposal.rb +62 -0
  41. data/lib/dinstaller/dbus/y2dir/modules/Autologin.rb +214 -0
  42. data/lib/dinstaller/dbus/y2dir/software/modules/SpaceCalculation.rb +44 -0
  43. data/lib/dinstaller/dbus.rb +11 -0
  44. data/lib/dinstaller/errors.rb +28 -0
  45. data/lib/dinstaller/installation_phase.rb +106 -0
  46. data/lib/dinstaller/language.rb +73 -0
  47. data/lib/dinstaller/luks_activation_question.rb +92 -0
  48. data/lib/dinstaller/manager.rb +261 -0
  49. data/lib/dinstaller/network.rb +53 -0
  50. data/lib/dinstaller/package_callbacks.rb +69 -0
  51. data/lib/dinstaller/progress.rb +149 -0
  52. data/lib/dinstaller/question.rb +103 -0
  53. data/lib/dinstaller/questions_manager.rb +145 -0
  54. data/lib/dinstaller/security.rb +96 -0
  55. data/lib/dinstaller/service_status_recorder.rb +58 -0
  56. data/lib/dinstaller/software.rb +232 -0
  57. data/lib/dinstaller/storage/actions.rb +90 -0
  58. data/lib/dinstaller/storage/callbacks/activate.rb +93 -0
  59. data/lib/dinstaller/storage/callbacks/activate_luks.rb +93 -0
  60. data/lib/dinstaller/storage/callbacks/activate_multipath.rb +77 -0
  61. data/lib/dinstaller/storage/callbacks.rb +30 -0
  62. data/lib/dinstaller/storage/manager.rb +104 -0
  63. data/lib/dinstaller/storage/proposal.rb +197 -0
  64. data/lib/dinstaller/storage.rb +30 -0
  65. data/lib/dinstaller/users.rb +156 -0
  66. data/lib/dinstaller/with_progress.rb +63 -0
  67. data/lib/dinstaller.rb +5 -0
  68. data/share/dbus.conf +38 -0
  69. data/share/dbus.service +5 -0
  70. data/share/systemd.service +14 -0
  71. metadata +295 -0
@@ -0,0 +1,145 @@
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
+ # Manager for questions
24
+ #
25
+ # Allows to configure callbacks with the actions to perform when adding, deleting or waiting for
26
+ # questions.
27
+ class QuestionsManager
28
+ # @return [Array<Question>]
29
+ attr_reader :questions
30
+
31
+ # Constructor
32
+ #
33
+ # @params logger [Logger]
34
+ def initialize(logger)
35
+ @logger = logger
36
+ @questions = []
37
+ @on_add_callbacks = []
38
+ @on_delete_callbacks = []
39
+ @on_wait_callbacks = []
40
+ end
41
+
42
+ # Adds a question
43
+ #
44
+ # Callbacks are called after adding the question, see {#on_add}.
45
+ #
46
+ # @yieldparam question [Question] added question
47
+ #
48
+ # @param question [Question]
49
+ # @return [Boolean] whether the question was added
50
+ def add(question)
51
+ return false if include?(question)
52
+
53
+ questions << question
54
+ on_add_callbacks.each { |c| c.call(question) }
55
+
56
+ true
57
+ end
58
+
59
+ # Deletes the given question
60
+ #
61
+ # Callbacks are called after deleting the question, see {#on_delete}.
62
+ #
63
+ # @yieldparam question [Question] deleted question
64
+ #
65
+ # @param question [Question]
66
+ # @return [Boolean] whether the question was deleted
67
+ def delete(question)
68
+ return false unless include?(question)
69
+
70
+ questions.delete(question)
71
+ on_delete_callbacks.each { |c| c.call(question) }
72
+
73
+ true
74
+ end
75
+
76
+ # Waits until all questions are answered
77
+ #
78
+ # Callbacks are periodically called while waiting, see {#on_wait}.
79
+ def wait
80
+ logger.info "Waiting for questions to be answered"
81
+
82
+ loop do
83
+ on_wait_callbacks.each(&:call)
84
+ sleep(0.1)
85
+ break if questions_answered?
86
+ end
87
+ end
88
+
89
+ # Registers a callback to be called when a new question is added
90
+ #
91
+ # @param block [Proc]
92
+ def on_add(&block)
93
+ on_add_callbacks << block
94
+ end
95
+
96
+ # Registers a callback to be called when a question is deleted
97
+ #
98
+ # @param block [Proc]
99
+ def on_delete(&block)
100
+ on_delete_callbacks << block
101
+ end
102
+
103
+ # Registers a callback to be called while waiting for questions be answered
104
+ #
105
+ # @param block [Proc]
106
+ def on_wait(&block)
107
+ on_wait_callbacks << block
108
+ end
109
+
110
+ private
111
+
112
+ # @return [Logger]
113
+ attr_reader :logger
114
+
115
+ # Callbacks to be called when the a new question is added
116
+ #
117
+ # @return [Array<Proc>]
118
+ attr_reader :on_add_callbacks
119
+
120
+ # Callbacks to be called when the a question is deleted
121
+ #
122
+ # @return [Array<Proc>]
123
+ attr_reader :on_delete_callbacks
124
+
125
+ # Callbacks to be called when waiting for answers
126
+ #
127
+ # @return [Array<Proc>]
128
+ attr_reader :on_wait_callbacks
129
+
130
+ # Whether a question with the same id as the given question is already in the list of questions
131
+ #
132
+ # @param question [Question]
133
+ # @return [Boolean]
134
+ def include?(question)
135
+ questions.any? { |q| q.id == question.id }
136
+ end
137
+
138
+ # Whether all questions are already answered
139
+ #
140
+ # @return [Boolean]
141
+ def questions_answered?
142
+ questions.all?(&:answered?)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,96 @@
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 "y2security/lsm"
24
+ require "yast2/execute"
25
+
26
+ require "dinstaller/config"
27
+
28
+ # FIXME: monkey patching of security config to not read control.xml and
29
+ # instead use DIinstaller::Config
30
+ # TODO: add ability to set product features in LSM::Base
31
+ module Y2Security
32
+ module LSM
33
+ # modified LSM Base class to use dinstaller config
34
+ class Base
35
+ def product_feature_settings
36
+ return @product_feature_settings unless @product_feature_settings.nil?
37
+
38
+ value = ::DInstaller::Config.current.data["security"]["available_lsms"][id.to_s]
39
+ res = if value
40
+ {
41
+ selectable: true,
42
+ configurable: true,
43
+ patterns: (value["patterns"] || []).join(" "),
44
+ mode: value["policy"]
45
+ }
46
+ else
47
+ {
48
+ selectable: false,
49
+ configurable: false,
50
+ patterns: "",
51
+ mode: nil
52
+ }
53
+ end
54
+ @product_feature_settings = res
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ module DInstaller
61
+ # Backend class between dbus service and yast code
62
+ class Security
63
+ # @return [Logger]
64
+ attr_reader :logger
65
+
66
+ def initialize(logger, config)
67
+ @config = config
68
+ @logger = logger
69
+ end
70
+
71
+ def write
72
+ lsm_config.save
73
+ end
74
+
75
+ def probe
76
+ selected_lsm = config.data["security"]["lsm"]
77
+ lsm_config.select(selected_lsm)
78
+
79
+ patterns = if selected_lsm.nil?
80
+ []
81
+ else
82
+ lsm_data = config.data["security"]["available_lsms"][selected_lsm]
83
+ lsm_data["patterns"]
84
+ end
85
+ Yast::PackagesProposal.SetResolvables("LSM", :pattern, patterns)
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :config
91
+
92
+ def lsm_config
93
+ Y2Security::LSM::Config.instance
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,58 @@
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 "dinstaller/dbus/service_status"
23
+
24
+ module DInstaller
25
+ # Allows to record the status of services and to register callbacks to be called when a service
26
+ # status changes its value
27
+ class ServiceStatusRecorder
28
+ def initialize
29
+ @statuses = {}
30
+ @on_service_status_change_callbacks = []
31
+ end
32
+
33
+ # Saves the status of the given service and runs the callbacks if the status has changed
34
+ #
35
+ # @param service_name [String]
36
+ # @param status [String] see {ServiceStatus}
37
+ def save(service_name, status)
38
+ return if @statuses[service_name] == status
39
+
40
+ @statuses[service_name] = status
41
+ @on_service_status_change_callbacks.each(&:call)
42
+ end
43
+
44
+ # Name of services with busy status
45
+ #
46
+ # @return [Array<String>]
47
+ def busy_services
48
+ @statuses.select { |_service, status| status == DBus::ServiceStatus::BUSY }.keys
49
+ end
50
+
51
+ # Registers callbacks to be called when saving a new status
52
+ #
53
+ # @param block [Proc]
54
+ def on_service_status_change(&block)
55
+ @on_service_status_change_callbacks << block
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,232 @@
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
+ require "fileutils"
24
+ require "dinstaller/package_callbacks"
25
+ require "dinstaller/config"
26
+ require "dinstaller/with_progress"
27
+ require "y2packager/product"
28
+
29
+ Yast.import "PackageInstallation"
30
+ Yast.import "Pkg"
31
+ Yast.import "Stage"
32
+
33
+ # YaST specific code lives under this namespace
34
+ module DInstaller
35
+ # This class is responsible for software handling
36
+ class Software
37
+ include WithProgress
38
+
39
+ GPG_KEYS_GLOB = "/usr/lib/rpm/gnupg/keys/gpg-*"
40
+ private_constant :GPG_KEYS_GLOB
41
+
42
+ attr_reader :product
43
+
44
+ DEFAULT_LANGUAGES = ["en_US"].freeze
45
+ private_constant :DEFAULT_LANGUAGES
46
+
47
+ attr_accessor :languages
48
+
49
+ # FIXME: what about defining a Product class?
50
+ # @return [Array<Array<String,Hash>>] An array containing the product ID and
51
+ # additional information in a hash
52
+ attr_reader :products
53
+
54
+ def initialize(config, logger)
55
+ @config = config
56
+ @logger = logger
57
+ @languages = DEFAULT_LANGUAGES
58
+ @products = @config.data["products"]
59
+ if @config.multi_product?
60
+ @product = nil
61
+ else
62
+ @product = @products.keys.first # use the available product as default
63
+ @config.pick_product(@product)
64
+ end
65
+ end
66
+
67
+ def select_product(name)
68
+ return if name == @product
69
+ raise ArgumentError unless @products[name]
70
+
71
+ @config.pick_product(name)
72
+ @product = name
73
+ end
74
+
75
+ def probe
76
+ logger.info "Probing software"
77
+
78
+ store_original_repos
79
+ Yast::Pkg.SetSolverFlags("ignoreAlreadyRecommended" => false, "onlyRequires" => true)
80
+
81
+ # as we use liveDVD with normal like ENV, lets temporary switch to normal to use its repos
82
+ Yast::Stage.Set("normal")
83
+
84
+ start_progress(3)
85
+ progress.step("Initialize target repositories") { initialize_target_repos }
86
+ progress.step("Initialize sources") { add_base_repo }
87
+ progress.step("Making the initial proposal") do
88
+ proposal = Yast::Packages.Proposal(force_reset = true, reinit = false, _simple = true)
89
+ logger.info "proposal #{proposal["raw_proposal"]}"
90
+ end
91
+
92
+ Yast::Stage.Set("initial")
93
+ end
94
+
95
+ def initialize_target_repos
96
+ Yast::Pkg.TargetInitialize("/")
97
+ import_gpg_keys
98
+ end
99
+
100
+ def propose
101
+ Yast::Pkg.TargetFinish # ensure that previous target is closed
102
+ Yast::Pkg.TargetInitialize(Yast::Installation.destdir)
103
+ Yast::Pkg.TargetLoad
104
+ Yast::Pkg.SetAdditionalLocales(languages)
105
+ select_base_product(@config.data["software"]["base_product"])
106
+
107
+ add_resolvables
108
+ proposal = Yast::Packages.Proposal(force_reset = false, reinit = false, _simple = true)
109
+ logger.info "proposal #{proposal["raw_proposal"]}"
110
+
111
+ solve_dependencies
112
+
113
+ # do not return proposal hash, so intentional nil here
114
+ nil
115
+ end
116
+
117
+ def install
118
+ start_progress(count_packages)
119
+ PackageCallbacks.setup(count_packages, progress)
120
+
121
+ # TODO: error handling
122
+ commit_result = Yast::PackageInstallation.Commit({})
123
+
124
+ if commit_result.nil? || commit_result.empty?
125
+ logger.error("Commit failed")
126
+ raise Yast::Pkg.LastError
127
+ end
128
+
129
+ logger.info "Commit result #{commit_result}"
130
+ end
131
+
132
+ # Writes the repositories information to the installed system
133
+ def finish
134
+ start_progress(2)
135
+ progress.step("Writing repositories to the target system") do
136
+ Yast::Pkg.SourceSaveAll
137
+ Yast::Pkg.TargetFinish
138
+ Yast::Pkg.SourceCacheCopyTo(Yast::Installation.destdir)
139
+ end
140
+ progress.step("Restoring original repositories") { restore_original_repos }
141
+ end
142
+
143
+ # Determine whether the given tag is provided by the selected packages
144
+ #
145
+ # @param tag [String] Tag to search for (package names, requires/provides, or file
146
+ # names)
147
+ # @return [Boolean] true if it is provided; false otherwise
148
+ def provision_selected?(tag)
149
+ Yast::Pkg.IsSelected(tag) || Yast::Pkg.IsProvided(tag)
150
+ end
151
+
152
+ private
153
+
154
+ # adds resolvables from yaml config for given product
155
+ def add_resolvables
156
+ mandatory_patterns = @config.data["software"]["mandatory_patterns"] || []
157
+ Yast::PackagesProposal.SetResolvables("d-installer", :pattern, mandatory_patterns)
158
+
159
+ optional_patterns = @config.data["software"]["optional_patterns"] || []
160
+ Yast::PackagesProposal.SetResolvables("d-installer", :pattern, optional_patterns,
161
+ optional: true)
162
+ end
163
+
164
+ # call solver to satisfy dependency or log error
165
+ def solve_dependencies
166
+ res = Yast::Pkg.PkgSolve(unused = true)
167
+ logger.info "solver run #{res.inspect}"
168
+
169
+ return if res
170
+
171
+ logger.error "Solver failed: #{Yast::Pkg.LastError}"
172
+ logger.error "Details: #{Yast::Pkg.LastErrorDetails}"
173
+ logger.error "Solving issues: #{Yast::Pkg.PkgSolveErrors}"
174
+ end
175
+
176
+ # @return [Logger]
177
+ attr_reader :logger
178
+
179
+ def count_packages
180
+ Yast::Pkg.PkgMediaCount.reduce(0) { |sum, res| sum + res.reduce(0, :+) }
181
+ end
182
+
183
+ def import_gpg_keys
184
+ gpg_keys = Dir.glob(GPG_KEYS_GLOB).map(&:to_s)
185
+ logger.info "Importing GPG keys: #{gpg_keys}"
186
+ gpg_keys.each do |path|
187
+ Yast::Pkg.ImportGPGKey(path, true)
188
+ end
189
+ end
190
+
191
+ def add_base_repo
192
+ @config.data["software"]["installation_repositories"].each do |repo|
193
+ Yast::Pkg.SourceCreate(repo, "/") # TODO: having that dir also in config?
194
+ end
195
+
196
+ Yast::Pkg.SourceSaveAll
197
+ end
198
+
199
+ def select_base_product(name)
200
+ base_product = Y2Packager::Product.available_base_products.find do |product|
201
+ product.name == name
202
+ end
203
+ logger.info "Base product to select: #{base_product&.name}"
204
+ base_product&.select
205
+ end
206
+
207
+ REPOS_BACKUP = "/etc/zypp/repos.d.dinstaller.backup"
208
+ private_constant :REPOS_BACKUP
209
+
210
+ REPOS_DIR = "/etc/zypp/repos.d"
211
+ private_constant :REPOS_DIR
212
+
213
+ # ensure that repos backup is there and repos.d is empty
214
+ def store_original_repos
215
+ # Backup was already created, so just remove all repos
216
+ if File.directory?(REPOS_BACKUP)
217
+ logger.info "removing #{REPOS_DIR}"
218
+ FileUtils.rm_rf(REPOS_DIR)
219
+ else # move repos to backup
220
+ logger.info "moving #{REPOS_DIR} to #{REPOS_BACKUP}"
221
+ FileUtils.mv(REPOS_DIR, REPOS_BACKUP)
222
+ end
223
+ end
224
+
225
+ def restore_original_repos
226
+ logger.info "removing #{REPOS_DIR}"
227
+ FileUtils.rm_rf(REPOS_DIR)
228
+ logger.info "moving #{REPOS_BACKUP} to #{REPOS_DIR}"
229
+ FileUtils.mv(REPOS_BACKUP, REPOS_DIR)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,90 @@
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 "y2storage/storage_manager"
23
+
24
+ module DInstaller
25
+ module Storage
26
+ # Backend class to get the list of actions over the storage devices
27
+ class Actions
28
+ # @param logger [Logger]
29
+ def initialize(logger)
30
+ @logger = logger
31
+ end
32
+
33
+ # All actions properly sorted
34
+ #
35
+ # @return [Array<Y2Storage::CompoundAction>]
36
+ def all
37
+ main_actions + subvolume_actions
38
+ end
39
+
40
+ private
41
+
42
+ # @param [Logger]
43
+ attr_reader :logger
44
+
45
+ # Sorted main actions (everything except subvolume actions)
46
+ #
47
+ # @return [Array<Y2Storage::CompoundAction>]
48
+ def main_actions
49
+ actions = self.actions.reject { |a| subvol_action?(a) }
50
+ sort_actions(actions)
51
+ end
52
+
53
+ # Sorted subvolume actions
54
+ #
55
+ # @return [Array<Y2Storage::CompoundAction>]
56
+ def subvolume_actions
57
+ actions = self.actions.select { |a| subvol_action?(a) }
58
+ sort_actions(actions)
59
+ end
60
+
61
+ # All actions, without sorting
62
+ #
63
+ # @return [Array<Y2Storage::CompoundAction>]
64
+ def actions
65
+ actiongraph = Y2Storage::StorageManager.instance.staging.actiongraph
66
+
67
+ return [] unless actiongraph
68
+
69
+ actiongraph.compound_actions
70
+ end
71
+
72
+ # Sorts actions, placing destructive actions at the end
73
+ #
74
+ # @param actions [Array<Y2Storage::CompoundAction>]
75
+ # @return [Array<Y2Storage::CompoundAction>]
76
+ def sort_actions(actions)
77
+ delete, other = actions.partition(&:delete?)
78
+ delete.concat(other)
79
+ end
80
+
81
+ # Whether the action acts over a Btrfs subvolume
82
+ #
83
+ # @param action [Y2Storage::CompoundAction]
84
+ # @return [Boolean]
85
+ def subvol_action?(action)
86
+ action.device_is?(:btrfs_subvolume)
87
+ end
88
+ end
89
+ end
90
+ end