machinery-tool 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,228 @@
|
|
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 SystemDescription < Machinery::Object
|
19
|
+
CURRENT_FORMAT_VERSION = 2
|
20
|
+
EXTRACTABLE_SCOPES = [
|
21
|
+
"changed_managed_files",
|
22
|
+
"config_files",
|
23
|
+
"unmanaged_files"
|
24
|
+
]
|
25
|
+
|
26
|
+
attr_accessor :name
|
27
|
+
attr_accessor :store
|
28
|
+
attr_accessor :format_version
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def from_json(name, json, store = nil)
|
32
|
+
begin
|
33
|
+
json_hash = JSON.parse(json)
|
34
|
+
rescue JSON::ParserError => e
|
35
|
+
lines = e.message.split("\n")
|
36
|
+
error_pos = json.split("\n").length - lines.length + 2
|
37
|
+
block_end = lines.index { |l| l =~ / [\}\]],?$/ }
|
38
|
+
|
39
|
+
# remove needless json error information
|
40
|
+
lines[0].gsub!(/^\d+: (.*)$/, "\\1")
|
41
|
+
json_error = lines[0..block_end].join("\n")
|
42
|
+
|
43
|
+
if error_pos == 1
|
44
|
+
json_error = "An opening bracket, a comma or quotation is missing " \
|
45
|
+
"in one of the global scope definitions or in the meta section. " \
|
46
|
+
"Unlike issues with the elements of the scopes, our JSON parser " \
|
47
|
+
"isn't able to locate issues like these."
|
48
|
+
error_pos = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
error = "The JSON data of the system description '#{name}' " \
|
52
|
+
"couldn't be parsed. The following error occured"
|
53
|
+
error += " around line #{error_pos}" if error_pos
|
54
|
+
error += " in file '#{store.manifest_path(name)}'" if store
|
55
|
+
error += ":\n\n#{json_error}"
|
56
|
+
|
57
|
+
raise Machinery::Errors::SystemDescriptionError.new(error)
|
58
|
+
end
|
59
|
+
|
60
|
+
if compatible_json?(json_hash)
|
61
|
+
SystemDescriptionValidator.new(self).validate_json(json_hash)
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
description = self.new(name, self.create_attrs(json_hash), store)
|
66
|
+
rescue NameError
|
67
|
+
raise Machinery::Errors::SystemDescriptionIncompatible.new(name)
|
68
|
+
end
|
69
|
+
|
70
|
+
json_format_version = json_hash["meta"]["format_version"] if json_hash["meta"]
|
71
|
+
description.format_version = json_format_version
|
72
|
+
|
73
|
+
description
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_attrs(hash)
|
77
|
+
entries = hash.map do |key, value|
|
78
|
+
next if key == "meta"
|
79
|
+
|
80
|
+
class_name = "#{key.split("_").map(&:capitalize).join}Scope"
|
81
|
+
value_converted = Object.const_get(class_name).from_json(value)
|
82
|
+
|
83
|
+
# Set metadata
|
84
|
+
if hash["meta"] && hash["meta"][key]
|
85
|
+
value_converted.meta = Machinery::Object.from_json(hash["meta"][key])
|
86
|
+
end
|
87
|
+
|
88
|
+
[key, value_converted]
|
89
|
+
end.compact
|
90
|
+
|
91
|
+
Hash[entries]
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def compatible_json?(json)
|
97
|
+
json.is_a?(Hash) &&
|
98
|
+
json["meta"].is_a?(Hash) &&
|
99
|
+
json["meta"]["format_version"] == CURRENT_FORMAT_VERSION
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize(name, hash = {}, store = nil)
|
104
|
+
@name = name
|
105
|
+
@store = store
|
106
|
+
@format_version = CURRENT_FORMAT_VERSION
|
107
|
+
|
108
|
+
super(hash)
|
109
|
+
end
|
110
|
+
|
111
|
+
def compatible?
|
112
|
+
!format_version.nil? &&
|
113
|
+
format_version == SystemDescription::CURRENT_FORMAT_VERSION
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_compatibility
|
117
|
+
if !compatible?
|
118
|
+
raise Machinery::Errors::SystemDescriptionIncompatible.new(self.name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_hash
|
123
|
+
meta = {}
|
124
|
+
meta["format_version"] = self.format_version if self.format_version
|
125
|
+
|
126
|
+
attributes.each do |key, value|
|
127
|
+
meta[key] = self[key].meta.as_json if self[key].meta
|
128
|
+
end
|
129
|
+
|
130
|
+
hash = as_json
|
131
|
+
hash["meta"] = meta unless meta.empty?
|
132
|
+
hash
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_json
|
136
|
+
JSON.pretty_generate(to_hash)
|
137
|
+
end
|
138
|
+
|
139
|
+
def scopes
|
140
|
+
scopes = attributes.map {
|
141
|
+
|key, value| key.to_s
|
142
|
+
}.sort
|
143
|
+
scopes
|
144
|
+
end
|
145
|
+
|
146
|
+
def assert_scopes(*scopes)
|
147
|
+
missing = scopes.reject do |e|
|
148
|
+
self.send(e) && !self.send(e).empty?
|
149
|
+
end
|
150
|
+
|
151
|
+
unless missing.empty?
|
152
|
+
raise Machinery::Errors::SystemDescriptionError.new(
|
153
|
+
"The system description misses the following section(s): #{missing.join(", ")}."
|
154
|
+
)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def short_os_version
|
159
|
+
assert_scopes("os")
|
160
|
+
|
161
|
+
case self.os.name
|
162
|
+
when /^SUSE Linux Enterprise Server/
|
163
|
+
"sles" + self.os.version[/\d+( SP\d+)*/].gsub(" ", "").downcase
|
164
|
+
when /^openSUSE/
|
165
|
+
self.os.version[/^\d+.\d+/]
|
166
|
+
else
|
167
|
+
"unknown"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def scope_extracted?(scope)
|
172
|
+
self[scope] && self[scope].is_extractable? && self[scope].extracted
|
173
|
+
end
|
174
|
+
|
175
|
+
def os_object
|
176
|
+
assert_scopes("os")
|
177
|
+
|
178
|
+
begin
|
179
|
+
Os.for(self.os.name)
|
180
|
+
rescue Machinery::Errors::UnknownOs => e
|
181
|
+
raise Machinery::Errors::SystemDescriptionError.new(e)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def missing_files(scope, file_list)
|
186
|
+
file_list.map! { |file| File.join(file_store(scope), file) }
|
187
|
+
|
188
|
+
file_list.select do |file|
|
189
|
+
!File.exists?(file)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def additional_files(scope, file_list)
|
194
|
+
file_list.map! { |file| File.join(file_store(scope), file) }
|
195
|
+
files = list_file_store_content(scope)
|
196
|
+
|
197
|
+
files - file_list
|
198
|
+
end
|
199
|
+
|
200
|
+
def validate_file_data
|
201
|
+
SystemDescriptionValidator.new(self).validate_file_data
|
202
|
+
end
|
203
|
+
|
204
|
+
# Filestore handling
|
205
|
+
def initialize_file_store(store_name)
|
206
|
+
@store.initialize_file_store(self.name, store_name)
|
207
|
+
end
|
208
|
+
|
209
|
+
def file_store(store_name)
|
210
|
+
@store.file_store(self.name, store_name)
|
211
|
+
end
|
212
|
+
|
213
|
+
def remove_file_store(store_name)
|
214
|
+
@store.remove_file_store(self.name, store_name)
|
215
|
+
end
|
216
|
+
|
217
|
+
def rename_file_store(old_name, new_name)
|
218
|
+
@store.rename_file_store(self.name, old_name, new_name)
|
219
|
+
end
|
220
|
+
|
221
|
+
def list_file_store_content(store_name)
|
222
|
+
@store.list_file_store_content(self.name, store_name)
|
223
|
+
end
|
224
|
+
|
225
|
+
def create_file_store_sub_dir(store_name, sub_dir)
|
226
|
+
@store.create_file_store_sub_dir(self.name, store_name, sub_dir)
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,167 @@
|
|
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 SystemDescriptionStore
|
19
|
+
attr_reader :base_path
|
20
|
+
|
21
|
+
def default_path
|
22
|
+
Machinery::DEFAULT_CONFIG_DIR
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(base_path = default_path)
|
26
|
+
@base_path = base_path
|
27
|
+
create_dir(@base_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def description_path(name)
|
31
|
+
File.join(@base_path, name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def manifest_path(name)
|
35
|
+
File.join(description_path(name), "manifest.json")
|
36
|
+
end
|
37
|
+
|
38
|
+
def html_path(name)
|
39
|
+
File.join(description_path(name), "index.html")
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_json(name)
|
43
|
+
validate_name(name)
|
44
|
+
file_name = manifest_path(name)
|
45
|
+
unless File.exists?(file_name)
|
46
|
+
raise Machinery::Errors::SystemDescriptionNotFound.new(
|
47
|
+
"A system description with the name #{name} was not found."
|
48
|
+
)
|
49
|
+
end
|
50
|
+
File.read(file_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def load(name)
|
54
|
+
json = load_json(name)
|
55
|
+
description = SystemDescription.from_json(name, json, self)
|
56
|
+
description.validate_compatibility
|
57
|
+
description.validate_file_data
|
58
|
+
description
|
59
|
+
end
|
60
|
+
|
61
|
+
def save(description)
|
62
|
+
validate_name(description.name)
|
63
|
+
create_dir(description_path(description.name))
|
64
|
+
path = manifest_path(description.name)
|
65
|
+
created = !File.exists?(path)
|
66
|
+
File.write(path, description.to_json)
|
67
|
+
File.chmod(0600,path) if created
|
68
|
+
end
|
69
|
+
|
70
|
+
def list
|
71
|
+
Dir["#{@base_path}/*"].
|
72
|
+
select { |item| File.exists?(manifest_path(File.basename(item)))}.
|
73
|
+
map { |item| File.basename(item) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove(name)
|
77
|
+
unless name.empty?
|
78
|
+
validate_name(name)
|
79
|
+
FileUtils.rm_rf(description_path(name))
|
80
|
+
else
|
81
|
+
raise "The system description has no name specified and thus can't be deleted."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def copy(from, to)
|
86
|
+
validate_name(from)
|
87
|
+
validate_name(to)
|
88
|
+
if !list.include?(from)
|
89
|
+
raise Machinery::Errors::SystemDescriptionNotFound.new(
|
90
|
+
"System description \"#{from}\" does not exist."
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
if list.include?(to)
|
95
|
+
raise Machinery::Errors::SystemDescriptionError.new(
|
96
|
+
"A System description with the name \"#{to}\" does already exist."
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
FileUtils.cp_r(description_path(from), description_path(to))
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize_file_store(description_name, store_name)
|
104
|
+
dir = File.join(description_path(description_name), store_name)
|
105
|
+
create_dir(dir, new_dir_mode(description_name))
|
106
|
+
end
|
107
|
+
|
108
|
+
def file_store(description_name, store_name)
|
109
|
+
dir = File.join(description_path(description_name), store_name)
|
110
|
+
Dir.exists?(dir) ? dir : nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def remove_file_store(description_name, store_name)
|
114
|
+
FileUtils.rm_rf(File.join(description_path(description_name), store_name))
|
115
|
+
end
|
116
|
+
|
117
|
+
def rename_file_store(description_name, store_old, store_new)
|
118
|
+
FileUtils.mv(
|
119
|
+
File.join(description_path(description_name), store_old),
|
120
|
+
File.join(description_path(description_name), store_new)
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
def create_file_store_sub_dir(description_name, store_name, sub_dir)
|
125
|
+
dir = File.join(description_path(description_name), store_name, sub_dir)
|
126
|
+
create_dir(dir, new_dir_mode(description_name))
|
127
|
+
end
|
128
|
+
|
129
|
+
def list_file_store_content(description_name, store_name)
|
130
|
+
dir = File.join(description_path(description_name), store_name)
|
131
|
+
|
132
|
+
files = Dir.glob(File.join(dir, "**/{*,.*}"))
|
133
|
+
# filter parent directories because they should not be listed separately
|
134
|
+
files.reject { |f| files.index { |e| e =~ /^#{f}\/.+/ } }
|
135
|
+
end
|
136
|
+
|
137
|
+
def new_dir_mode(name)
|
138
|
+
mode = 0700
|
139
|
+
if Dir.exists?(description_path(name))
|
140
|
+
mode = File.stat(description_path(name)).mode & 0777
|
141
|
+
end
|
142
|
+
mode
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def create_dir(dir, mode = 0700)
|
148
|
+
unless Dir.exists?(dir)
|
149
|
+
FileUtils.mkdir_p(dir, :mode => mode)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def validate_name(name)
|
154
|
+
if ! /^[\w\.:-]*$/.match(name)
|
155
|
+
raise Machinery::Errors::SystemDescriptionError.new(
|
156
|
+
"System description name \"#{name}\" is invalid. Only \"a-zA-Z0-9_:.-\" are valid characters."
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
if name.start_with?(".")
|
161
|
+
raise Machinery::Errors::SystemDescriptionError.new(
|
162
|
+
"System description name \"#{name}\" is invalid. A dot is not allowed as first character."
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,216 @@
|
|
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 SystemDescriptionValidator
|
19
|
+
class << self
|
20
|
+
def load_global_schema
|
21
|
+
JSON.parse(File.read(File.join(
|
22
|
+
Machinery::ROOT,
|
23
|
+
"schema",
|
24
|
+
"v#{SystemDescription::CURRENT_FORMAT_VERSION}",
|
25
|
+
"system-description-global.schema.json"
|
26
|
+
)))
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_scope_schemas
|
30
|
+
schema_dir = File.join(
|
31
|
+
Machinery::ROOT,
|
32
|
+
"plugins",
|
33
|
+
"schema",
|
34
|
+
"v#{SystemDescription::CURRENT_FORMAT_VERSION}",
|
35
|
+
)
|
36
|
+
|
37
|
+
Hash[
|
38
|
+
Dir["#{schema_dir}/*.schema.json"].map do |file|
|
39
|
+
scope = file.match(/system-description-(.*)\.schema\.json$/)[1].tr("-", "_")
|
40
|
+
schema = JSON.parse(File.read(file))
|
41
|
+
|
42
|
+
[scope, schema]
|
43
|
+
end
|
44
|
+
]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
GLOBAL_SCHEMA = load_global_schema
|
49
|
+
SCOPE_SCHEMAS = load_scope_schemas
|
50
|
+
|
51
|
+
def initialize(description)
|
52
|
+
@description = description
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_json(json)
|
56
|
+
errors = JSON::Validator.fully_validate(GLOBAL_SCHEMA, json)
|
57
|
+
|
58
|
+
scopes = json.keys
|
59
|
+
scopes.delete("meta")
|
60
|
+
|
61
|
+
errors += scopes.flat_map do |scope|
|
62
|
+
schema = SCOPE_SCHEMAS[scope]
|
63
|
+
|
64
|
+
if schema
|
65
|
+
issue = JSON::Validator.fully_validate(schema, json[scope]).map do |error|
|
66
|
+
"In scope #{scope}: #{error}"
|
67
|
+
end
|
68
|
+
issue.map do |error|
|
69
|
+
cleanup_json_error_message(error, scope)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
[]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if !errors.empty?
|
77
|
+
raise Machinery::Errors::SystemDescriptionError.new(errors.join("\n"))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def missing_files_for_plain_scope(scope)
|
82
|
+
expected_files = @description[scope].files.reject { |file| file.changes.include?("deleted") }
|
83
|
+
missing_files = @description.missing_files(scope, expected_files.map(&:name))
|
84
|
+
end
|
85
|
+
|
86
|
+
def expected_files_for_tared_scope(scope)
|
87
|
+
has_files_tarball = @description[scope].files.any? { |f| f.type == "file" || f.type == "link" }
|
88
|
+
tree_tarballs = @description[scope].files.
|
89
|
+
select { |f| f.type == "dir" }.
|
90
|
+
map { |d| File.join("trees", d.name.sub(/\/$/, "") + ".tgz") }
|
91
|
+
|
92
|
+
expected_files = []
|
93
|
+
expected_files << "files.tgz" if has_files_tarball
|
94
|
+
expected_files += tree_tarballs
|
95
|
+
end
|
96
|
+
|
97
|
+
def missing_files_for_tared_scope(scope)
|
98
|
+
expected_files = expected_files_for_tared_scope(scope)
|
99
|
+
missing_files = @description.missing_files(scope, expected_files)
|
100
|
+
end
|
101
|
+
|
102
|
+
def missing_files_for_scope(scope)
|
103
|
+
if scope == "unmanaged_files"
|
104
|
+
missing_files_for_tared_scope(scope)
|
105
|
+
else
|
106
|
+
missing_files_for_plain_scope(scope)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def additional_files_for_tared_scope(scope)
|
111
|
+
expected_files = expected_files_for_tared_scope(scope)
|
112
|
+
additional_files = @description.additional_files(scope, expected_files)
|
113
|
+
end
|
114
|
+
|
115
|
+
def additional_files_for_plain_scope(scope)
|
116
|
+
expected_files = @description[scope].files.reject { |file| file.changes.include?("deleted") }
|
117
|
+
additional_files = @description.additional_files(scope, expected_files.map(&:name))
|
118
|
+
end
|
119
|
+
|
120
|
+
def additional_files_for_scope(scope)
|
121
|
+
if scope == "unmanaged_files"
|
122
|
+
additional_files_for_tared_scope(scope)
|
123
|
+
else
|
124
|
+
additional_files_for_plain_scope(scope)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_file_errors(scope, missing_files, additional_files)
|
129
|
+
errors = missing_files.map do |file|
|
130
|
+
" * File '" + file + "' doesn't exist"
|
131
|
+
end
|
132
|
+
errors += additional_files.map do |file|
|
133
|
+
" * File '" + file + "' doesn't have meta data"
|
134
|
+
end
|
135
|
+
"Scope '#{scope}':\n" + errors.join("\n")
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_file_data
|
139
|
+
errors = []
|
140
|
+
|
141
|
+
["config_files", "changed_managed_files", "unmanaged_files"].each do |scope|
|
142
|
+
if @description.scope_extracted?(scope)
|
143
|
+
missing_files = missing_files_for_scope(scope)
|
144
|
+
additional_files = additional_files_for_scope(scope)
|
145
|
+
|
146
|
+
if !missing_files.empty? or !additional_files.empty?
|
147
|
+
errors.push(format_file_errors(scope, missing_files, additional_files))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if !errors.empty?
|
153
|
+
e = Machinery::Errors::SystemDescriptionValidationFailed.new(errors)
|
154
|
+
e.header = "Error validating description '#{@description.name}'"
|
155
|
+
raise e
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def cleanup_json_error_message(message, scope)
|
160
|
+
message = cleanup_json_path(message, scope)
|
161
|
+
message = remove_json_error_uuid(message)
|
162
|
+
message
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def cleanup_json_path(message, scope)
|
168
|
+
old_path = message[/The property '#\/(.*?)'/,1]
|
169
|
+
|
170
|
+
position = error_position_from_json_path(old_path)
|
171
|
+
details = extract_details_from_json_path(old_path, scope)
|
172
|
+
|
173
|
+
new_path = "The property"
|
174
|
+
new_path += " ##{position}" if position > -1
|
175
|
+
new_path += " (#{details})" if !details.empty?
|
176
|
+
|
177
|
+
message.gsub(/The property '#\/.*?'/, new_path)
|
178
|
+
end
|
179
|
+
|
180
|
+
def error_position_from_json_path(path)
|
181
|
+
elements = path.split("/")
|
182
|
+
|
183
|
+
position = -1
|
184
|
+
elements.each do |e|
|
185
|
+
if Machinery::is_int?(e)
|
186
|
+
number = e.to_i
|
187
|
+
position = number if number > position
|
188
|
+
end
|
189
|
+
end
|
190
|
+
position
|
191
|
+
end
|
192
|
+
|
193
|
+
def extract_details_from_json_path(path, scope)
|
194
|
+
elements = path.split("/")
|
195
|
+
|
196
|
+
elements.uniq!
|
197
|
+
|
198
|
+
# filter numbers since the position is calculated elswhere
|
199
|
+
elements.reject! { |e| Machinery::is_int?(e) }
|
200
|
+
|
201
|
+
# The json schema path often contains the word "type" in many messages
|
202
|
+
# but this information adds no value for the user since it is not related
|
203
|
+
# to our manifest.json
|
204
|
+
# So we filter it for all scopes except of repositories since this is the
|
205
|
+
# only scope which does have an attribute called "type"
|
206
|
+
if scope != "repositories"
|
207
|
+
elements.reject! { |e| e == "type" }
|
208
|
+
end
|
209
|
+
|
210
|
+
elements.join("/")
|
211
|
+
end
|
212
|
+
|
213
|
+
def remove_json_error_uuid(message)
|
214
|
+
message.gsub(/ in schema .*$/, ".")
|
215
|
+
end
|
216
|
+
end
|