machinery-tool 1.0.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/COPYING +674 -0
- data/NEWS +143 -0
- data/bin/machinery +29 -0
- data/helpers/changed_managed_files.sh +32 -0
- data/helpers/filter-packages-for-build.yaml +6 -0
- data/html/assets/arrow_down.png +0 -0
- data/html/assets/arrow_up.png +0 -0
- data/html/assets/bootstrap-popover.js +113 -0
- data/html/assets/bootstrap-tooltip.js +457 -0
- data/html/assets/bootstrap.min.css +5 -0
- data/html/assets/collapse.js +174 -0
- data/html/assets/hogan-3.0.2.min.mustache.js +5 -0
- data/html/assets/jquery-2.1.1.min.js +4 -0
- data/html/assets/logo-changed-managed-files-small.png +0 -0
- data/html/assets/logo-changed-managed-files.png +0 -0
- data/html/assets/logo-config-files-small.png +0 -0
- data/html/assets/logo-config-files.png +0 -0
- data/html/assets/logo-groups-small.png +0 -0
- data/html/assets/logo-groups.png +0 -0
- data/html/assets/logo-os-small.png +0 -0
- data/html/assets/logo-os.png +0 -0
- data/html/assets/logo-packages-small.png +0 -0
- data/html/assets/logo-packages.png +0 -0
- data/html/assets/logo-patterns-small.png +0 -0
- data/html/assets/logo-patterns.png +0 -0
- data/html/assets/logo-repositories-small.png +0 -0
- data/html/assets/logo-repositories.png +0 -0
- data/html/assets/logo-services-small.png +0 -0
- data/html/assets/logo-services.png +0 -0
- data/html/assets/logo-unmanaged-files-small.png +0 -0
- data/html/assets/logo-unmanaged-files.png +0 -0
- data/html/assets/logo-users-small.png +0 -0
- data/html/assets/logo-users.png +0 -0
- data/html/assets/machinery-base.css +5767 -0
- data/html/assets/machinery.css +131 -0
- data/html/assets/machinery.js +148 -0
- data/html/assets/transition.js +59 -0
- data/html/assets/wheels_horizontal.png +0 -0
- data/html/index.html.haml +468 -0
- data/kiwi_helpers/kiwi_export_readme.md +22 -0
- data/kiwi_helpers/merge_users_and_groups.pl.erb +231 -0
- data/kiwi_helpers/unmanaged_files_build_excludes +5 -0
- data/lib/analyze_config_file_diffs_task.rb +130 -0
- data/lib/array.rb +98 -0
- data/lib/build_task.rb +124 -0
- data/lib/changed_rpm_files_helper.rb +96 -0
- data/lib/cli.rb +600 -0
- data/lib/compare_task.rb +68 -0
- data/lib/config.rb +33 -0
- data/lib/config_base.rb +117 -0
- data/lib/config_task.rb +56 -0
- data/lib/constants.rb +24 -0
- data/lib/copy_task.rb +22 -0
- data/lib/current_user.rb +23 -0
- data/lib/deploy_task.rb +89 -0
- data/lib/exceptions.rb +113 -0
- data/lib/generate_html_task.rb +22 -0
- data/lib/helper.rb +22 -0
- data/lib/hint.rb +39 -0
- data/lib/html.rb +103 -0
- data/lib/inspect_task.rb +93 -0
- data/lib/inspector.rb +65 -0
- data/lib/kiwi_config.rb +356 -0
- data/lib/kiwi_export_task.rb +36 -0
- data/lib/list_task.rb +64 -0
- data/lib/local_system.rb +127 -0
- data/lib/logged_cheetah.rb +25 -0
- data/lib/machinery.rb +85 -0
- data/lib/machinery_logger.rb +47 -0
- data/lib/migration.rb +128 -0
- data/lib/mountpoints.rb +72 -0
- data/lib/object.rb +138 -0
- data/lib/os.rb +78 -0
- data/lib/remote_system.rb +114 -0
- data/lib/remove_task.rb +43 -0
- data/lib/renderer.rb +243 -0
- data/lib/renderer_helper.rb +26 -0
- data/lib/rpm.rb +52 -0
- data/lib/scope_mixin.rb +38 -0
- data/lib/show_task.rb +65 -0
- data/lib/system.rb +81 -0
- data/lib/system_description.rb +228 -0
- data/lib/system_description_store.rb +167 -0
- data/lib/system_description_validator.rb +216 -0
- data/lib/tarball.rb +82 -0
- data/lib/ui.rb +74 -0
- data/lib/upgrade_format_task.rb +55 -0
- data/lib/validate_task.rb +23 -0
- data/lib/version.rb +22 -0
- data/lib/zypper.rb +70 -0
- data/man/generated/machinery.1.gz +0 -0
- data/man/generated/machinery.1.html +1056 -0
- data/plugins/docs/changed_managed_files.md +2 -0
- data/plugins/docs/config_files.md +5 -0
- data/plugins/docs/groups.md +2 -0
- data/plugins/docs/os.md +2 -0
- data/plugins/docs/packages.md +2 -0
- data/plugins/docs/patterns.md +5 -0
- data/plugins/docs/repositories.md +24 -0
- data/plugins/docs/services.md +6 -0
- data/plugins/docs/unmanaged_files.md +13 -0
- data/plugins/docs/users.md +3 -0
- data/plugins/inspect/changed_managed_files_inspector.rb +109 -0
- data/plugins/inspect/config_files_inspector.rb +117 -0
- data/plugins/inspect/groups_inspector.rb +46 -0
- data/plugins/inspect/os_inspector.rb +116 -0
- data/plugins/inspect/packages_inspector.rb +46 -0
- data/plugins/inspect/patterns_inspector.rb +67 -0
- data/plugins/inspect/repositories_inspector.rb +107 -0
- data/plugins/inspect/services_inspector.rb +88 -0
- data/plugins/inspect/unmanaged_files_inspector.rb +393 -0
- data/plugins/inspect/users_inspector.rb +87 -0
- data/plugins/model/changed_managed_files_model.rb +29 -0
- data/plugins/model/config_files_model.rb +29 -0
- data/plugins/model/groups_model.rb +26 -0
- data/plugins/model/os_model.rb +20 -0
- data/plugins/model/packages_model.rb +26 -0
- data/plugins/model/patterns_model.rb +26 -0
- data/plugins/model/repositories_model.rb +26 -0
- data/plugins/model/services_model.rb +48 -0
- data/plugins/model/unmanaged_files_model.rb +29 -0
- data/plugins/model/users_model.rb +26 -0
- data/plugins/schema/v1/system-description-changed-managed-files.schema.json +83 -0
- data/plugins/schema/v1/system-description-config-files.schema.json +83 -0
- data/plugins/schema/v1/system-description-groups.schema.json +30 -0
- data/plugins/schema/v1/system-description-os.schema.json +21 -0
- data/plugins/schema/v1/system-description-packages.schema.json +34 -0
- data/plugins/schema/v1/system-description-patterns.schema.json +24 -0
- data/plugins/schema/v1/system-description-repositories.schema.json +41 -0
- data/plugins/schema/v1/system-description-services.schema.json +30 -0
- data/plugins/schema/v1/system-description-unmanaged-files.schema.json +105 -0
- data/plugins/schema/v1/system-description-users.schema.json +61 -0
- data/plugins/schema/v2/system-description-changed-managed-files.schema.json +92 -0
- data/plugins/schema/v2/system-description-config-files.schema.json +92 -0
- data/plugins/schema/v2/system-description-groups.schema.json +30 -0
- data/plugins/schema/v2/system-description-os.schema.json +21 -0
- data/plugins/schema/v2/system-description-packages.schema.json +34 -0
- data/plugins/schema/v2/system-description-patterns.schema.json +24 -0
- data/plugins/schema/v2/system-description-repositories.schema.json +41 -0
- data/plugins/schema/v2/system-description-services.schema.json +30 -0
- data/plugins/schema/v2/system-description-unmanaged-files.schema.json +138 -0
- data/plugins/schema/v2/system-description-users.schema.json +61 -0
- data/plugins/show/changed_managed_files_renderer.rb +46 -0
- data/plugins/show/config_files_renderer.rb +62 -0
- data/plugins/show/groups_renderer.rb +36 -0
- data/plugins/show/os_renderer.rb +31 -0
- data/plugins/show/packages_renderer.rb +32 -0
- data/plugins/show/patterns_renderer.rb +32 -0
- data/plugins/show/repositories_renderer.rb +38 -0
- data/plugins/show/services_renderer.rb +32 -0
- data/plugins/show/unmanaged_files_renderer.rb +42 -0
- data/plugins/show/users_renderer.rb +35 -0
- data/schema/migrations/migrate1to2.rb +56 -0
- data/schema/v1/system-description-global.schema.json +31 -0
- data/schema/v2/system-description-global.schema.json +31 -0
- metadata +370 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright (c) 2013-2014 SUSE LLC
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of version 3 of the GNU General Public License as
|
|
5
|
+
# published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program; if not, contact SUSE LLC.
|
|
14
|
+
#
|
|
15
|
+
# To contact SUSE about this file by physical or electronic mail,
|
|
16
|
+
# you may find current contact information at www.suse.com
|
|
17
|
+
|
|
18
|
+
class PackagesInspector < Inspector
|
|
19
|
+
def inspect(system, description, options = {})
|
|
20
|
+
system.check_requirement("rpm", "--version")
|
|
21
|
+
|
|
22
|
+
packages = Array.new
|
|
23
|
+
rpm_data = system.run_command(
|
|
24
|
+
"rpm","-qa","--qf",
|
|
25
|
+
"%{NAME}|%{VERSION}|%{RELEASE}|%{ARCH}|%{VENDOR}|%{SIGMD5}$",
|
|
26
|
+
:stdout=>:capture
|
|
27
|
+
)
|
|
28
|
+
# gpg-pubkeys are no real packages but listed by rpm in the regular
|
|
29
|
+
# package list
|
|
30
|
+
rpm_data.scan(/(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\$/).reject do |name, *attrs|
|
|
31
|
+
name =~ /^gpg-pubkey$/
|
|
32
|
+
end.each do |name, version, release, arch, vendor, checksum|
|
|
33
|
+
packages << Package.new(
|
|
34
|
+
:name => name,
|
|
35
|
+
:version => version,
|
|
36
|
+
:release => release,
|
|
37
|
+
:arch => arch,
|
|
38
|
+
:vendor => vendor,
|
|
39
|
+
:checksum => checksum
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
description.packages = PackagesScope.new(packages.sort_by(&:name))
|
|
44
|
+
"Found #{packages.count} packages."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright (c) 2013-2014 SUSE LLC
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of version 3 of the GNU General Public License as
|
|
5
|
+
# published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program; if not, contact SUSE LLC.
|
|
14
|
+
#
|
|
15
|
+
# To contact SUSE about this file by physical or electronic mail,
|
|
16
|
+
# you may find current contact information at www.suse.com
|
|
17
|
+
|
|
18
|
+
class PatternsInspector < Inspector
|
|
19
|
+
def inspect(system, description, options = {})
|
|
20
|
+
system.check_requirement("zypper", "--version")
|
|
21
|
+
|
|
22
|
+
xml = system.run_command("zypper", "-xq", "patterns", "-i", :stdout => :capture)
|
|
23
|
+
pattern_list = Nokogiri::XML(xml).xpath("/stream/pattern-list/pattern")
|
|
24
|
+
|
|
25
|
+
if pattern_list.count == 0
|
|
26
|
+
description.patterns = PatternsScope.new()
|
|
27
|
+
return "Found 0 patterns."
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The zypper patterns output looks like this:
|
|
31
|
+
#
|
|
32
|
+
# <?xml version='1.0'?>
|
|
33
|
+
# <stream>
|
|
34
|
+
# <pattern-list>
|
|
35
|
+
# <pattern name="base" version="13.1" release="13.6.1" epoch="0" arch="i586" vendor="openSUSE" summary="Base System" repo="repo-oss" installed="1" uservisible="1">
|
|
36
|
+
# <description>This is the base runtime system. It contains only a minimal multiuser booting system. For running on real hardware, you need to add additional packages and pattern to make this pattern useful on its own.</description>
|
|
37
|
+
# </pattern>
|
|
38
|
+
# <pattern name="base" version="13.1" release="13.6.1" epoch="0" arch="x86_64" vendor="openSUSE" summary="Base System" repo="repo-oss" installed="1" uservisible="1">
|
|
39
|
+
# <description>This is the base runtime system. It contains only a minimal multiuser booting system. For running on real hardware, you need to add additional packages and pattern to make this pattern useful on its own.</description>
|
|
40
|
+
# </pattern>
|
|
41
|
+
# </pattern-list>
|
|
42
|
+
# </stream>
|
|
43
|
+
#
|
|
44
|
+
#
|
|
45
|
+
# and we want to return an array of pattern objects like this:
|
|
46
|
+
#
|
|
47
|
+
# [
|
|
48
|
+
# {
|
|
49
|
+
# name: "base",
|
|
50
|
+
# version: "13.1-13.6.1",
|
|
51
|
+
# }
|
|
52
|
+
# ]
|
|
53
|
+
#
|
|
54
|
+
# Patterns listed for different architectures should be combined.
|
|
55
|
+
|
|
56
|
+
patterns = pattern_list.map do |pattern|
|
|
57
|
+
Pattern.new(
|
|
58
|
+
name: pattern["name"],
|
|
59
|
+
version: pattern["version"],
|
|
60
|
+
release: pattern["release"]
|
|
61
|
+
)
|
|
62
|
+
end.uniq.sort_by(&:name)
|
|
63
|
+
|
|
64
|
+
description.patterns = PatternsScope.new(patterns)
|
|
65
|
+
"Found #{patterns.count} patterns."
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Copyright (c) 2013-2014 SUSE LLC
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of version 3 of the GNU General Public License as
|
|
5
|
+
# published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program; if not, contact SUSE LLC.
|
|
14
|
+
#
|
|
15
|
+
# To contact SUSE about this file by physical or electronic mail,
|
|
16
|
+
# you may find current contact information at www.suse.com
|
|
17
|
+
|
|
18
|
+
require "nokogiri"
|
|
19
|
+
|
|
20
|
+
class RepositoriesInspector < Inspector
|
|
21
|
+
def inspect(system, description, options = {})
|
|
22
|
+
system.check_requirement("zypper", "--version")
|
|
23
|
+
|
|
24
|
+
xml = system.run_command(
|
|
25
|
+
"zypper", "--non-interactive", "--xmlout", "repos", "--details", :stdout => :capture
|
|
26
|
+
)
|
|
27
|
+
details = system.run_command(
|
|
28
|
+
"zypper", "--non-interactive", "repos", "--details", :stdout => :capture
|
|
29
|
+
).split("\n").select { |l| l =~ /\A# +\| |\A *\d+ \| / }.
|
|
30
|
+
map { |l| l.split("|").map(&:strip) }
|
|
31
|
+
|
|
32
|
+
if !details.empty?
|
|
33
|
+
# parse and remove header
|
|
34
|
+
idx_prio = details.first.index("Priority")
|
|
35
|
+
idx_alias = details.first.index("Alias")
|
|
36
|
+
details.shift
|
|
37
|
+
|
|
38
|
+
prio = {}
|
|
39
|
+
details.each_with_index do |entry,idx|
|
|
40
|
+
prio[entry[idx_alias]] = entry[idx_prio].to_i
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
credentials = {}
|
|
44
|
+
credential_dir = "/etc/zypp/credentials.d/"
|
|
45
|
+
credential_files = system.run_command(
|
|
46
|
+
"bash", "-c",
|
|
47
|
+
"test -d '#{credential_dir}' && ls -1 '#{credential_dir}' || echo ''",
|
|
48
|
+
:stdout => :capture
|
|
49
|
+
)
|
|
50
|
+
credential_files.split("\n").each do |f|
|
|
51
|
+
content = system.run_command(
|
|
52
|
+
"cat", "/etc/zypp/credentials.d/#{f}", :stdout => :capture
|
|
53
|
+
)
|
|
54
|
+
content.match(/username=(\w*)\npassword=(\w*)/)
|
|
55
|
+
credentials[f] = {
|
|
56
|
+
username: $1,
|
|
57
|
+
password: $2
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
reps = Nokogiri::XML(xml).xpath("/stream/repo-list/repo")
|
|
62
|
+
summary = "Found #{reps.count} repositories."
|
|
63
|
+
result = reps.map do |rep|
|
|
64
|
+
pri_value = rep["priority"] ? rep["priority"].to_i : prio[rep["alias"]]
|
|
65
|
+
|
|
66
|
+
# NCC
|
|
67
|
+
rep.at_xpath("./url").text.match(/\?credentials=(\w*)/)
|
|
68
|
+
cred_value = $1
|
|
69
|
+
if cred_value && credentials[cred_value]
|
|
70
|
+
username = credentials[cred_value][:username]
|
|
71
|
+
password = credentials[cred_value][:password]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# SCC
|
|
75
|
+
rep.at_xpath("./url").text.match(/(https:\/\/updates.suse.com\/SUSE\/)/)
|
|
76
|
+
scc_url = $1
|
|
77
|
+
cred_value = "SCCcredentials"
|
|
78
|
+
if scc_url && credentials[cred_value]
|
|
79
|
+
username = credentials[cred_value][:username]
|
|
80
|
+
password = credentials[cred_value][:password]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
repository = Repository.new(
|
|
84
|
+
alias: rep["alias"],
|
|
85
|
+
name: rep["name"],
|
|
86
|
+
type: rep["type"],
|
|
87
|
+
url: rep.at_xpath("./url").text,
|
|
88
|
+
enabled: rep["enabled"] == "1",
|
|
89
|
+
autorefresh: rep["autorefresh"] == "1",
|
|
90
|
+
gpgcheck: rep["gpgcheck"] == "1",
|
|
91
|
+
priority: pri_value
|
|
92
|
+
)
|
|
93
|
+
if username && password
|
|
94
|
+
repository[:username] = username
|
|
95
|
+
repository[:password] = password
|
|
96
|
+
end
|
|
97
|
+
repository
|
|
98
|
+
end.sort_by(&:name)
|
|
99
|
+
else
|
|
100
|
+
result = []
|
|
101
|
+
summary = "Found 0 repositories."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
description.repositories = RepositoriesScope.new(result)
|
|
105
|
+
summary
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright (c) 2013-2014 SUSE LLC
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of version 3 of the GNU General Public License as
|
|
5
|
+
# published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program; if not, contact SUSE LLC.
|
|
14
|
+
#
|
|
15
|
+
# To contact SUSE about this file by physical or electronic mail,
|
|
16
|
+
# you may find current contact information at www.suse.com
|
|
17
|
+
|
|
18
|
+
class ServicesInspector < Inspector
|
|
19
|
+
def inspect(system, description, options = {})
|
|
20
|
+
if has_systemd(system)
|
|
21
|
+
result = ServicesScope.new(
|
|
22
|
+
init_system: "systemd",
|
|
23
|
+
services: inspect_systemd_services(system)
|
|
24
|
+
)
|
|
25
|
+
else
|
|
26
|
+
result = ServicesScope.new(
|
|
27
|
+
init_system: "sysvinit",
|
|
28
|
+
services: inspect_sysvinit_services(system)
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
description.services = result
|
|
33
|
+
@summary
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def has_systemd(system)
|
|
39
|
+
system.run_command("systemctl", "--version")
|
|
40
|
+
true
|
|
41
|
+
rescue Cheetah::ExecutionFailed
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def inspect_systemd_services(system)
|
|
46
|
+
output = system.run_command(
|
|
47
|
+
"systemctl",
|
|
48
|
+
"list-unit-files",
|
|
49
|
+
"--type=service,socket",
|
|
50
|
+
:stdout => :capture
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# The first line contains a table header. The last two lines contain a
|
|
54
|
+
# separator and a summary (e.g. "197 unit files listed"). We also filter
|
|
55
|
+
# templates.
|
|
56
|
+
lines = output.lines[1..-3].reject { |l| l =~ /@/ }
|
|
57
|
+
|
|
58
|
+
@summary = "Found #{lines.size} services."
|
|
59
|
+
|
|
60
|
+
services = lines.map do |line|
|
|
61
|
+
name, state = line.split(/\s+/)
|
|
62
|
+
|
|
63
|
+
Service.new(name: name, state: state)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ServiceList.new(services.sort_by(&:name))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def inspect_sysvinit_services(system)
|
|
70
|
+
system.check_requirement("chkconfig", "--help")
|
|
71
|
+
|
|
72
|
+
output = system.run_command(
|
|
73
|
+
"chkconfig",
|
|
74
|
+
"--allservices",
|
|
75
|
+
:stdout => :capture
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@summary = "Found #{output.lines.size} services."
|
|
79
|
+
|
|
80
|
+
services = output.lines.map do |line|
|
|
81
|
+
name, state = line.split(/\s+/)
|
|
82
|
+
|
|
83
|
+
Service.new(name: name, state: state)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
ServiceList.new(services.sort_by(&:name))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Copyright (c) 2013-2014 SUSE LLC
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of version 3 of the GNU General Public License as
|
|
5
|
+
# published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program; if not, contact SUSE LLC.
|
|
14
|
+
#
|
|
15
|
+
# To contact SUSE about this file by physical or electronic mail,
|
|
16
|
+
# you may find current contact information at www.suse.com
|
|
17
|
+
|
|
18
|
+
class UnmanagedFilesInspector < Inspector
|
|
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
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# extract pathes from rpm database into ruby hashes
|
|
30
|
+
def extract_rpm_database(system)
|
|
31
|
+
out = system.run_command(
|
|
32
|
+
["rpm", "-qlav"],
|
|
33
|
+
["sed", "s/^\\(.\\)[^/]* /\\1 /"],
|
|
34
|
+
:stdout => :capture
|
|
35
|
+
)
|
|
36
|
+
files = {}
|
|
37
|
+
dirh = {}
|
|
38
|
+
links = {}
|
|
39
|
+
# result of above command is lines with type as first character, then a path
|
|
40
|
+
# handled types are: "-" for normal files, "d" for directories, "l" for links
|
|
41
|
+
# links have " -> " with link content after path
|
|
42
|
+
out.each_line do |l|
|
|
43
|
+
type = l[0]
|
|
44
|
+
entry = l[2..-2]
|
|
45
|
+
if type == "-"
|
|
46
|
+
files[entry] = ""
|
|
47
|
+
elsif type == "d"
|
|
48
|
+
dirh[entry] = true
|
|
49
|
+
elsif type == "l"
|
|
50
|
+
pair = entry.split(" -> ",2)
|
|
51
|
+
files[pair.first] = pair[1]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
files.each do |f,e|
|
|
55
|
+
dir, sep, file = f.rpartition("/")
|
|
56
|
+
|
|
57
|
+
# make sure that dirs leading to a managed file are treated as if they were
|
|
58
|
+
# in rpm database, otherwise we cannot exclude whole unmanaged trees
|
|
59
|
+
while( !dirh.has_key?(dir) && dir.size > 1 )
|
|
60
|
+
dirh[dir] = false
|
|
61
|
+
dir=dir[0..dir.rindex("/") - 1]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# put links to a managed directory also into directory hash
|
|
65
|
+
if !e.empty? && dirh.has_key?(e)
|
|
66
|
+
dirh[f] = false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
Machinery.logger.debug "extract_rpm_database files:#{files.size} dirs:#{dirh.size}"
|
|
70
|
+
[files, dirh]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def check_consistency(files, dirh)
|
|
74
|
+
p "files=#{files.size} dirs=#{dirh.size}"
|
|
75
|
+
p "dirs in rpmdb=#{dirh.select{|k,e| e}.size} added:#{dirh.select{|k,e| !e}.size}"
|
|
76
|
+
list = files.select { |f| !f.start_with?("/") }
|
|
77
|
+
p "should not happen non-abs file:#{list}" unless list.empty?
|
|
78
|
+
list = dirh.select { |f| !f.start_with?("/") }
|
|
79
|
+
p "should not happen non-abs dirs:#{list}" unless list.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def extract_unmanaged_files(system, description, files, trees, excluded, store_name)
|
|
83
|
+
description.remove_file_store(store_name)
|
|
84
|
+
description.initialize_file_store(store_name)
|
|
85
|
+
store_path = description.file_store(store_name)
|
|
86
|
+
|
|
87
|
+
archive_path = File.join(store_path, "files.tgz")
|
|
88
|
+
system.create_archive(files.join("\0"), archive_path, excluded)
|
|
89
|
+
|
|
90
|
+
trees.each do |tree|
|
|
91
|
+
tree_name = File.basename(tree)
|
|
92
|
+
parent_dir = File.dirname(tree)
|
|
93
|
+
sub_dir = File.join("trees", parent_dir)
|
|
94
|
+
|
|
95
|
+
description.create_file_store_sub_dir(store_name, sub_dir)
|
|
96
|
+
archive_path = File.join(store_path, sub_dir, "#{tree_name}.tgz")
|
|
97
|
+
system.create_archive(tree, archive_path, excluded)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# extract metadata from extracted tar archives and put data into Object
|
|
102
|
+
def extract_tar_metadata(osl, destdir)
|
|
103
|
+
if Dir.exists?(destdir)
|
|
104
|
+
tarballs = [File.join(destdir, "files.tgz")]
|
|
105
|
+
osl.select{ |os| os.type == "dir" }.map(&:name).each do |d|
|
|
106
|
+
base = File.dirname(d)
|
|
107
|
+
tarballs << File.join(destdir, "trees", base, "#{File.basename(d)}.tgz")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
tarballs.each do | archive|
|
|
111
|
+
files = Tarball.new(archive).list
|
|
112
|
+
|
|
113
|
+
files.each do |file|
|
|
114
|
+
os = osl.find do |o|
|
|
115
|
+
o.name == "/#{file[:path]}#{file[:type] == :dir ? "/" : ""}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
os.user = file[:user]
|
|
119
|
+
os.group = file[:group]
|
|
120
|
+
if file[:type] != :link
|
|
121
|
+
os.size = file[:size]
|
|
122
|
+
os.mode = file[:mode]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# unmanaged dirs are trees and only have one entry in the manifest
|
|
126
|
+
if os.type == "dir"
|
|
127
|
+
os.size = files.map { |d| d[:size] }.reduce(:+)
|
|
128
|
+
os.files = files.size
|
|
129
|
+
break
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
osl
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# find paths below dir until a certain depth is reached
|
|
138
|
+
def get_find_data( system, dir, depth )
|
|
139
|
+
dep = depth - 1
|
|
140
|
+
files = {}
|
|
141
|
+
dirs = {}
|
|
142
|
+
excluded_files = []
|
|
143
|
+
|
|
144
|
+
# compute command line
|
|
145
|
+
cmd = "find #{dir.shellescape} -xdev -maxdepth 1 -maxdepth #{depth} "
|
|
146
|
+
cmd += '-printf "%y\0%P\0%l\0"'
|
|
147
|
+
|
|
148
|
+
# Cheetah seems to be unable to handle binary zeroes "\0" in parameters
|
|
149
|
+
# misuse stdin for command
|
|
150
|
+
#
|
|
151
|
+
# Filenames can contain invalid UTF-8 characters, so we treat the data as
|
|
152
|
+
# binary information first while splitting the raw output and then convert
|
|
153
|
+
# the separate strings to UTF-8, replacing invalid characters with the
|
|
154
|
+
# "REPLACEMENT CHARACTER" (U+FFFD). That way we have both the raw data
|
|
155
|
+
# (which is needed in order to be able to access the files) and the cleaned
|
|
156
|
+
# string which can be safely used.
|
|
157
|
+
out = system.run_command(
|
|
158
|
+
"/bin/bash",
|
|
159
|
+
{
|
|
160
|
+
:stdin => cmd,
|
|
161
|
+
:stdout => :capture,
|
|
162
|
+
:disable_logging => true
|
|
163
|
+
}
|
|
164
|
+
).force_encoding("binary")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# find creates three field per path
|
|
168
|
+
out.split("\0", -1).each_slice(3) do |type, raw_path, raw_link|
|
|
169
|
+
next unless raw_path && !raw_path.empty?
|
|
170
|
+
|
|
171
|
+
path = scrub(raw_path)
|
|
172
|
+
link = scrub(raw_link)
|
|
173
|
+
|
|
174
|
+
if [path, link].any? { |f| f.include?("\uFFFD") }
|
|
175
|
+
broken_names = []
|
|
176
|
+
if path.include?("\uFFFD")
|
|
177
|
+
broken_names << "filename '#{path}'"
|
|
178
|
+
excluded_files << raw_path
|
|
179
|
+
end
|
|
180
|
+
if link.include?("\uFFFD")
|
|
181
|
+
broken_names << "link target '#{link}'"
|
|
182
|
+
excluded_files << raw_link
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
warning = broken_names.join(" and ")
|
|
186
|
+
warning += " contain#{"s" if broken_names.length == 1}"
|
|
187
|
+
warning += " invalid UTF-8 characters. Skipping."
|
|
188
|
+
warning[0] = warning[0].upcase
|
|
189
|
+
|
|
190
|
+
Machinery.logger.warn(warning)
|
|
191
|
+
Machinery::Ui.warn(warning)
|
|
192
|
+
next
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
files[path] = link if type == "l"
|
|
196
|
+
files[path] = "" if type == "f"
|
|
197
|
+
|
|
198
|
+
# dirs at maxdepth could be non-leafs all othere are leafs
|
|
199
|
+
dirs[path] = path.count("/") == dep if type == "d"
|
|
200
|
+
end
|
|
201
|
+
Machinery.logger.debug "get_find_data dir:#{dir} depth:#{depth} file:#{files.size} dirs:#{dirs.size} excluded:#{excluded_files}"
|
|
202
|
+
[files, dirs, excluded_files]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def max_depth
|
|
206
|
+
6
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def start_depth
|
|
210
|
+
3
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def inspect(system, description, options = nil)
|
|
214
|
+
do_extract = options && options[:extract_unmanaged_files]
|
|
215
|
+
check_requirements(system, do_extract)
|
|
216
|
+
|
|
217
|
+
tmp_file_store = "unmanaged_files.tmp"
|
|
218
|
+
final_file_store = "unmanaged_files"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
ignore_list = [
|
|
222
|
+
"tmp",
|
|
223
|
+
"var/tmp",
|
|
224
|
+
"lost+found",
|
|
225
|
+
"var/run",
|
|
226
|
+
"var/lib/rpm",
|
|
227
|
+
".snapshots",
|
|
228
|
+
description.store.base_path.sub(/^\//, ""),
|
|
229
|
+
"proc"
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
# Information about users and groups are extracted by the according inspector
|
|
233
|
+
ignore_list += [
|
|
234
|
+
"etc/passwd",
|
|
235
|
+
"etc/shadow",
|
|
236
|
+
"etc/group"
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# Information about services is extracted by the ServicesInspector, so
|
|
240
|
+
# we ignore the links representing the same information when inspecting
|
|
241
|
+
# unmanaged files.
|
|
242
|
+
ignore_list += [
|
|
243
|
+
"etc/init.d/boot.d",
|
|
244
|
+
"etc/init.d/rc0.d",
|
|
245
|
+
"etc/init.d/rc1.d",
|
|
246
|
+
"etc/init.d/rc2.d",
|
|
247
|
+
"etc/init.d/rc3.d",
|
|
248
|
+
"etc/init.d/rc4.d",
|
|
249
|
+
"etc/init.d/rc5.d",
|
|
250
|
+
"etc/init.d/rc6.d",
|
|
251
|
+
"etc/init.d/rcS.d"
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
rpm_files, rpm_dirs = extract_rpm_database(system)
|
|
255
|
+
|
|
256
|
+
mount_points = MountPoints.new(system)
|
|
257
|
+
mounts = mount_points.local
|
|
258
|
+
unmanaged_files = []
|
|
259
|
+
unmanaged_trees = []
|
|
260
|
+
excluded_files = []
|
|
261
|
+
unmanaged_links = {}
|
|
262
|
+
remote_dirs = mount_points.remote
|
|
263
|
+
ignore_list.each do |ignore|
|
|
264
|
+
remote_dirs.delete_if { |e| e.start_with?(File.join("/", ignore, "/")) }
|
|
265
|
+
end
|
|
266
|
+
dirs_todo = [ "/" ]
|
|
267
|
+
start = start_depth
|
|
268
|
+
max = max_depth
|
|
269
|
+
find_count = 0
|
|
270
|
+
sub_tree_containing_remote_fs = []
|
|
271
|
+
excluded_files += remote_dirs
|
|
272
|
+
|
|
273
|
+
if !remote_dirs.empty?
|
|
274
|
+
warning = "The content of the following remote directories is ignored: #{remote_dirs.uniq.join(", ")}."
|
|
275
|
+
Machinery.logger.warn(warning)
|
|
276
|
+
Machinery::Ui.warn(warning)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
while !dirs_todo.empty?
|
|
280
|
+
find_dir = dirs_todo.first
|
|
281
|
+
|
|
282
|
+
# determine files and directories below find_dir until a certain depth
|
|
283
|
+
depth = mounts.include?(find_dir) ? start : max
|
|
284
|
+
files, dirs, excluded = get_find_data( system, find_dir, depth )
|
|
285
|
+
excluded_files += excluded
|
|
286
|
+
find_count += 1
|
|
287
|
+
find_dir += "/" if find_dir.size > 1
|
|
288
|
+
if !mounts.empty?
|
|
289
|
+
# force all mount points to be non-leave directories (find is called with -xdev)
|
|
290
|
+
mounts.each do |mp|
|
|
291
|
+
dirs[mp] = true if dirs.has_key?(mp)
|
|
292
|
+
end
|
|
293
|
+
mounts.reject!{ |mp| dirs.has_key?(mp) }
|
|
294
|
+
end
|
|
295
|
+
if find_dir == "/"
|
|
296
|
+
ignore_list.each do |d|
|
|
297
|
+
td_with_slash = d + "/"
|
|
298
|
+
dirs.reject! {|p| p == d || p.start_with?(td_with_slash) }
|
|
299
|
+
files.reject! {|p| p == d || p.start_with?(td_with_slash) }
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
managed, unmanaged = dirs.keys.partition{ |d| rpm_dirs.has_key?(find_dir + d) }
|
|
303
|
+
|
|
304
|
+
# unmanaged dirs lead to removal of files and dirs below that dir
|
|
305
|
+
while !unmanaged.empty?
|
|
306
|
+
dir = unmanaged.shift
|
|
307
|
+
|
|
308
|
+
# save into list of unmanaged trees
|
|
309
|
+
if !remote_dirs.include?(find_dir + dir)
|
|
310
|
+
unmanaged_trees << find_dir + dir
|
|
311
|
+
end
|
|
312
|
+
dir = File.join(dir, "/")
|
|
313
|
+
|
|
314
|
+
# find sub trees containing remote file systems
|
|
315
|
+
remote_dirs.each do |remote_dir|
|
|
316
|
+
if unmanaged.include?(remote_dir[1..-1])
|
|
317
|
+
sub_tree_containing_remote_fs << remote_dir
|
|
318
|
+
unmanaged = unmanaged.drop_while{ |d| d.start_with?(remote_dir[1...-1]) }
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
# remove all possible further references starting with this subdir
|
|
322
|
+
unmanaged = unmanaged.drop_while{ |d| d.start_with?(dir) }
|
|
323
|
+
files.reject!{ |d| d.start_with?(dir) }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# remove all (currently known) leaf directories
|
|
327
|
+
managed.select!{ |d| dirs[d] }
|
|
328
|
+
|
|
329
|
+
# update list for still to handle directories
|
|
330
|
+
dirs_todo.shift
|
|
331
|
+
managed.map!{ |d| find_dir + d }
|
|
332
|
+
dirs_todo.push(*managed)
|
|
333
|
+
|
|
334
|
+
# unmanaged files are simply stored
|
|
335
|
+
managed, unmanaged = files.keys.partition{ |d| rpm_files.has_key?(find_dir + d) }
|
|
336
|
+
links = unmanaged.reject { |d| files[d].empty? }
|
|
337
|
+
unmanaged.map!{ |d| find_dir + d }
|
|
338
|
+
unmanaged_files.push(*unmanaged)
|
|
339
|
+
links.each { |d| unmanaged_links[find_dir + d] = "" }
|
|
340
|
+
end
|
|
341
|
+
Machinery.logger.debug "inspect unmanaged files find calls:#{find_count} files:#{unmanaged_files.size} trees:#{unmanaged_trees.size}"
|
|
342
|
+
begin
|
|
343
|
+
if do_extract
|
|
344
|
+
extract_unmanaged_files(system, description, unmanaged_files, unmanaged_trees, excluded_files, tmp_file_store)
|
|
345
|
+
else
|
|
346
|
+
description.remove_file_store(final_file_store)
|
|
347
|
+
end
|
|
348
|
+
osl = unmanaged_files.map do |p|
|
|
349
|
+
type = unmanaged_links.has_key?(p) ? "link" : "file"
|
|
350
|
+
UnmanagedFile.new(name: p, type: type)
|
|
351
|
+
end
|
|
352
|
+
osl += unmanaged_trees.map { |p| UnmanagedFile.new( name: p + "/", type: "dir") }
|
|
353
|
+
if do_extract
|
|
354
|
+
osl = extract_tar_metadata(osl, description.file_store(tmp_file_store))
|
|
355
|
+
description.remove_file_store(final_file_store)
|
|
356
|
+
description.rename_file_store(
|
|
357
|
+
tmp_file_store, final_file_store
|
|
358
|
+
)
|
|
359
|
+
end
|
|
360
|
+
rescue SignalException => e
|
|
361
|
+
# Handle SIGHUP(1), SIGINT(2) and SIGTERM(15) gracefully
|
|
362
|
+
if [1, 2, 15].include?(e.signo)
|
|
363
|
+
Machinery::Ui.warn "Interrupted by user. The partly extracted unmanaged-files are available" \
|
|
364
|
+
" under '#{description.file_store(tmp_file_store)}'"
|
|
365
|
+
end
|
|
366
|
+
raise
|
|
367
|
+
end
|
|
368
|
+
remote_dirs.each do |remote_dir|
|
|
369
|
+
osl << UnmanagedFile.new( name: remote_dir + "/", type: "remote_dir")
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
summary = "#{do_extract ? "Extracted" : "Found"} #{osl.size} unmanaged files and trees."
|
|
373
|
+
description["unmanaged_files"] = UnmanagedFilesScope.new(
|
|
374
|
+
extracted: !!do_extract,
|
|
375
|
+
files: UnmanagedFileList.new(osl.sort_by(&:name))
|
|
376
|
+
)
|
|
377
|
+
summary
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private
|
|
381
|
+
|
|
382
|
+
# Implementation of String#scrub for Ruby < 2.1. Assumes the string is in
|
|
383
|
+
# UTF-8.
|
|
384
|
+
def scrub(s)
|
|
385
|
+
# We have a string in UTF-8 with possible invalid byte sequences. It turns
|
|
386
|
+
# out that String#encode can remove these sequences when given appropriate
|
|
387
|
+
# options, but just converting into UTF-8 would be a no-op. So let's convert
|
|
388
|
+
# into UTF-16 (which has the same character set as UTF-8) and back.
|
|
389
|
+
#
|
|
390
|
+
# See also: http://stackoverflow.com/a/21315619
|
|
391
|
+
s.dup.force_encoding("UTF-8").encode("UTF-16", invalid: :replace).encode("UTF-8")
|
|
392
|
+
end
|
|
393
|
+
end
|