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,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
|