machinery-tool 1.5.0 → 1.6.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS +18 -0
  3. data/html/assets/machinery.css +20 -1
  4. data/html/assets/machinery.js +53 -26
  5. data/html/index.html.haml +69 -46
  6. data/lib/analyze_config_file_diffs_task.rb +1 -1
  7. data/lib/array.rb +36 -14
  8. data/lib/cli.rb +45 -50
  9. data/lib/compare_task.rb +1 -1
  10. data/lib/config.rb +8 -2
  11. data/lib/config_base.rb +14 -1
  12. data/lib/element_filter.rb +48 -11
  13. data/lib/exceptions.rb +5 -0
  14. data/lib/filter.rb +63 -14
  15. data/lib/filter_option_parser.rb +83 -0
  16. data/lib/generate_html_task.rb +3 -1
  17. data/lib/hint.rb +36 -18
  18. data/lib/html.rb +1 -0
  19. data/lib/inspect_task.rb +12 -21
  20. data/lib/inspector.rb +13 -6
  21. data/lib/kiwi_config.rb +17 -14
  22. data/lib/list_task.rb +5 -1
  23. data/lib/local_system.rb +3 -4
  24. data/lib/logged_cheetah.rb +3 -1
  25. data/lib/machinery.rb +1 -0
  26. data/lib/object.rb +24 -19
  27. data/lib/scope_file_store.rb +3 -1
  28. data/lib/show_task.rb +5 -7
  29. data/lib/system_description.rb +11 -12
  30. data/lib/system_description_store.rb +1 -1
  31. data/lib/ui.rb +44 -36
  32. data/lib/upgrade_format_task.rb +4 -1
  33. data/lib/version.rb +1 -1
  34. data/man/generated/machinery.1.gz +0 -0
  35. data/man/generated/machinery.1.html +7 -2
  36. data/plugins/inspect/changed_managed_files_inspector.rb +13 -6
  37. data/plugins/inspect/config_files_inspector.rb +27 -20
  38. data/plugins/inspect/groups_inspector.rb +12 -4
  39. data/plugins/inspect/os_inspector.rb +29 -22
  40. data/plugins/inspect/packages_inspector.rb +13 -5
  41. data/plugins/inspect/patterns_inspector.rb +24 -10
  42. data/plugins/inspect/repositories_inspector.rb +19 -15
  43. data/plugins/inspect/services_inspector.rb +28 -22
  44. data/plugins/inspect/unmanaged_files_inspector.rb +42 -33
  45. data/plugins/inspect/users_inspector.rb +13 -5
  46. data/plugins/model/os_model.rb +1 -1
  47. metadata +3 -2
@@ -18,8 +18,8 @@
18
18
  # Inspect name, version, and other attributes of the operating system
19
19
  class OsInspector < Inspector
20
20
  # determines the architecture
21
- def get_arch(system)
22
- system.run_command("uname", "-m", :stdout => :capture).chomp
21
+ def get_arch
22
+ @system.run_command("uname", "-m", :stdout => :capture).chomp
23
23
  end
24
24
 
25
25
  def strip_arch_from_name(name)
@@ -31,51 +31,58 @@ class OsInspector < Inspector
31
31
  end
32
32
 
33
33
  # checks for additional version information like Beta or RC
34
- def get_additional_version(system)
35
- issue = system.read_file("/etc/issue")
34
+ def get_additional_version
35
+ issue = @system.read_file("/etc/issue")
36
36
  special_version = issue.scan(/Beta\d+|RC\d|GMC\d*/).first if issue
37
37
 
38
38
  special_version ? " #{special_version.gsub(/[0-9]{1,2}/," \\0")}" : ""
39
39
  end
40
40
 
41
- def inspect(system, description, _filter, _options = {})
42
- system.check_requirement("cat", "--version") if system.is_a?(RemoteSystem)
41
+ def initialize(system, description)
42
+ @system = system
43
+ @description = description
44
+ end
45
+
46
+ def inspect(_filter, _options = {})
47
+ @system.check_requirement("cat", "--version") if @system.is_a?(RemoteSystem)
43
48
 
44
- os = get_os(system)
49
+ os = get_os
45
50
  if os
46
- os.architecture = get_arch(system)
47
- os.version += get_additional_version(system)
48
- summary = "Found operating system '#{os.name}' version '#{os.version}'."
51
+ os.architecture = get_arch
52
+ os.version += get_additional_version
49
53
  else
50
54
  raise Machinery::Errors::UnknownOs
51
55
  end
52
56
 
53
- description.os = os
54
- summary
57
+ @description.os = os
58
+ end
59
+
60
+ def summary
61
+ "Found operating system '#{@description.os.name}' version '#{@description.os.version}'."
55
62
  end
56
63
 
57
64
  private
58
65
 
59
- def get_os(system)
66
+ def get_os
60
67
  # Use os-release file by default
61
- os = get_os_from_os_release(system)
68
+ os = get_os_from_os_release
62
69
 
63
70
  # Fall back to SuSE-release file
64
71
  if !os
65
- os = get_os_from_suse_release(system)
72
+ os = get_os_from_suse_release
66
73
  end
67
74
 
68
75
  # Fall back to redhat-release file
69
76
  if !os
70
- os = get_os_from_redhat_release(system)
77
+ os = get_os_from_redhat_release
71
78
  end
72
79
 
73
80
  os
74
81
  end
75
82
 
76
83
  # check for freedesktop standard: /etc/os-release
77
- def get_os_from_os_release(system)
78
- os_release = system.read_file("/etc/os-release")
84
+ def get_os_from_os_release
85
+ os_release = @system.read_file("/etc/os-release")
79
86
  return if !os_release
80
87
 
81
88
  result = Hash.new
@@ -94,8 +101,8 @@ class OsInspector < Inspector
94
101
  end
95
102
 
96
103
  # checks for old suse standard: /etc/SuSE-release
97
- def get_os_from_suse_release(system)
98
- suse_release = system.read_file("/etc/SuSE-release")
104
+ def get_os_from_suse_release
105
+ suse_release = @system.read_file("/etc/SuSE-release")
99
106
  return if !suse_release
100
107
 
101
108
  result = Hash.new
@@ -120,8 +127,8 @@ class OsInspector < Inspector
120
127
  end
121
128
 
122
129
  # checks for redhat standard: /etc/redhat-release
123
- def get_os_from_redhat_release(system)
124
- redhat_release = system.read_file("/etc/redhat-release")
130
+ def get_os_from_redhat_release
131
+ redhat_release = @system.read_file("/etc/redhat-release")
125
132
  return if !redhat_release
126
133
 
127
134
  result = Hash.new
@@ -16,11 +16,16 @@
16
16
  # you may find current contact information at www.suse.com
17
17
 
18
18
  class PackagesInspector < Inspector
19
- def inspect(system, description, _filter, _options = {})
20
- system.check_requirement("rpm", "--version")
19
+ def initialize(system, description)
20
+ @system = system
21
+ @description = description
22
+ end
23
+
24
+ def inspect(_filter, _options = {})
25
+ @system.check_requirement("rpm", "--version")
21
26
 
22
27
  packages = Array.new
23
- rpm_data = system.run_command(
28
+ rpm_data = @system.run_command(
24
29
  "rpm","-qa","--qf",
25
30
  "%{NAME}|%{VERSION}|%{RELEASE}|%{ARCH}|%{VENDOR}|%{SIGMD5}$",
26
31
  :stdout=>:capture
@@ -40,7 +45,10 @@ class PackagesInspector < Inspector
40
45
  )
41
46
  end
42
47
 
43
- description.packages = PackagesScope.new(packages.sort_by(&:name))
44
- "Found #{packages.count} packages."
48
+ @description.packages = PackagesScope.new(packages.sort_by(&:name))
49
+ end
50
+
51
+ def summary
52
+ "Found #{@description.packages.size} packages."
45
53
  end
46
54
  end
@@ -16,20 +16,35 @@
16
16
  # you may find current contact information at www.suse.com
17
17
 
18
18
  class PatternsInspector < Inspector
19
- def inspect(system, description, _filter, _options = {})
20
- if system.has_command?("zypper")
21
- inspect_with_zypper(system, description)
19
+ def initialize(system, description)
20
+ @system = system
21
+ @description = description
22
+ end
23
+
24
+ def inspect(_filter, _options = {})
25
+ if @system.has_command?("zypper")
26
+ @patterns_supported = true
27
+ inspect_with_zypper
28
+ else
29
+ @patterns_supported = false
30
+ @description.patterns = PatternsScope.new
31
+ "Patterns are not supported on this system."
32
+ end
33
+ end
34
+
35
+ def summary
36
+ if @patterns_supported
37
+ "Found #{@description.patterns.count} patterns."
22
38
  else
23
- description.patterns = PatternsScope.new
24
39
  "Patterns are not supported on this system."
25
40
  end
26
41
  end
27
42
 
28
43
  private
29
44
 
30
- def inspect_with_zypper(system, description)
45
+ def inspect_with_zypper
31
46
  begin
32
- xml = system.run_command("zypper", "-xq", "--no-refresh", "patterns",
47
+ xml = @system.run_command("zypper", "-xq", "--no-refresh", "patterns",
33
48
  "-i", stdout: :capture)
34
49
  rescue Cheetah::ExecutionFailed => e
35
50
  if e.stdout.include?("locked")
@@ -44,8 +59,8 @@ class PatternsInspector < Inspector
44
59
  pattern_list = Nokogiri::XML(xml).xpath("/stream/pattern-list/pattern")
45
60
 
46
61
  if pattern_list.count == 0
47
- description.patterns = PatternsScope.new
48
- return "Found 0 patterns."
62
+ @description.patterns = PatternsScope.new
63
+ return
49
64
  end
50
65
 
51
66
  patterns = pattern_list.map do |pattern|
@@ -56,7 +71,6 @@ class PatternsInspector < Inspector
56
71
  )
57
72
  end.uniq.sort_by(&:name)
58
73
 
59
- description.patterns = PatternsScope.new(patterns)
60
- "Found #{patterns.count} patterns."
74
+ @description.patterns = PatternsScope.new(patterns)
61
75
  end
62
76
  end
@@ -16,23 +16,30 @@
16
16
  # you may find current contact information at www.suse.com
17
17
 
18
18
  class RepositoriesInspector < Inspector
19
- def inspect(system, description, _filter, _options = {})
19
+ def initialize(system, description)
20
+ @system = system
21
+ @description = description
22
+ end
23
+
24
+ def inspect(_filter, _options = {})
20
25
  if system.has_command?("zypper")
21
- description.repositories, summary = inspect_zypp_repositories(system)
26
+ @description.repositories = inspect_zypp_repositories
22
27
  elsif system.has_command?("yum")
23
- description.repositories, summary = inspect_yum_repositories(system)
28
+ @description.repositories = inspect_yum_repositories
24
29
  else
25
30
  raise Machinery::Errors::MissingRequirement.new(
26
31
  "Need either the binary 'zypper' or 'yum' to be available on the inspected system."
27
32
  )
28
33
  end
34
+ end
29
35
 
30
- summary
36
+ def summary
37
+ "Found #{@description.repositories.size} repositories."
31
38
  end
32
39
 
33
40
  private
34
41
 
35
- def inspect_zypp_repositories(system)
42
+ def inspect_zypp_repositories
36
43
  begin
37
44
  xml = system.run_command(
38
45
  "zypper", "--non-interactive", "--xmlout", "repos", "--details",
@@ -52,18 +59,16 @@ class RepositoriesInspector < Inspector
52
59
 
53
60
  if details.empty?
54
61
  result = []
55
- summary = "Found 0 repositories."
56
62
  else
57
63
  priorities = parse_priorities_from_details(details)
58
- credentials = get_credentials_from_system(system)
64
+ credentials = get_credentials_from_system
59
65
  result = parse_repositories_from_xml(xml, priorities, credentials)
60
- summary = "Found #{result.count} repositories."
61
66
  end
62
67
 
63
- [RepositoriesScope.new(result), summary]
68
+ RepositoriesScope.new(result)
64
69
  end
65
70
 
66
- def inspect_yum_repositories(system)
71
+ def inspect_yum_repositories
67
72
  script = File.read(File.join(Machinery::ROOT, "helpers", "yum_repositories.py"))
68
73
  begin
69
74
  repositories = JSON.parse(system.run_command(
@@ -80,8 +85,7 @@ class RepositoriesInspector < Inspector
80
85
  raise Machinery::Errors::InspectionFailed.new("Extraction of YUM repositories failed.")
81
86
  end
82
87
 
83
- summary = "Found #{repositories.count} repositories"
84
- [RepositoriesScope.new(repositories), summary]
88
+ RepositoriesScope.new(repositories)
85
89
  end
86
90
 
87
91
  def parse_priorities_from_details(details)
@@ -98,16 +102,16 @@ class RepositoriesInspector < Inspector
98
102
  prio
99
103
  end
100
104
 
101
- def get_credentials_from_system(system)
105
+ def get_credentials_from_system
102
106
  credentials = {}
103
107
  credential_dir = "/etc/zypp/credentials.d/"
104
- credential_files = system.run_command(
108
+ credential_files = @system.run_command(
105
109
  "bash", "-c",
106
110
  "test -d '#{credential_dir}' && ls -1 '#{credential_dir}' || echo ''",
107
111
  stdout: :capture
108
112
  )
109
113
  credential_files.split("\n").each do |f|
110
- content = system.run_command(
114
+ content = @system.run_command(
111
115
  "cat", "/etc/zypp/credentials.d/#{f}", stdout: :capture
112
116
  )
113
117
  content.match(/username=(\w*)\npassword=(\w*)/)
@@ -16,27 +16,35 @@
16
16
  # you may find current contact information at www.suse.com
17
17
 
18
18
  class ServicesInspector < Inspector
19
- def inspect(system, description, _filter, _options = {})
20
- if system.has_command?("systemctl")
19
+ def initialize(system, description)
20
+ @system = system
21
+ @description = description
22
+ end
23
+
24
+ def inspect(_filter, _options = {})
25
+ if @system.has_command?("systemctl")
21
26
  result = ServicesScope.new(
22
27
  init_system: "systemd",
23
- services: inspect_systemd_services(system)
28
+ services: inspect_systemd_services
24
29
  )
25
30
  else
26
31
  result = ServicesScope.new(
27
32
  init_system: "sysvinit",
28
- services: inspect_sysvinit_services(system)
33
+ services: inspect_sysvinit_services
29
34
  )
30
35
  end
31
36
 
32
- description.services = result
33
- @summary
37
+ @description.services = result
38
+ end
39
+
40
+ def summary
41
+ "Found #{@description.services.services.length} services."
34
42
  end
35
43
 
36
44
  private
37
45
 
38
- def inspect_systemd_services(system)
39
- output = system.run_command(
46
+ def inspect_systemd_services
47
+ output = @system.run_command(
40
48
  "systemctl",
41
49
  "list-unit-files",
42
50
  "--type=service,socket",
@@ -53,30 +61,28 @@ class ServicesInspector < Inspector
53
61
  Service.new(name: name, state: state)
54
62
  end
55
63
 
56
- @summary = "Found #{services.size} services."
57
64
  ServiceList.new(services.sort_by(&:name))
58
65
  end
59
66
 
60
- def inspect_sysvinit_services(system)
67
+ def inspect_sysvinit_services
61
68
  # Red Hat's chkconfig behaves differently than SUSE's: It takes different
62
69
  # command line arguments and has a different output format. We determine
63
70
  # if it's Red Hat by calling 'chkconfig --version'. On SUSE it exits with
64
71
  # an error, on Red Hat it doesn't.
65
72
  #
66
73
  begin
67
- system.run_command("chkconfig", "--version")
68
- services = parse_redhat_chkconfig(system)
74
+ @system.run_command("chkconfig", "--version")
75
+ services = parse_redhat_chkconfig
69
76
  rescue
70
- services = parse_suse_chkconfig(system)
77
+ services = parse_suse_chkconfig
71
78
  end
72
79
 
73
- @summary = "Found #{services.size} services."
74
80
  ServiceList.new(services.sort_by(&:name))
75
81
  end
76
82
 
77
- def parse_suse_chkconfig(system)
78
- system.check_requirement("chkconfig", "--help")
79
- output = system.run_command(
83
+ def parse_suse_chkconfig
84
+ @system.check_requirement("chkconfig", "--help")
85
+ output = @system.run_command(
80
86
  "chkconfig",
81
87
  "--allservices",
82
88
  :stdout => :capture
@@ -88,15 +94,15 @@ class ServicesInspector < Inspector
88
94
  end
89
95
  end
90
96
 
91
- def parse_redhat_chkconfig(system)
92
- system.check_requirement("chkconfig", "--version")
93
- system.check_requirement("runlevel")
94
- _, runlevel = system.run_command(
97
+ def parse_redhat_chkconfig
98
+ @system.check_requirement("chkconfig", "--version")
99
+ @system.check_requirement("runlevel")
100
+ _, runlevel = @system.run_command(
95
101
  "runlevel",
96
102
  stdout: :capture
97
103
  ).split(" ")
98
104
 
99
- output = system.run_command(
105
+ output = @system.run_command(
100
106
  "chkconfig", "--list",
101
107
  stdout: :capture
102
108
  )
@@ -17,18 +17,18 @@
17
17
 
18
18
  class UnmanagedFilesInspector < Inspector
19
19
  # checks if all required binaries are present
20
- def check_requirements(system, check_tar)
21
- system.check_requirement("rpm", "--version")
22
- system.check_requirement("sed", "--version")
23
- system.check_requirement("cat", "--version")
24
- system.check_requirement("find", "--version")
25
- system.check_requirement("tar", "--version") if check_tar
26
- system.check_requirement("gzip", "--version") if check_tar
20
+ def check_requirements(check_tar)
21
+ @system.check_requirement("rpm", "--version")
22
+ @system.check_requirement("sed", "--version")
23
+ @system.check_requirement("cat", "--version")
24
+ @system.check_requirement("find", "--version")
25
+ @system.check_requirement("tar", "--version") if check_tar
26
+ @system.check_requirement("gzip", "--version") if check_tar
27
27
  end
28
28
 
29
29
  # extract pathes from rpm database into ruby hashes
30
- def extract_rpm_database(system)
31
- out = system.run_command(
30
+ def extract_rpm_database
31
+ out = @system.run_command(
32
32
  ["rpm", "-qlav"],
33
33
  ["sed", "s/^\\(.\\)[^/]* /\\1 /"],
34
34
  :stdout => :capture
@@ -79,14 +79,14 @@ class UnmanagedFilesInspector < Inspector
79
79
  p "should not happen non-abs dirs:#{list}" unless list.empty?
80
80
  end
81
81
 
82
- def extract_unmanaged_files(system, description, files, trees, excluded, store_name)
83
- file_store = description.scope_file_store(store_name)
82
+ def extract_unmanaged_files(files, trees, excluded, store_name)
83
+ file_store = @description.scope_file_store(store_name)
84
84
  file_store.remove
85
85
  file_store.create
86
86
  store_path = file_store.path
87
87
 
88
88
  archive_path = File.join(store_path, "files.tgz")
89
- system.create_archive(files.join("\0"), archive_path, excluded)
89
+ @system.create_archive(files.join("\0"), archive_path, excluded)
90
90
 
91
91
  trees.each do |tree|
92
92
  tree_name = File.basename(tree)
@@ -95,7 +95,7 @@ class UnmanagedFilesInspector < Inspector
95
95
 
96
96
  file_store.create_sub_directory(sub_dir)
97
97
  archive_path = File.join(store_path, sub_dir, "#{tree_name}.tgz")
98
- system.create_archive(tree, archive_path, excluded)
98
+ @system.create_archive(tree, archive_path, excluded)
99
99
  end
100
100
  end
101
101
 
@@ -136,7 +136,7 @@ class UnmanagedFilesInspector < Inspector
136
136
  end
137
137
 
138
138
  # find paths below dir until a certain depth is reached
139
- def get_find_data( system, dir, depth )
139
+ def get_find_data(dir, depth )
140
140
  dep = depth - 1
141
141
  files = {}
142
142
  dirs = {}
@@ -157,7 +157,7 @@ class UnmanagedFilesInspector < Inspector
157
157
  # string which can be safely used.
158
158
  out = ""
159
159
  begin
160
- out = system.run_command(
160
+ out = @system.run_command(
161
161
  "/bin/bash",
162
162
  stdin: cmd,
163
163
  stdout: :capture,
@@ -218,20 +218,25 @@ class UnmanagedFilesInspector < Inspector
218
218
  3
219
219
  end
220
220
 
221
- def inspect(system, description, filter, options = {})
221
+ def initialize(system, description)
222
+ @system = system
223
+ @description = description
224
+ end
225
+
226
+ def inspect(filter, options = {})
222
227
  do_extract = options && options[:extract_unmanaged_files]
223
- check_requirements(system, do_extract)
228
+ check_requirements(do_extract)
224
229
 
225
- file_store_tmp = description.scope_file_store("unmanaged_files.tmp")
226
- file_store_final = description.scope_file_store("unmanaged_files")
230
+ file_store_tmp = @description.scope_file_store("unmanaged_files.tmp")
231
+ file_store_final = @description.scope_file_store("unmanaged_files")
227
232
 
228
- mount_points = MountPoints.new(system)
233
+ mount_points = MountPoints.new(@system)
229
234
 
230
- rpm_files, rpm_dirs = extract_rpm_database(system)
235
+ rpm_files, rpm_dirs = extract_rpm_database
231
236
 
232
237
  # Btrfs subvolumes and local mounts need to be inspected separately because
233
238
  # they are not part of the `get_find_data` return data
234
- local_filesystems = mount_points.local + btrfs_subvolumes(system)
239
+ local_filesystems = mount_points.local + btrfs_subvolumes
235
240
 
236
241
  unmanaged_files = []
237
242
  unmanaged_trees = []
@@ -242,10 +247,12 @@ class UnmanagedFilesInspector < Inspector
242
247
 
243
248
  file_filter = filter.element_filter_for("/unmanaged_files/files/name") if filter
244
249
  file_filter ||= ElementFilter.new("/unmanaged_files/files/name")
245
- file_filter.add_matchers(description.store.base_path)
250
+ file_filter.add_matchers("=", @description.store.base_path)
246
251
 
247
252
  # Add a recursive pendant to each ignored element
248
- file_filter.add_matchers(file_filter.matchers.map { |entry| File.join(entry, "/*") })
253
+ file_filter.matchers.each do |operator, matchers|
254
+ file_filter.add_matchers(operator, matchers.map { |entry| File.join(entry, "/*") })
255
+ end
249
256
 
250
257
  remote_dirs.delete_if { |e| file_filter.matches?(e) }
251
258
 
@@ -276,7 +283,7 @@ class UnmanagedFilesInspector < Inspector
276
283
 
277
284
  # determine files and directories below find_dir until a certain depth
278
285
  depth = local_filesystems.include?(find_dir) ? start : max
279
- files, dirs, excluded = get_find_data( system, find_dir, depth )
286
+ files, dirs, excluded = get_find_data(find_dir, depth )
280
287
  excluded_files += excluded
281
288
  find_count += 1
282
289
  find_dir += "/" if find_dir.size > 1
@@ -341,8 +348,7 @@ class UnmanagedFilesInspector < Inspector
341
348
  Machinery.logger.debug "inspect unmanaged files find calls:#{find_count} files:#{unmanaged_files.size} trees:#{unmanaged_trees.size}"
342
349
  begin
343
350
  if do_extract
344
- extract_unmanaged_files(system, description, unmanaged_files,
345
- unmanaged_trees, excluded_files, file_store_tmp.store_name)
351
+ extract_unmanaged_files(unmanaged_files, unmanaged_trees, excluded_files, file_store_tmp.store_name)
346
352
  else
347
353
  file_store_final.remove
348
354
  end
@@ -360,7 +366,7 @@ class UnmanagedFilesInspector < Inspector
360
366
  # Handle SIGHUP(1), SIGINT(2) and SIGTERM(15) gracefully
361
367
  if [1, 2, 15].include?(e.signo)
362
368
  Machinery::Ui.warn "Interrupted by user. The partly extracted unmanaged-files are available" \
363
- " under '#{description.file_store(tmp_file_store)}'"
369
+ " under '#{@description.file_store(tmp_file_store)}'"
364
370
  end
365
371
  raise
366
372
  end
@@ -368,12 +374,15 @@ class UnmanagedFilesInspector < Inspector
368
374
  osl << UnmanagedFile.new( name: remote_dir + "/", type: "remote_dir")
369
375
  end
370
376
 
371
- summary = "#{do_extract ? "Extracted" : "Found"} #{osl.size} unmanaged files and trees."
372
- description["unmanaged_files"] = UnmanagedFilesScope.new(
377
+ @description["unmanaged_files"] = UnmanagedFilesScope.new(
373
378
  extracted: !!do_extract,
374
379
  files: UnmanagedFileList.new(osl.sort_by(&:name))
375
380
  )
376
- summary
381
+ end
382
+
383
+ def summary
384
+ "#{@description.unmanaged_files.extracted ? "Extracted" : "Found"} " +
385
+ "#{@description.unmanaged_files.files.count} unmanaged files and trees."
377
386
  end
378
387
 
379
388
  private
@@ -390,8 +399,8 @@ class UnmanagedFilesInspector < Inspector
390
399
  s.dup.force_encoding("UTF-8").encode("UTF-16", invalid: :replace).encode("UTF-8")
391
400
  end
392
401
 
393
- def btrfs_subvolumes(system)
394
- system.run_command(
402
+ def btrfs_subvolumes
403
+ @system.run_command(
395
404
  ["btrfs", "subvolume", "list", "/"],
396
405
  ["awk", "{print $NF}"],
397
406
  stdout: :capture