machinery-tool 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/system.rb CHANGED
@@ -27,6 +27,8 @@ class System
27
27
  abstract_method :kiwi_describe
28
28
  abstract_method :retrieve_files
29
29
  abstract_method :read_file
30
+ abstract_method :inject_file
31
+ abstract_method :remove_file
30
32
 
31
33
  def self.for(host, remote_user = "root")
32
34
  if host && host != "localhost"
@@ -90,4 +92,8 @@ class System
90
92
  rescue Cheetah::ExecutionFailed
91
93
  false
92
94
  end
95
+
96
+ def arch
97
+ run_command("uname", "-m", stdout: :capture).chomp
98
+ end
93
99
  end
@@ -269,4 +269,30 @@ class SystemDescription < Machinery::Object
269
269
  def description_path
270
270
  @store.description_path(name)
271
271
  end
272
+
273
+ def runs_service?(name)
274
+ self["services"].services.any? { |service| service.name == "#{name}.service" }
275
+ end
276
+
277
+ def has_file?(name)
278
+ self["config_files"].files.any? { |file| file.name == name } ||
279
+ self["unmanaged_files"].files.any? { |file| file.name == name }
280
+ end
281
+
282
+ def read_config(path, key)
283
+ if scope_extracted?("config_files")
284
+ file = self["config_files"].files.find { |f| f.name == path }
285
+ return parse_variable_assignment(file.content, key) if file
286
+ end
287
+ if scope_extracted?("unmanaged_files")
288
+ file = self["unmanaged_files"].files.find { |f| f.name == path }
289
+ return parse_variable_assignment(file.content, key) if file
290
+ end
291
+ end
292
+
293
+ private
294
+
295
+ def parse_variable_assignment(string, variable)
296
+ /^(?#) *#{variable} *(=|:| ) *(.*)/.match(string).to_a.fetch(2, "").gsub(/\"/, "").strip
297
+ end
272
298
  end
data/lib/version.rb CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  module Machinery
19
19
 
20
- VERSION = "1.11.2"
20
+ VERSION = "1.12.0"
21
21
 
22
22
  end
@@ -0,0 +1,141 @@
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
+ class WorkloadMapper
18
+ def save(workloads, path)
19
+ workloads.each do |workload, config|
20
+ FileUtils.mkdir_p(File.join(path, workload))
21
+ FileUtils.cp_r(
22
+ File.join(workload_mapper_path, workload, "container", "."),
23
+ File.join(path, workload)
24
+ )
25
+ end
26
+ compose_services = compose_services(workloads)
27
+ linked_services = link_compose_services(compose_services)
28
+ File.write(File.join(path, "docker-compose.yml"), linked_services.to_yaml)
29
+ end
30
+
31
+ def link_compose_services(services)
32
+ services.each do |service, config|
33
+ config.fetch("links", {}).each do |linked_service|
34
+ services[service]["environment"] ||= {}
35
+ if services[linked_service]
36
+ vars = services[linked_service].fetch("environment", {})
37
+ services[service]["environment"].merge!(vars)
38
+ else
39
+ raise Machinery::Errors::ComposeServiceLink.new(
40
+ "Could not detect '#{linked_service}', which is referenced by '#{service}'."\
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def compose_service(workload, config)
48
+ name = config["service"]
49
+ service = {
50
+ name => compact(load_compose_template(workload))
51
+ }
52
+ fill_in_template(service[name], config["parameters"])
53
+ service
54
+ end
55
+
56
+ def load_compose_template(workload)
57
+ template = YAML::load(
58
+ File.read(File.join(workload_mapper_path, workload, "compose-template.yml"))
59
+ )
60
+ template[workload]
61
+ end
62
+
63
+ def identify_workloads(system_description)
64
+ system_description.assert_scopes("services", "unmanaged_files", "config_files")
65
+
66
+ ["unmanaged_files", "config_files"].each do |scope|
67
+ if !system_description.scope_extracted?(scope)
68
+ raise Machinery::Errors::SystemDescriptionError.new(
69
+ "Required scope: '#{scope}' was not extracted. Can't continue."
70
+ )
71
+ end
72
+ end
73
+
74
+ workloads = {}
75
+
76
+ Dir["#{File.expand_path(workload_mapper_path)}/*"].each do |workload_dir|
77
+ mapper = WorkloadMapperDSL.new(system_description)
78
+ workload = mapper.check_clue(File.read(File.join(workload_dir, "clue.rb")))
79
+ workloads.merge!(workload.to_h)
80
+ end
81
+ workloads
82
+ end
83
+
84
+ def fill_in_template(service, parameters)
85
+ service.each do |key, value|
86
+ if value.is_a?(Hash)
87
+ fill_in_template(value, parameters)
88
+ elsif value.is_a?(Symbol)
89
+ service[key] = parameters[value.to_s]
90
+ end
91
+ end
92
+ end
93
+
94
+ def extract(system_description, workloads, path)
95
+ Dir.mktmpdir do |dir|
96
+ if !workloads.select { |_, w| w["data"] }.empty?
97
+ system_description.unmanaged_files.export_files_as_tarballs(dir)
98
+ workloads.each do |workload, config|
99
+ config.fetch("data", {}).each do |origin, destination|
100
+ file = system_description.unmanaged_files.files.find { |f| f.name == origin }
101
+ file ||= system_description.config_files.files.find { |f| f.name == origin }
102
+ if file && file.directory?
103
+ tgz_file = File.join(dir, "trees", "#{origin.chop}.tgz")
104
+ output_path = File.join(path, workload, destination)
105
+ FileUtils.mkdir_p(output_path)
106
+ Cheetah.run("tar", "zxf", tgz_file, "-C", output_path, "--strip=1")
107
+ copy_workload_config_files(workload, output_path)
108
+ end
109
+ if file && file.file?
110
+ output_path = File.join(path, workload, destination, origin)
111
+ FileUtils.mkdir_p(File.dirname(output_path))
112
+ File.write(output_path, file.content)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def copy_workload_config_files(workload, path)
123
+ FileUtils.cp_r(File.join(workload_mapper_path, workload, "config", "."), path)
124
+ end
125
+
126
+ def workload_mapper_path
127
+ File.join(Machinery::ROOT, "workload_mapper")
128
+ end
129
+
130
+ def compact(service)
131
+ service.each { |_, attr| attr.is_a?(Hash) && attr.reject! { |_, val| val.nil? } }
132
+ end
133
+
134
+ def compose_services(workloads)
135
+ compose_services = {}
136
+ workloads.each do |workload, config|
137
+ compose_services.merge!(compose_service(workload, config))
138
+ end
139
+ compose_services
140
+ end
141
+ end
@@ -0,0 +1,54 @@
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
+ class WorkloadMapperDSL
18
+ attr_reader :system, :name, :service, :parameters, :data
19
+
20
+ def initialize(system)
21
+ @system = system
22
+ @parameters = {}
23
+ @data = {}
24
+ end
25
+
26
+ def identify(name, service = nil)
27
+ @name = name
28
+ @service = !!service ? service : name
29
+ end
30
+
31
+ def parameter(name, value)
32
+ @parameters[name] = value
33
+ end
34
+
35
+ def extract(origin, destination)
36
+ @data[origin] = destination
37
+ end
38
+
39
+ def to_h
40
+ return {} unless service
41
+ {
42
+ name => {
43
+ "service" => service,
44
+ "parameters" => parameters,
45
+ "data" => data
46
+ }
47
+ }
48
+ end
49
+
50
+ def check_clue(clue)
51
+ instance_eval(clue)
52
+ self
53
+ end
54
+ end
Binary file
@@ -867,6 +867,13 @@ via <code>ssh-copy-id</code> to the inspected host, e.g.: <code>ssh-copy-id root
867
867
  whitelist given in the sudoers file:</p>
868
868
 
869
869
  <p>machinery ALL=(ALL) NOPASSWD: /usr/bin/find,/usr/bin/cat,/bin/cat,/usr/bin/rsync,/bin/rpm -V *,/bin/tar --create *</p></li>
870
+ <li><p>To add a remote <code>machinery</code> user run as root:</p>
871
+
872
+ <h1><code>useradd -m machinery -c "remote user for machinery"</code></h1>
873
+
874
+ <p>To configure a password for the new user run:</p>
875
+
876
+ <h1><code>passwd machinery</code></h1></li>
870
877
  </ul>
871
878
 
872
879
 
@@ -1198,7 +1205,7 @@ manually editing it.</p>
1198
1205
 
1199
1206
  <ol class='man-decor man-foot man foot'>
1200
1207
  <li class='tl'></li>
1201
- <li class='tc'>July 2015</li>
1208
+ <li class='tc'>September 2015</li>
1202
1209
  <li class='tr'>machinery(1)</li>
1203
1210
  </ol>
1204
1211
 
@@ -18,10 +18,6 @@
18
18
  # Inspect name, version, and other attributes of the operating system
19
19
  class OsInspector < Inspector
20
20
  has_priority 10
21
- # determines the architecture
22
- def get_arch
23
- @system.run_command("uname", "-m", :stdout => :capture).chomp
24
- end
25
21
 
26
22
  def strip_arch_from_name(name)
27
23
  # architecture information in the name might be misleading
@@ -49,7 +45,7 @@ class OsInspector < Inspector
49
45
 
50
46
  os = get_os
51
47
  if os
52
- os.architecture = get_arch
48
+ os.architecture = @system.arch
53
49
  os.version += get_additional_version if os.version
54
50
  else
55
51
  raise Machinery::Errors::UnknownOs
@@ -84,6 +84,10 @@ class RepositoriesInspector < Inspector
84
84
  end
85
85
  rescue JSON::ParserError
86
86
  raise Machinery::Errors::InspectionFailed.new("Extraction of YUM repositories failed.")
87
+ rescue Cheetah::ExecutionFailed => e
88
+ raise Machinery::Errors::InspectionFailed.new(
89
+ "Extraction of YUM repositories failed:\n#{e.stderr}"
90
+ )
87
91
  end
88
92
 
89
93
  RepositoriesScope.new(repositories)
@@ -53,6 +53,17 @@ class UnmanagedFilesInspector < Inspector
53
53
  files[pair.first] = pair[1]
54
54
  end
55
55
  end
56
+ # make sure that all parent directories of managed rpm directories are considered
57
+ # managed
58
+ dirh.dup.keys.each do |d|
59
+ dir = d.rpartition("/").first
60
+
61
+ while !dirh.has_key?(dir) && dir.size > 1
62
+ dirh[dir] = false
63
+ dir = dir[0..dir.rindex("/") - 1]
64
+ end
65
+ end
66
+
56
67
  files.each do |f,e|
57
68
  dir, sep, file = f.rpartition("/")
58
69
 
@@ -97,6 +108,12 @@ class UnmanagedFilesInspector < Inspector
97
108
  os = osl.find do |o|
98
109
  o.name == "/#{file[:path]}#{file[:type] == :dir ? "/" : ""}"
99
110
  end
111
+ if !os
112
+ raise Machinery::Errors::UnexpectedInputData.new(
113
+ "The inspection failed because of the unexpected input data:\n#{file.inspect}\n\n" \
114
+ "Please file a bug report at: https://github.com/SUSE/machinery/issues/new"
115
+ )
116
+ end
100
117
 
101
118
  os.user = file[:user]
102
119
  os.group = file[:group]
@@ -144,7 +161,6 @@ class UnmanagedFilesInspector < Inspector
144
161
  Machinery::Ui.warn(message)
145
162
  end
146
163
 
147
-
148
164
  # find creates three field per path
149
165
  out.split("\0", -1).each_slice(3) do |type, raw_path, raw_link|
150
166
  next unless raw_path && !raw_path.empty?
@@ -213,6 +229,57 @@ class UnmanagedFilesInspector < Inspector
213
229
 
214
230
  scope.scope_file_store = file_store_tmp
215
231
 
232
+ file_filter = filter.element_filter_for("/unmanaged_files/files/name").dup if filter
233
+ file_filter ||= ElementFilter.new("/unmanaged_files/files/name")
234
+ file_filter.add_matchers("=", @description.store.base_path)
235
+
236
+ # Add a recursive pendant to each ignored element
237
+ file_filter.matchers.each do |operator, matchers|
238
+ file_filter.add_matchers(operator, matchers.map { |entry| File.join(entry, "/*") })
239
+ end
240
+
241
+ helper = MachineryHelper.new(@system)
242
+ if helper_usable?(helper, options)
243
+ begin
244
+ helper.inject_helper
245
+ helper.run_helper(scope)
246
+ ensure
247
+ helper.remove_helper
248
+ end
249
+ scope.extracted = false
250
+
251
+ scope.files.delete_if { |f| file_filter.matches?(f.name) }
252
+
253
+ @description["unmanaged_files"] = scope
254
+ else
255
+ run_inspection(file_filter, options, do_extract, file_store_tmp, file_store_final, scope)
256
+ end
257
+ end
258
+
259
+ def helper_usable?(helper, options)
260
+ if !helper.can_help?
261
+ Machinery::Ui.puts(
262
+ "Note: Using traditional inspection because there is no helper binary for" \
263
+ " architecture '#{@system.arch}' available."
264
+ )
265
+ elsif options[:extract_unmanaged_files]
266
+ Machinery::Ui.puts(
267
+ "Note: Using traditional inspection because file extraction is not" \
268
+ " supported by the helper binary."
269
+ )
270
+ elsif options[:remote_user] && options[:remote_user] != "root"
271
+ Machinery::Ui.puts(
272
+ "Note: Using traditional inspection because only 'root' is supported as remote user."
273
+ )
274
+ else
275
+ Machinery::Ui.puts "Note: Using helper binary for inspection of unmanaged files."
276
+ return true
277
+ end
278
+
279
+ false
280
+ end
281
+
282
+ def run_inspection(file_filter, options, do_extract, file_store_tmp, file_store_final, scope)
216
283
  mount_points = MountPoints.new(@system)
217
284
 
218
285
  rpm_files, rpm_dirs = extract_rpm_database
@@ -228,14 +295,6 @@ class UnmanagedFilesInspector < Inspector
228
295
  remote_dirs = mount_points.remote
229
296
  special_dirs = mount_points.special
230
297
 
231
- file_filter = filter.element_filter_for("/unmanaged_files/files/name").dup if filter
232
- file_filter ||= ElementFilter.new("/unmanaged_files/files/name")
233
- file_filter.add_matchers("=", @description.store.base_path)
234
-
235
- # Add a recursive pendant to each ignored element
236
- file_filter.matchers.each do |operator, matchers|
237
- file_filter.add_matchers(operator, matchers.map { |entry| File.join(entry, "/*") })
238
- end
239
298
 
240
299
  remote_dirs.delete_if { |e| file_filter.matches?(e) }
241
300
 
@@ -335,6 +394,18 @@ class UnmanagedFilesInspector < Inspector
335
394
  Machinery::Ui.progress(progress)
336
395
  end
337
396
  Machinery.logger.debug "inspect unmanaged files find calls:#{find_count} files:#{unmanaged_files.size} trees:#{unmanaged_trees.size}"
397
+
398
+ processed_files = run_extraction(unmanaged_files, unmanaged_trees, unmanaged_links,
399
+ excluded_files, remote_dirs, do_extract, file_store_tmp, file_store_final, scope)
400
+
401
+ scope.extracted = !!do_extract
402
+ scope.files = UnmanagedFileList.new(processed_files.sort_by(&:name))
403
+
404
+ @description["unmanaged_files"] = scope
405
+ end
406
+
407
+ def run_extraction(unmanaged_files, unmanaged_trees, unmanaged_links, excluded_files, remote_dirs,
408
+ do_extract, file_store_tmp, file_store_final, scope)
338
409
  begin
339
410
  if do_extract
340
411
  file_store_tmp.remove
@@ -374,10 +445,7 @@ class UnmanagedFilesInspector < Inspector
374
445
  osl << UnmanagedFile.new( name: remote_dir + "/", type: "remote_dir")
375
446
  end
376
447
 
377
- scope.extracted = !!do_extract
378
- scope.files = UnmanagedFileList.new(osl.sort_by(&:name))
379
-
380
- @description["unmanaged_files"] = scope
448
+ osl
381
449
  end
382
450
 
383
451
  def summary