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.
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,176 @@
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 "yast2/systemd/service"
24
+ require "cfa/base_model"
25
+ require "transfer/file_from_url"
26
+ require "fileutils"
27
+
28
+ Yast.import "URL"
29
+
30
+ module DInstaller
31
+ # Cockpit configuration file representation
32
+ #
33
+ # @example Set the AllowUnencrypted option
34
+ # file = CockpitConfig.new
35
+ # file.load
36
+ # file.web_service["AllowUnencrypted"] = "true"
37
+ # file.save
38
+ class CockpitConfig < CFA::BaseModel
39
+ # Constructor
40
+ #
41
+ # @param path [String] File path
42
+ # @param file_handler [.read, .write] Object to read/write the file.
43
+ def initialize(path: DEFAULT_PATH, file_handler: nil)
44
+ super(CFA::AugeasParser.new("puppet.lns"), path, file_handler: file_handler)
45
+ end
46
+
47
+ # Returns the augeas tree for the "WebService" section
48
+ #
49
+ # If the given section does not exist, it returns an empty one
50
+ #
51
+ # @param name [String] section name
52
+ # @return [AugeasTree]
53
+ def web_service
54
+ data["WebService"] ||= CFA::AugeasTree.new
55
+ end
56
+ end
57
+
58
+ # Handles the Cockpit service
59
+ #
60
+ # This API offers an API to adjust Cockpit configuration and restart/reload the process. At this
61
+ # point, just a few options are allowed (@see #setup).
62
+ class CockpitManager
63
+ include Yast::Logger
64
+ include Yast::Transfer::FileFromUrl
65
+ include Yast::I18n
66
+
67
+ # Directory to store Cockpit certificates
68
+ WS_CERTS_DIR = "/etc/cockpit/ws-certs.d"
69
+ COCKPIT_SERVICE = "cockpit"
70
+ COCKPIT_CONF_PATH = "/etc/cockpit/cockpit.conf"
71
+
72
+ def initialize(logger, prefix: "/")
73
+ @prefix = prefix
74
+ @logger = logger
75
+ end
76
+
77
+ # Adjust Cockpit configuration and restart the process if needed
78
+ #
79
+ # If all arguments are nil, the configuration is not modified and the process is not restarted.
80
+ #
81
+ # @param config [Hash]
82
+ # @option ssl [Boolean,nil] SSL is enabled
83
+ # @option ssl_cert [String,nil] SSL/TLS certificate URL
84
+ # @option ssl_key [String,nil] SSL/TLS key URL
85
+ def setup(options)
86
+ return if options.values.all?(&:nil?)
87
+
88
+ enable_ssl(options["ssl"]) unless options["ssl"].nil?
89
+ if options["ssl_cert"]
90
+ copy_ssl_cert(options["ssl_cert"])
91
+ copy_ssl_key(options["ssl_key"]) unless options["ssl_key"].nil?
92
+ clear_self_signed_cert
93
+ end
94
+
95
+ restart_cockpit
96
+ end
97
+
98
+ private
99
+
100
+ attr_reader :prefix
101
+
102
+ # @return [Logger]
103
+ attr_reader :logger
104
+
105
+ # Enable/Disable SSL
106
+ #
107
+ # @param enabled [Boolean] Whether to enable or disable SSL
108
+ def enable_ssl(enabled)
109
+ path = File.join(prefix, COCKPIT_CONF_PATH)
110
+ config = CockpitConfig.new(path: path)
111
+ config.load if File.readable?(path)
112
+ config.web_service["AllowUnencrypted"] = (!enabled).to_s
113
+ config.save
114
+ end
115
+
116
+ # Copy the SSL certificate to Cockpit's certificates directory
117
+ #
118
+ # The certificate is renamed as `0-d-installer.cert`.
119
+ #
120
+ # @param location [String] Certificate location
121
+ def copy_ssl_cert(location)
122
+ logger.info "Retrieving SSL certificate from #{location}"
123
+ copy_file(location, File.join(prefix, WS_CERTS_DIR, "0-d-installer.cert"))
124
+ end
125
+
126
+ # Copy the SSL certificate key to Cockpit's certificates directory
127
+ #
128
+ # The certificate is renamed as `0-d-installer.key`.
129
+ #
130
+ # @param location [String] Certificate key location
131
+ def copy_ssl_key(location)
132
+ logger.info "Retrieving SSL key from #{location}"
133
+ copy_file(location, File.join(prefix, WS_CERTS_DIR, "0-d-installer.key"))
134
+ end
135
+
136
+ # Remove Cockpit's self signed certificates if they exist
137
+ def clear_self_signed_cert
138
+ self_signed = Dir[File.join(prefix, WS_CERTS_DIR, "0-self-signed.*")]
139
+ ::FileUtils.rm(self_signed)
140
+ end
141
+
142
+ # Copy a file from a potentially remote location
143
+ #
144
+ # @param location [String] File location. It might be an URL-like string (e.g.,
145
+ # "http://example.net/example.cert").
146
+ # @param target [String] Path to copy the file to.
147
+ # @return [Boolean] Whether the file was sucessfully copied or not
148
+ def copy_file(location, target)
149
+ url = Yast::URL.Parse(location)
150
+
151
+ res = get_file_from_url(
152
+ scheme: url["scheme"],
153
+ host: url["host"],
154
+ urlpath: url["path"],
155
+ localfile: target,
156
+ urltok: url,
157
+ destdir: "/"
158
+ )
159
+ # TODO: exception?
160
+ logger.error "script #{location} could not be retrieved" unless res
161
+ res
162
+ end
163
+
164
+ # Restart the Cockpit service
165
+ def restart_cockpit
166
+ logger.info "Restarting Cockpit"
167
+ service = Yast2::Systemd::Service.find(COCKPIT_SERVICE)
168
+ if service.nil?
169
+ logger.error "Could not found #{COCKPIT_SERVICE} service"
170
+ return
171
+ end
172
+
173
+ service.restart
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,128 @@
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 "yaml"
23
+ require "dinstaller/config_reader"
24
+
25
+ module DInstaller
26
+ # Class responsible for getting current configuration.
27
+ # It is smarter then just plain yaml reader as it also evaluates
28
+ # conditions in it, so it is result of all conditions in file.
29
+ # This also means that config needs to be re-evaluated if conditions
30
+ # data change, like if user pick different distro to install.
31
+ class Config
32
+ # @return [Hash] configuration data
33
+ attr_accessor :pure_data
34
+
35
+ class << self
36
+ attr_accessor :current, :base
37
+
38
+ # Loads base and current config reading configuration from the system
39
+ def load(logger = Logger.new($stdout))
40
+ @base = ConfigReader.new(logger: logger).config
41
+ @current = @base&.copy
42
+ end
43
+
44
+ # It resets the configuration internal state
45
+ def reset
46
+ @base = nil
47
+ @current = nil
48
+ end
49
+
50
+ # Load the configuration from a given file
51
+ #
52
+ # @param path [String|Pathname] File path
53
+ def from_file(path)
54
+ new(YAML.safe_load(File.read(path.to_s)))
55
+ end
56
+ end
57
+
58
+ # Constructor
59
+ #
60
+ # @param config_data [Hash] configuration data
61
+ def initialize(config_data = nil)
62
+ @pure_data = config_data
63
+ end
64
+
65
+ # parse loaded yaml file, so it properly applies conditions
66
+ # with default options it load file without conditions
67
+ def parse_file(_arch = nil, _distro = nil)
68
+ # TODO: move to internal only. public one should be something
69
+ # like evaluate or just setter for distro and arch
70
+ # logger.info "parse file with #{arch} and #{distro}"
71
+ # TODO: do real evaluation of conditions
72
+ data
73
+ end
74
+
75
+ def data
76
+ return @data if @data
77
+
78
+ @data = @pure_data.dup || {}
79
+ pick_product(@data["products"].keys.first) if @data["products"]
80
+ @data
81
+ end
82
+
83
+ def pick_product(product)
84
+ data.merge!(data[product])
85
+ end
86
+
87
+ # Whether there are more than one product
88
+ #
89
+ # @return [Boolean] false if there is only one product; true otherwise
90
+ def multi_product?
91
+ data["products"].size > 1
92
+ end
93
+
94
+ # Returns a copy of this Object
95
+ #
96
+ # @return [Config]
97
+ def copy
98
+ Marshal.load(Marshal.dump(self))
99
+ end
100
+
101
+ # Returns a new {Config} with the merge of the given ones
102
+ #
103
+ # @params config [Config, Hash]
104
+ # @return [Config] new Configuration with the merge of the given ones
105
+ def merge(config)
106
+ Config.new(simple_merge(data, config.data))
107
+ end
108
+
109
+ private
110
+
111
+ # Simple deep merge
112
+ #
113
+ # @param a_hash [Hash] Default values
114
+ # @param another_hash [Hash] Pillar data
115
+ # @return [Hash]
116
+ def simple_merge(a_hash, another_hash)
117
+ a_hash.reduce({}) do |all, (k, v)|
118
+ next all.merge(k => v) if another_hash[k].nil?
119
+
120
+ if v.is_a?(Hash)
121
+ all.merge(k => simple_merge(a_hash[k], another_hash[k]))
122
+ else
123
+ all.merge(k => another_hash[k])
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,164 @@
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 "yaml"
24
+ require "logger"
25
+ require "dinstaller/config"
26
+ require "dinstaller/cmdline_args"
27
+ require "transfer/file_from_url"
28
+
29
+ Yast.import "URL"
30
+ Yast.import "Directory"
31
+
32
+ module DInstaller
33
+ # This class is responsible for reading DInstaller configuration from different locations
34
+ # including kernel cmdline options
35
+ class ConfigReader
36
+ include Yast::Transfer::FileFromUrl
37
+ include Yast::I18n
38
+
39
+ # Default DInstaller configuration which should define all the possible values
40
+ SYSTEM_PATH = "/etc/d-installer.yaml"
41
+ GIT_PATH = File.expand_path("#{__dir__}/../../etc/d-installer.yaml")
42
+ REMOTE_BOOT_CONFIG = "d-installer_boot.yaml"
43
+
44
+ PATHS = [
45
+ "/usr/lib/d-installer.d",
46
+ "/etc/d-installer.d",
47
+ "/run/d-installer.d"
48
+ ].freeze
49
+
50
+ attr_reader :logger
51
+ attr_reader :workdir
52
+
53
+ # Constructor
54
+ #
55
+ # @param logger [Logger]
56
+ # @param workdir [String] Root directory to read the configuration from
57
+ def initialize(logger: nil, workdir: "/")
58
+ @logger = logger || ::Logger.new($stdout)
59
+ @workdir = workdir
60
+ end
61
+
62
+ # loads correct yaml file
63
+ def config_from_file(path = nil)
64
+ raise "Missing config file at #{path}" unless File.exist?(path)
65
+
66
+ Config.from_file(path)
67
+ end
68
+
69
+ # Return an {Array} with the different {Config} objects read from the different locations
70
+ #
71
+ # TODO: handle precedence correctly
72
+ #
73
+ # @returm [Array<Config>] an array with all the configurations read from the system
74
+ def configs
75
+ return @configs if @configs
76
+
77
+ @configs = config_paths.map { |path| config_from_file(path) }
78
+ @configs << remote_config if remote_config
79
+ @configs << cmdline_config if cmdline_config
80
+ @configs
81
+ end
82
+
83
+ # Return a {Config} oject
84
+ # @return [Config] resultant Config after merging all the configurations
85
+ def config
86
+ config = configs.first || Config.new
87
+ (configs[1..-1] || []).each { |c| config = config.merge(c) }
88
+ config
89
+ end
90
+
91
+ private
92
+
93
+ # Copy a file from a potentially remote location
94
+ #
95
+ # @param location [String] File location. It might be an URL-like string (e.g.,
96
+ # "http://example.net/example.yml").
97
+ # @param target [String] Path to copy the file to.
98
+ # @return [Boolean] Whether the file was sucessfully copied or not
99
+ def copy_file(location, target)
100
+ url = Yast::URL.Parse(location)
101
+
102
+ res = get_file_from_url(
103
+ scheme: url["scheme"],
104
+ host: url["host"],
105
+ urlpath: url["path"],
106
+ localfile: target,
107
+ urltok: url,
108
+ destdir: "/"
109
+ )
110
+
111
+ # TODO: exception?
112
+ logger.error "script #{location} could not be retrieved" unless res
113
+ res
114
+ end
115
+
116
+ # @return [CmdlineArgs]
117
+ def cmdline_args
118
+ @cmdline_args ||= CmdlineArgs.read_from(File.join(workdir, "/proc/cmdline"))
119
+ end
120
+
121
+ # return [Config]
122
+ def cmdline_config
123
+ Config.new(cmdline_args.data)
124
+ end
125
+
126
+ # return [Config]
127
+ def remote_config
128
+ return unless cmdline_args.config_url
129
+
130
+ file_path = File.join(Yast::Directory.tmpdir, REMOTE_BOOT_CONFIG)
131
+ logger.info "Copying boot config to #{file_path}"
132
+
133
+ copy_file(cmdline_args.config_url, file_path)
134
+ config_from_file(file_path)
135
+ end
136
+
137
+ def default_path
138
+ File.exist?(GIT_PATH) ? GIT_PATH : SYSTEM_PATH
139
+ end
140
+
141
+ def config_paths
142
+ paths = PATHS.each_with_object([]) do |path, all|
143
+ all.concat(file_paths_in(File.join(workdir, path)))
144
+ end
145
+
146
+ paths.uniq! { |f| File.basename(f) }
147
+ # Sort files lexicographic
148
+ paths.sort_by! { |f| File.basename(f) }
149
+ paths.prepend(default_path)
150
+
151
+ paths
152
+ end
153
+
154
+ def file_paths_in(path)
155
+ if File.file?(path)
156
+ [path]
157
+ elsif File.directory?(path)
158
+ Dir.glob("#{path}/*.{yml,yaml}")
159
+ else
160
+ []
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,58 @@
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 "dbus"
23
+
24
+ module DInstaller
25
+ module DBus
26
+ # Base class for DBus objects
27
+ class BaseObject < ::DBus::Object
28
+ # Constructor
29
+ #
30
+ # @param path [::DBus::ObjectPath]
31
+ # @param logger [Logger, nil]
32
+ def initialize(path, logger: nil)
33
+ @logger = logger || Logger.new($stdout)
34
+ super(path)
35
+ end
36
+
37
+ # Generates information about interfaces and properties of the object
38
+ #
39
+ # Returns a hash containing interfaces names as keys. Each value is the same hash that would
40
+ # be returned by the org.freedesktop.DBus.Properties.GetAll() method for that combination of
41
+ # object path and interface. If an interface has no properties, the empty hash is returned.
42
+ #
43
+ # @return [Hash]
44
+ def interfaces_and_properties
45
+ get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll)
46
+
47
+ intfs.keys.each_with_object({}) do |interface, hash|
48
+ hash[interface] = public_send(get_all_method, interface).first
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # @return [Logger]
55
+ attr_reader :logger
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
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 "dbus"
23
+ require "abstract_method"
24
+
25
+ module DInstaller
26
+ module DBus
27
+ module Clients
28
+ # Base class for D-Bus clients
29
+ class Base
30
+ # @!method service_name
31
+ # Name of the D-Bus service
32
+ # @return [String]
33
+ abstract_method :service_name
34
+
35
+ # D-Bus service
36
+ #
37
+ # @return [::DBus::Service]
38
+ def service
39
+ @service ||= bus.service(service_name)
40
+ end
41
+
42
+ private
43
+
44
+ # Registers callback to be called when the properties of the given object changes
45
+ #
46
+ # @note Signal subscription is done only once. Otherwise, the latest subscription overrides
47
+ # the previous one.
48
+ #
49
+ # @param dbus_object [::DBus::Object]
50
+ # @param block [Proc]
51
+ def on_properties_change(dbus_object, &block)
52
+ @on_properties_change_callbacks ||= {}
53
+ @on_properties_change_callbacks[dbus_object.path] ||= []
54
+ @on_properties_change_callbacks[dbus_object.path] << block
55
+
56
+ return if @on_properties_change_callbacks[dbus_object.path].size > 1
57
+
58
+ dbus_properties = dbus_object["org.freedesktop.DBus.Properties"]
59
+ dbus_properties.on_signal("PropertiesChanged") do |interface, changes, invalid|
60
+ callbacks = @on_properties_change_callbacks[dbus_object.path]
61
+ callbacks.each { |c| c.call(interface, changes, invalid) }
62
+ end
63
+ end
64
+
65
+ def bus
66
+ @bus ||= ::DBus::SystemBus.instance
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,86 @@
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/clients/base"
23
+
24
+ module DInstaller
25
+ module DBus
26
+ module Clients
27
+ # D-Bus client for language configuration
28
+ class Language < Base
29
+ def initialize
30
+ super
31
+
32
+ @dbus_object = service.object("/org/opensuse/DInstaller/Language1")
33
+ @dbus_object.introspect
34
+ end
35
+
36
+ def service_name
37
+ @service_name ||= "org.opensuse.DInstaller.Language"
38
+ end
39
+
40
+ # Available languages for the installation
41
+ #
42
+ # @return [Array<Array<String, String>>] id and name of each language
43
+ def available_languages
44
+ dbus_object["org.opensuse.DInstaller.Language1"]["AvailableLanguages"].map { |l| l[0..1] }
45
+ end
46
+
47
+ # Languages selected to install
48
+ #
49
+ # @return [Array<String>] ids of the languages
50
+ def selected_languages
51
+ dbus_object["org.opensuse.DInstaller.Language1"]["MarkedForInstall"]
52
+ end
53
+
54
+ # Selects the languages to install
55
+ #
56
+ # @param ids [Array<String>]
57
+ def select_languages(ids)
58
+ dbus_object.ToInstall(ids)
59
+ end
60
+
61
+ # Finishes the language installation
62
+ def finish
63
+ dbus_object.Finish
64
+ end
65
+
66
+ # Registers a callback to run when the language changes
67
+ #
68
+ # @note Signal subscription is done only once. Otherwise, the latest subscription overrides
69
+ # the previous one.
70
+ #
71
+ # @param block [Proc] Callback to run when a language is selected
72
+ def on_language_selected(&block)
73
+ on_properties_change(dbus_object) do |_, changes, _|
74
+ languages = changes["MarkedForInstall"]
75
+ block.call(languages)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @return [::DBus::Object]
82
+ attr_reader :dbus_object
83
+ end
84
+ end
85
+ end
86
+ end