machinery-tool 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS +18 -0
- data/html/assets/machinery.css +20 -1
- data/html/assets/machinery.js +53 -26
- data/html/index.html.haml +69 -46
- data/lib/analyze_config_file_diffs_task.rb +1 -1
- data/lib/array.rb +36 -14
- data/lib/cli.rb +45 -50
- data/lib/compare_task.rb +1 -1
- data/lib/config.rb +8 -2
- data/lib/config_base.rb +14 -1
- data/lib/element_filter.rb +48 -11
- data/lib/exceptions.rb +5 -0
- data/lib/filter.rb +63 -14
- data/lib/filter_option_parser.rb +83 -0
- data/lib/generate_html_task.rb +3 -1
- data/lib/hint.rb +36 -18
- data/lib/html.rb +1 -0
- data/lib/inspect_task.rb +12 -21
- data/lib/inspector.rb +13 -6
- data/lib/kiwi_config.rb +17 -14
- data/lib/list_task.rb +5 -1
- data/lib/local_system.rb +3 -4
- data/lib/logged_cheetah.rb +3 -1
- data/lib/machinery.rb +1 -0
- data/lib/object.rb +24 -19
- data/lib/scope_file_store.rb +3 -1
- data/lib/show_task.rb +5 -7
- data/lib/system_description.rb +11 -12
- data/lib/system_description_store.rb +1 -1
- data/lib/ui.rb +44 -36
- data/lib/upgrade_format_task.rb +4 -1
- data/lib/version.rb +1 -1
- data/man/generated/machinery.1.gz +0 -0
- data/man/generated/machinery.1.html +7 -2
- data/plugins/inspect/changed_managed_files_inspector.rb +13 -6
- data/plugins/inspect/config_files_inspector.rb +27 -20
- data/plugins/inspect/groups_inspector.rb +12 -4
- data/plugins/inspect/os_inspector.rb +29 -22
- data/plugins/inspect/packages_inspector.rb +13 -5
- data/plugins/inspect/patterns_inspector.rb +24 -10
- data/plugins/inspect/repositories_inspector.rb +19 -15
- data/plugins/inspect/services_inspector.rb +28 -22
- data/plugins/inspect/unmanaged_files_inspector.rb +42 -33
- data/plugins/inspect/users_inspector.rb +13 -5
- data/plugins/model/os_model.rb +1 -1
- metadata +3 -2
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright (c) 2013-2015 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
|
+
# This class takes care of transforming the user-provided filter options into
|
19
|
+
# actual Filter objects.
|
20
|
+
class FilterOptionParser
|
21
|
+
class <<self
|
22
|
+
def parse(command, options, global_options)
|
23
|
+
filter = Filter.from_default_definition(command)
|
24
|
+
|
25
|
+
definitions = skip_files_definitions(options.delete("skip-files"))
|
26
|
+
definitions += exclude_definitions(global_options["exclude"])
|
27
|
+
|
28
|
+
definitions.map! { |definition| definition.gsub("\\@", "@") } # Unescape escaped @s
|
29
|
+
definitions.each do |definition|
|
30
|
+
filter.add_element_filter_from_definition(definition)
|
31
|
+
end
|
32
|
+
|
33
|
+
filter
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def exclude_definitions(exclude)
|
39
|
+
return [] if !exclude
|
40
|
+
|
41
|
+
filters = exclude.scan(/(@[^,]+)|\"([^,]+?=[^\"]+)\"|([^,]+=[^=]+)$|([^,]+=[^,]+)/).
|
42
|
+
map(&:compact).flat_map do |filter_definition|
|
43
|
+
if filter_definition[0].start_with?("@")
|
44
|
+
expand_filter_file(filter_definition[0])
|
45
|
+
else
|
46
|
+
filter_definition
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
filters.reject!(&:empty?) # Ignore empty filters
|
51
|
+
filters
|
52
|
+
end
|
53
|
+
|
54
|
+
def skip_files_definitions(skip_files)
|
55
|
+
return [] if !skip_files
|
56
|
+
|
57
|
+
files = skip_files.split(/(?<!\\),/) # Do not split on escaped commas
|
58
|
+
files = files.flat_map do |file|
|
59
|
+
if file.start_with?("@")
|
60
|
+
expand_filter_file(file)
|
61
|
+
else
|
62
|
+
file
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
files.reject!(&:empty?) # Ignore empty filters
|
67
|
+
files.map! { |file| file.chomp("/") } # List directories without the trailing /, in order to
|
68
|
+
# not confuse the unmanaged files inspector
|
69
|
+
files.map { |file| "/unmanaged_files/files/name=#{file}" }
|
70
|
+
end
|
71
|
+
|
72
|
+
def expand_filter_file(path)
|
73
|
+
filename = File.expand_path(path[1..-1])
|
74
|
+
|
75
|
+
if !File.exists?(filename)
|
76
|
+
raise Machinery::Errors::MachineryError.new(
|
77
|
+
"The filter file '#{filename}' does not exist."
|
78
|
+
)
|
79
|
+
end
|
80
|
+
File.read(filename).lines.map(&:strip)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/generate_html_task.rb
CHANGED
data/lib/hint.rb
CHANGED
@@ -16,29 +16,47 @@
|
|
16
16
|
# you may find current contact information at www.suse.com
|
17
17
|
|
18
18
|
class Hint
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
class << self
|
20
|
+
def print(method, options = {})
|
21
|
+
return if !Machinery::Config.new.hints
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
end
|
23
|
+
Machinery::Ui.puts to_string(method, options)
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
"#{$0} show --scope config-files --show-diffs #{options[:name]}"
|
30
|
-
end
|
26
|
+
def to_string(method, options = {})
|
27
|
+
return "" if !Machinery::Config.new.hints
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
"\nHint: #{send(method, options)}\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def get_started(_options)
|
35
|
+
"You can get started by inspecting a system. Run:\n#{$0} inspect HOSTNAME"
|
36
|
+
end
|
37
|
+
|
38
|
+
def upgrade_format_force(options)
|
39
|
+
"To force an upgrade of system descriptions run:\n" \
|
40
|
+
"#{$0} upgrade-format --force #{options[:name]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def show_data(options)
|
44
|
+
"To show the data of the system you just inspected run:\n#{$0} show #{options[:name]}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def show_analyze_data(options)
|
48
|
+
"To show the config file diffs you just created run:\n" \
|
49
|
+
"#{$0} show --scope config-files --show-diffs #{options[:name]}"
|
50
|
+
end
|
36
51
|
|
37
|
-
|
52
|
+
def do_complete_inspection(options)
|
53
|
+
"To do a full inspection containing all scopes and to extract files run:\n" \
|
54
|
+
"#{$0} inspect #{options[:host]} --name #{options[:name]} --extract-files"
|
55
|
+
end
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
|
57
|
+
def upgrade_system_description(_options)
|
58
|
+
"To upgrade all system descriptions run:\n" \
|
59
|
+
"#{$0} upgrade-format --all"
|
42
60
|
end
|
43
61
|
end
|
44
62
|
end
|
data/lib/html.rb
CHANGED
data/lib/inspect_task.rb
CHANGED
@@ -58,16 +58,6 @@ class InspectTask
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def adapt_filter_in_metadata(filter_in_metadata, scope, filter)
|
62
|
-
filter_in_metadata.element_filters.
|
63
|
-
reject! { |path, _filter| path.start_with?("/#{scope}") }
|
64
|
-
filter.element_filters.
|
65
|
-
select { |path, _filter| path.start_with?("/#{scope}") }.
|
66
|
-
each do |_path, element_filter|
|
67
|
-
filter_in_metadata.add_element_filter(element_filter)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
61
|
def build_description(store, name, system, scopes, filter, options)
|
72
62
|
begin
|
73
63
|
description = SystemDescription.load(name, store)
|
@@ -83,16 +73,17 @@ class InspectTask
|
|
83
73
|
|
84
74
|
failed_inspections = {}
|
85
75
|
|
86
|
-
|
87
|
-
filter_in_metadata = description.filters["inspect"]
|
88
|
-
else
|
89
|
-
filter_in_metadata = Filter.new
|
90
|
-
end
|
76
|
+
effective_filter = Filter.new(description.filter_definitions("inspect"))
|
91
77
|
|
92
|
-
scopes.
|
78
|
+
scopes.each do |scope|
|
79
|
+
inspector = Inspector.for(scope).new(system, description)
|
93
80
|
Machinery::Ui.puts "Inspecting #{Machinery::Ui.internal_scope_list_to_string(inspector.scope)}..."
|
81
|
+
|
82
|
+
element_filters = filter.element_filters_for_scope(scope)
|
83
|
+
effective_filter.set_element_filters_for_scope(scope, element_filters)
|
84
|
+
|
94
85
|
begin
|
95
|
-
|
86
|
+
inspector.inspect(effective_filter, options)
|
96
87
|
rescue Machinery::Errors::MachineryError => e
|
97
88
|
Machinery::Ui.puts " -> Inspection failed!"
|
98
89
|
failed_inspections[inspector.scope] = e
|
@@ -100,13 +91,13 @@ class InspectTask
|
|
100
91
|
end
|
101
92
|
description[inspector.scope].set_metadata(timestring, host)
|
102
93
|
|
103
|
-
adapt_filter_in_metadata(filter_in_metadata, inspector.scope, filter)
|
104
|
-
|
105
94
|
if !description.attributes.empty?
|
106
|
-
|
95
|
+
effective_filter.apply!(description)
|
96
|
+
description.set_filter_definitions("inspect", effective_filter.to_array)
|
107
97
|
description.save
|
108
98
|
end
|
109
|
-
|
99
|
+
|
100
|
+
Machinery::Ui.puts " -> " + inspector.summary
|
110
101
|
end
|
111
102
|
|
112
103
|
return description, failed_inspections
|
data/lib/inspector.rb
CHANGED
@@ -33,6 +33,7 @@
|
|
33
33
|
# The description object can also be used to store files in the description.
|
34
34
|
class Inspector
|
35
35
|
abstract_method :inspect
|
36
|
+
abstract_method :summary
|
36
37
|
|
37
38
|
@inspectors = []
|
38
39
|
|
@@ -44,22 +45,28 @@ class Inspector
|
|
44
45
|
def for(scope)
|
45
46
|
class_name = "#{scope.split("_").map(&:capitalize).join}Inspector"
|
46
47
|
|
47
|
-
Object.const_get(class_name)
|
48
|
+
Object.const_get(class_name) if Object.const_defined?(class_name)
|
48
49
|
end
|
49
50
|
|
50
51
|
def all
|
51
|
-
@inspectors
|
52
|
+
@inspectors
|
52
53
|
end
|
53
54
|
|
54
55
|
def all_scopes
|
55
56
|
all.map(&:scope)
|
56
57
|
end
|
58
|
+
|
59
|
+
def scope
|
60
|
+
# Return the un-camelcased name of the inspector,
|
61
|
+
# e.g. "foo_bar" for "FooBarInspector"
|
62
|
+
scope = self.name.match(/^(.*)Inspector$/)[1]
|
63
|
+
scope.gsub(/([^A-Z])([A-Z])/, "\\1_\\2").downcase
|
64
|
+
end
|
57
65
|
end
|
58
66
|
|
67
|
+
attr_accessor :system, :description
|
68
|
+
|
59
69
|
def scope
|
60
|
-
|
61
|
-
# e.g. "foo_bar" for "FooBarInspector"
|
62
|
-
scope = self.class.name.match(/^(.*)Inspector$/)[1]
|
63
|
-
scope.gsub(/([^A-Z])([A-Z])/, "\\1_\\2").downcase
|
70
|
+
self.class.scope
|
64
71
|
end
|
65
72
|
end
|
data/lib/kiwi_config.rb
CHANGED
@@ -148,20 +148,23 @@ suseImportBuildKey
|
|
148
148
|
suseConfig
|
149
149
|
EOF
|
150
150
|
case @system_description.os
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
151
|
+
when OsOpenSuse13_2
|
152
|
+
boot = "vmxboot/suse-13.2"
|
153
|
+
bootloader = "grub2"
|
154
|
+
when OsOpenSuse13_1
|
155
|
+
boot = "vmxboot/suse-13.1"
|
156
|
+
bootloader = "grub2"
|
157
|
+
when OsSles12
|
158
|
+
boot = "vmxboot/suse-SLES12"
|
159
|
+
bootloader = "grub2"
|
160
|
+
when OsSles11
|
161
|
+
boot = "vmxboot/suse-SLES11"
|
162
|
+
bootloader = "grub"
|
163
|
+
else
|
164
|
+
raise Machinery::Errors::ExportFailed.new(
|
165
|
+
"Export is not possible because the operating system " \
|
166
|
+
"'#{@system_description.os.display_name}' is not supported."
|
167
|
+
)
|
165
168
|
end
|
166
169
|
|
167
170
|
builder = Nokogiri::XML::Builder.new do |xml|
|
data/lib/list_task.rb
CHANGED
@@ -17,7 +17,8 @@
|
|
17
17
|
|
18
18
|
class ListTask
|
19
19
|
def list(store, options = {})
|
20
|
-
descriptions = store.list
|
20
|
+
descriptions = store.list
|
21
|
+
has_incompatible_version = false
|
21
22
|
|
22
23
|
descriptions.each do |name|
|
23
24
|
begin
|
@@ -28,6 +29,7 @@ class ListTask
|
|
28
29
|
elsif e.format_version < SystemDescription::CURRENT_FORMAT_VERSION
|
29
30
|
show_error("#{name}: format version #{e.format_version}, " \
|
30
31
|
"needs to be upgraded.\n", options)
|
32
|
+
has_incompatible_version = true
|
31
33
|
else
|
32
34
|
show_error("#{name}: format version #{e.format_version}. " \
|
33
35
|
"Please upgrade Machinery to the latest version.\n", options)
|
@@ -77,6 +79,8 @@ class ListTask
|
|
77
79
|
Machinery::Ui.puts " #{name}:\n * " + scopes .join("\n * ") + "\n\n"
|
78
80
|
end
|
79
81
|
end
|
82
|
+
|
83
|
+
Hint.print(:upgrade_system_description) if has_incompatible_version
|
80
84
|
end
|
81
85
|
|
82
86
|
private
|
data/lib/local_system.rb
CHANGED
@@ -21,10 +21,9 @@ class LocalSystem < System
|
|
21
21
|
class << self
|
22
22
|
def os
|
23
23
|
if !@@os
|
24
|
-
description = SystemDescription.new("localhost",
|
25
|
-
|
26
|
-
inspector
|
27
|
-
inspector.inspect(System.for("localhost"), description, nil)
|
24
|
+
description = SystemDescription.new("localhost", SystemDescriptionMemoryStore.new)
|
25
|
+
inspector = OsInspector.new(System.for("localhost"), description)
|
26
|
+
inspector.inspect(nil)
|
28
27
|
@@os = description.os
|
29
28
|
end
|
30
29
|
@@os
|
data/lib/logged_cheetah.rb
CHANGED
data/lib/machinery.rb
CHANGED
@@ -89,6 +89,7 @@ require_relative "json_validation_error_cleaner"
|
|
89
89
|
require_relative "file_validator"
|
90
90
|
require_relative "element_filter"
|
91
91
|
require_relative "filter"
|
92
|
+
require_relative "filter_option_parser"
|
92
93
|
|
93
94
|
Dir[File.join(Machinery::ROOT, "plugins", "**", "*.rb")].each { |f| require(f) }
|
94
95
|
|
data/lib/object.rb
CHANGED
@@ -23,31 +23,34 @@ module Machinery
|
|
23
23
|
@property_classes[name.to_s] = options[:class]
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
def convert_element(key, value)
|
27
|
+
property_class = @property_classes[key.to_s] if @property_classes
|
28
|
+
if property_class
|
29
|
+
value.is_a?(property_class) ? value : property_class.from_json(value)
|
30
|
+
else
|
31
|
+
case value
|
32
|
+
when ::Array
|
33
|
+
Machinery::Array.from_json(value)
|
34
|
+
when Hash
|
35
|
+
Machinery::Object.from_json(value)
|
32
36
|
else
|
33
|
-
|
34
|
-
when ::Array
|
35
|
-
Machinery::Array.from_json(value)
|
36
|
-
when Hash
|
37
|
-
Machinery::Object.from_json(value)
|
38
|
-
else
|
39
|
-
value
|
40
|
-
end
|
37
|
+
value
|
41
38
|
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def convert_raw_hash(hash)
|
43
|
+
return nil unless hash
|
42
44
|
|
43
|
-
|
45
|
+
entries = hash.map do |key, value|
|
46
|
+
[key, convert_element(key, value)]
|
44
47
|
end
|
45
48
|
|
46
49
|
Hash[entries]
|
47
50
|
end
|
48
51
|
|
49
|
-
def from_json(
|
50
|
-
new(
|
52
|
+
def from_json(json_object)
|
53
|
+
new(json_object)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
@@ -58,6 +61,7 @@ module Machinery
|
|
58
61
|
end
|
59
62
|
|
60
63
|
def set_attributes(attrs)
|
64
|
+
attrs = self.class.convert_raw_hash(attrs) if attrs.is_a?(Hash)
|
61
65
|
@attributes = attrs.inject({}) do |attributes, (key, value)|
|
62
66
|
key = key.to_s if key.is_a?(Symbol)
|
63
67
|
|
@@ -84,7 +88,7 @@ module Machinery
|
|
84
88
|
end
|
85
89
|
|
86
90
|
def []=(key, value)
|
87
|
-
@attributes[key.to_s] = value
|
91
|
+
@attributes[key.to_s] = self.class.convert_element(key, value)
|
88
92
|
end
|
89
93
|
|
90
94
|
def empty?
|
@@ -96,7 +100,8 @@ module Machinery
|
|
96
100
|
if args.size != 1
|
97
101
|
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)"
|
98
102
|
end
|
99
|
-
|
103
|
+
key = name.to_s[0..-2]
|
104
|
+
@attributes[key] = self.class.convert_element(key, args.first)
|
100
105
|
else
|
101
106
|
if @attributes.has_key?(name.to_s)
|
102
107
|
if !args.empty?
|