machinery-tool 1.8.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS +13 -0
  3. data/helpers/changed_files.sh +105 -0
  4. data/helpers/filter-packages-for-build.yaml +2 -0
  5. data/lib/array.rb +8 -0
  6. data/lib/autoyast.rb +28 -26
  7. data/lib/build_task.rb +11 -2
  8. data/lib/changed_rpm_files_helper.rb +26 -7
  9. data/lib/cli.rb +12 -2
  10. data/lib/compare_task.rb +9 -2
  11. data/lib/config.rb +4 -0
  12. data/lib/element_filter.rb +4 -0
  13. data/lib/exceptions.rb +1 -0
  14. data/lib/exporter.rb +4 -0
  15. data/lib/file_validator.rb +3 -3
  16. data/lib/helper.rb +10 -4
  17. data/lib/inspector.rb +26 -0
  18. data/lib/kiwi_config.rb +36 -35
  19. data/lib/local_system.rb +19 -9
  20. data/lib/machinery.rb +2 -0
  21. data/lib/manifest.rb +1 -1
  22. data/lib/object.rb +8 -0
  23. data/lib/scope.rb +7 -1
  24. data/lib/scope_file_access.rb +67 -0
  25. data/lib/system.rb +2 -2
  26. data/lib/system_description.rb +26 -21
  27. data/lib/system_description_store.rb +1 -1
  28. data/lib/system_file.rb +44 -0
  29. data/lib/tarball.rb +1 -1
  30. data/lib/ui.rb +27 -0
  31. data/lib/upgrade_format_task.rb +1 -1
  32. data/lib/version.rb +1 -1
  33. data/lib/zypper.rb +1 -1
  34. data/man/generated/machinery.1.gz +0 -0
  35. data/man/generated/machinery.1.html +1 -1
  36. data/plugins/changed_managed_files/changed_managed_files_inspector.rb +27 -11
  37. data/plugins/changed_managed_files/changed_managed_files_model.rb +2 -1
  38. data/plugins/changed_managed_files/schema/system-description-changed-managed-files.schema-v4.json +126 -0
  39. data/plugins/config_files/config_files_inspector.rb +28 -23
  40. data/plugins/config_files/config_files_model.rb +2 -1
  41. data/plugins/config_files/schema/system-description-config-files.schema-v4.json +126 -0
  42. data/plugins/groups/schema/system-description-groups.schema-v4.json +30 -0
  43. data/plugins/os/os_inspector.rb +2 -2
  44. data/plugins/os/schema/system-description-os.schema-v4.json +21 -0
  45. data/plugins/packages/schema/system-description-packages.schema-v4.json +34 -0
  46. data/plugins/patterns/schema/system-description-patterns.schema-v4.json +24 -0
  47. data/plugins/repositories/schema/system-description-repositories.schema-v4.json +45 -0
  48. data/plugins/services/schema/system-description-services.schema-v4.json +30 -0
  49. data/plugins/unmanaged_files/schema/system-description-unmanaged-files.schema-v4.json +144 -0
  50. data/plugins/unmanaged_files/unmanaged_files_inspector.rb +34 -26
  51. data/plugins/unmanaged_files/unmanaged_files_model.rb +2 -1
  52. data/plugins/users/schema/system-description-users.schema-v4.json +61 -0
  53. data/schema/migrations/migrate3to4.rb +52 -0
  54. data/schema/system-description-global.schema-v4.json +43 -0
  55. metadata +17 -3
  56. data/helpers/changed_managed_files.sh +0 -46
@@ -78,9 +78,9 @@ class FileValidator
78
78
  expected_files << "files.tgz" if has_files_tarball
79
79
  expected_files += tree_tarballs
80
80
  else
81
- expected_files = files.
82
- reject { |file| file["changes"].include?("deleted") }.
83
- map { |file| file["name"] }
81
+ expected_files = files.reject do |file|
82
+ file["changes"].include?("deleted") || (file["type"] && file["type"] != "file")
83
+ end.map { |file| file["name"] }
84
84
  end
85
85
 
86
86
  store_base_path = ScopeFileStore.new(@base_path, scope.to_s).path
@@ -32,12 +32,18 @@ module Machinery
32
32
  s.dup.force_encoding("UTF-8").encode("UTF-16", invalid: :replace).encode("UTF-8")
33
33
  end
34
34
 
35
- def self.pluralize(text, number)
36
- if number > 1 || number == 0
37
- text + "s"
35
+ def self.pluralize(count, singular, plural = nil)
36
+ val = if count > 1 || count == 0
37
+ if !plural
38
+ singular + "s"
39
+ else
40
+ plural
41
+ end
38
42
  else
39
- text
43
+ singular
40
44
  end
45
+
46
+ val.gsub("%d", count.to_s)
41
47
  end
42
48
  end
43
49
 
@@ -89,4 +89,30 @@ class Inspector
89
89
  def scope
90
90
  self.class.scope
91
91
  end
92
+
93
+ # Runs the given script on the inspected machine asynchronously and calls the callback method
94
+ # periodically with new output when it occurs.
95
+ #
96
+ # Example:
97
+ #
98
+ # count = 0
99
+ # raw_list = run_script_with_progress("changed_managed_files.sh") do |chunk|
100
+ # count += chunk.lines.count
101
+ # Machinery::Ui.progress("Found #{count} changed files...")
102
+ # end
103
+ def run_script_with_progress(*script, &callback)
104
+ output = ""
105
+ write_io = StringIO.new(output, "a")
106
+ read_io = StringIO.new(output, "r")
107
+
108
+ inspect_thread = Thread.new { @system.run_script(*script, stdout: write_io) }
109
+
110
+ while inspect_thread.alive?
111
+ sleep 0.1
112
+ chunk = read_io.read
113
+ callback.call(chunk)
114
+ end
115
+
116
+ output
117
+ end
92
118
  end
@@ -29,7 +29,7 @@ class KiwiConfig < Exporter
29
29
  "packages",
30
30
  "os"
31
31
  )
32
- check_existance_of_extraced_files
32
+ check_existance_of_extracted_files
33
33
 
34
34
  generate_config
35
35
  end
@@ -95,25 +95,46 @@ class KiwiConfig < Exporter
95
95
  end
96
96
 
97
97
  def inject_extracted_files(output_location)
98
- ["config_files", "changed_managed_files"].each do |dir|
99
- path = @system_description.scope_file_store(dir).path
100
- if path
101
- output_root_path = File.join(output_location, "root")
102
- FileUtils.mkdir_p(output_root_path)
103
- FileUtils.cp_r(Dir.glob("#{path}/*"), output_root_path)
98
+ ["changed_managed_files", "config_files"].each do |scope|
99
+ next if !@system_description.scope_extracted?(scope)
100
+
101
+ output_root_path = File.join(output_location, "root")
102
+ FileUtils.mkdir_p(output_root_path)
103
+
104
+ @system_description[scope].files.each do |file|
105
+ if file.deleted?
106
+ @sh << "rm -rf '#{quote(file.name)}'\n"
107
+ elsif file.directory?
108
+ @sh << <<EOF
109
+ chmod #{file.mode} '#{quote(file.name)}'
110
+ chown #{file.user}:#{file.group} '#{quote(file.name)}'
111
+ EOF
112
+ elsif file.file?
113
+ @system_description[scope].write_file(file, output_root_path)
114
+ @sh << <<EOF
115
+ chmod #{file.mode} '#{quote(file.name)}'
116
+ chown #{file.user}:#{file.group} '#{quote(file.name)}'
117
+ EOF
118
+ elsif file.link?
119
+ @sh << <<EOF
120
+ rm -rf '#{quote(file.name)}'
121
+ ln -s '#{quote(file.target)}' '#{quote(file.name)}'
122
+ chown --no-dereference #{file.user}:#{file.group} '#{quote(file.name)}'
123
+ EOF
124
+ end
104
125
  end
105
126
  end
106
127
 
107
- unmanaged_files_path = @system_description.
108
- scope_file_store("unmanaged_files").path
109
- if unmanaged_files_path
110
- filter = "unmanaged_files_#{@name}_excludes"
111
- destination = File.join(output_location, "root", "tmp")
128
+ if @system_description.scope_extracted?("unmanaged_files")
129
+ destination = File.join(output_location, "root", "tmp", "unmanaged_files")
112
130
  FileUtils.mkdir_p(destination, mode: 01777)
113
- FileUtils.cp_r(unmanaged_files_path, destination)
131
+ filter = "unmanaged_files_#{@name}_excludes"
132
+
133
+ @system_description.unmanaged_files.export_files_as_tarballs(destination)
134
+
114
135
  FileUtils.cp(
115
136
  File.join(Machinery::ROOT, "export_helpers/#{filter}"),
116
- destination
137
+ File.join(output_location, "root", "tmp")
117
138
  )
118
139
 
119
140
  @sh << "# Apply the extracted unmanaged files\n"
@@ -123,7 +144,7 @@ class KiwiConfig < Exporter
123
144
  end
124
145
  end
125
146
 
126
- def check_existance_of_extraced_files
147
+ def check_existance_of_extracted_files
127
148
  missing_scopes = []
128
149
  ["config_files", "changed_managed_files", "unmanaged_files"].each do |scope|
129
150
 
@@ -190,7 +211,6 @@ EOF
190
211
 
191
212
  apply_repositories(xml)
192
213
  apply_packages(xml)
193
- apply_extracted_files_attributes
194
214
  apply_services
195
215
  end
196
216
  end
@@ -261,25 +281,6 @@ EOF
261
281
  end
262
282
  end
263
283
 
264
- def apply_extracted_files_attributes
265
- ["config_files", "changed_managed_files"].each do |scope|
266
- if @system_description[scope]
267
- deleted, files = @system_description[scope].files.partition do |f|
268
- f.changes == Machinery::Array.new(["deleted"])
269
- end
270
-
271
- files.each do |file|
272
- @sh << "chmod #{file.mode} '#{file.name}'\n"
273
- @sh << "chown #{file.user}:#{file.group} '#{file.name}'\n"
274
- end
275
-
276
- deleted.each do |file|
277
- @sh << "rm -rf '#{file.name}'\n"
278
- end
279
- end
280
- end
281
- end
282
-
283
284
  def apply_services
284
285
  if @system_description["services"]
285
286
  init_system = @system_description["services"].init_system
@@ -44,21 +44,23 @@ class LocalSystem < System
44
44
  end
45
45
 
46
46
  def validate_machinery_compatibility
47
- if !os.can_run_machinery?
48
- supported_oses = Os.supported_host_systems.map { |o| o.canonical_name }.
49
- sort.join(", ")
50
- message = "Running Machinery is not supported on this system.\n" \
51
- "Supported operating systems are: #{supported_oses}"
47
+ return if !Machinery::Config.new.perform_support_check || os.can_run_machinery?
52
48
 
53
- raise(Machinery::Errors::IncompatibleHost.new(message))
54
- end
49
+ supported_oses = Os.supported_host_systems.map { |o| o.canonical_name }.sort.join(", ")
50
+ message = <<EOF
51
+ You are running Machinery on a platform we do not explicitly support and test.
52
+ It still could work very well. If you run into issues or would like to provide us feedback, you are welcome to file an issue at https://github.com/SUSE/machinery/issues/new or write an email to machinery@lists.suse.com.
53
+ Oficially supported operating systems are: #{supported_oses}"
54
+ EOF
55
+
56
+ Machinery::Ui.warn message
55
57
  end
56
58
 
57
59
  def validate_build_compatibility(system_description)
58
60
  if !os.can_build?(system_description.os)
59
61
  message = "Building '#{system_description.os.display_name}' is " \
60
62
  "not supported on this distribution.\n" \
61
- "Check the 'BUILD SUPPORT MATRIX' section in our man page for " \
63
+ "Check the 'BUILD SUPPORT MATRIX' by running `#{$0} build --help` for " \
62
64
  "further information which build targets are supported."
63
65
 
64
66
  raise(Machinery::Errors::BuildFailed.new(message))
@@ -94,7 +96,15 @@ class LocalSystem < System
94
96
  # the directory where to put the files.
95
97
  def retrieve_files(filelist, destination)
96
98
  begin
97
- LoggedCheetah.run("rsync", "--chmod=go-rwx", "--files-from=-", "/", destination, :stdout => :capture, :stdin => filelist.join("\n") )
99
+ LoggedCheetah.run(
100
+ "rsync",
101
+ "--chmod=go-rwx",
102
+ "--files-from=-",
103
+ "/",
104
+ destination,
105
+ stdout: :capture,
106
+ stdin: filelist.join("\n")
107
+ )
98
108
  rescue Cheetah::ExecutionFailed => e
99
109
  raise Machinery::Errors::RsyncFailed.new(
100
110
  "Could not rsync files from localhost. \n" \
@@ -91,6 +91,8 @@ require_relative "element_filter"
91
91
  require_relative "filter"
92
92
  require_relative "filter_option_parser"
93
93
  require_relative "file_scope"
94
+ require_relative "system_file"
95
+ require_relative "scope_file_access"
94
96
 
95
97
  Dir[File.join(Machinery::ROOT, "plugins", "**", "*.rb")].each { |f| require(f) }
96
98
 
@@ -24,7 +24,7 @@ class Manifest
24
24
  def self.load(name, path)
25
25
  unless File.exists?(path)
26
26
  raise Machinery::Errors::SystemDescriptionNotFound.new(
27
- "A system description with the name #{name} was not found."
27
+ "Couldn't find a system description with the name '#{name}'."
28
28
  )
29
29
  end
30
30
 
@@ -55,11 +55,19 @@ module Machinery
55
55
  end
56
56
 
57
57
  attr_reader :attributes
58
+ attr_accessor :scope
58
59
 
59
60
  def initialize(attrs = {})
60
61
  set_attributes(attrs)
61
62
  end
62
63
 
64
+ def scope=(scope)
65
+ @scope = scope
66
+ @attributes.values.each do |child|
67
+ child.scope = @scope if child.respond_to?(:scope=)
68
+ end
69
+ end
70
+
63
71
  def set_attributes(attrs)
64
72
  attrs = self.class.convert_raw_hash(attrs) if attrs.is_a?(Hash)
65
73
  @attributes = attrs.inject({}) do |attributes, (key, value)|
@@ -26,9 +26,14 @@ module Machinery
26
26
  @scopes
27
27
  end
28
28
 
29
- def self.class_for(scope_name)
29
+ def self.for(scope_name, json, scope_file_store)
30
30
  all_scopes.each do |scope|
31
31
  if scope.new.scope_name == scope_name
32
+ scope = scope.from_json(json)
33
+ scope.scope_file_store = scope_file_store
34
+
35
+ scope.scope = scope
36
+
32
37
  return scope
33
38
  end
34
39
  end
@@ -36,6 +41,7 @@ module Machinery
36
41
  end
37
42
 
38
43
  attr_accessor :meta
44
+ attr_accessor :scope_file_store
39
45
 
40
46
  def set_metadata(timestring, host)
41
47
  self.meta = Machinery::Object.new(
@@ -0,0 +1,67 @@
1
+ module ScopeFileAccessFlat
2
+ def retrieve_files_from_system(system, paths)
3
+ system.retrieve_files(paths, scope_file_store.path)
4
+ end
5
+
6
+ def write_file(system_file, target)
7
+ raise Machinery::Errors::FileUtilsError, "Not a file" if !system_file.file?
8
+
9
+ target_path = File.join(target, system_file.name)
10
+ FileUtils.mkdir_p(File.dirname(target_path))
11
+ FileUtils.cp(file_path(system_file), target_path)
12
+ end
13
+
14
+ def file_path(system_file)
15
+ raise Machinery::Errors::FileUtilsError, "Not a file" if !system_file.file?
16
+
17
+ File.join(scope_file_store.path, system_file.name)
18
+ end
19
+ end
20
+
21
+ module ScopeFileAccessArchive
22
+ def retrieve_files_from_system_as_archive(system, files, excluded_files)
23
+ archive_path = File.join(scope_file_store.path, "files.tgz")
24
+ system.create_archive(files, archive_path, excluded_files)
25
+ end
26
+
27
+ def retrieve_trees_from_system_as_archive(system, trees, excluded_files, &callback)
28
+ trees.each_with_index do |tree, index|
29
+ tree_name = File.basename(tree)
30
+ parent_dir = File.dirname(tree)
31
+ sub_dir = File.join("trees", parent_dir)
32
+
33
+ scope_file_store.create_sub_directory(sub_dir)
34
+ archive_path = File.join(scope_file_store.path, sub_dir, "#{tree_name}.tgz")
35
+ system.create_archive(tree, archive_path, excluded_files)
36
+
37
+ callback.call(index + 1) if callback
38
+ end
39
+ end
40
+
41
+ def export_files_as_tarballs(destination)
42
+ FileUtils.cp(File.join(scope_file_store.path, "files.tgz"), destination)
43
+
44
+ target = File.join(destination, "trees")
45
+ files.select(&:directory?).each do |system_file|
46
+ raise Machinery::Errors::FileUtilsError if !system_file.directory?
47
+
48
+ tarball_target = File.join(target, File.dirname(system_file.name))
49
+
50
+ FileUtils.mkdir_p(tarball_target)
51
+ FileUtils.cp(tarball_path(system_file), tarball_target)
52
+ end
53
+ end
54
+
55
+ def tarball_path(system_file)
56
+ if system_file.directory?
57
+ File.join(
58
+ system_file.scope.scope_file_store.path,
59
+ "trees",
60
+ File.dirname(system_file.name),
61
+ File.basename(system_file.name) + ".tgz"
62
+ )
63
+ else
64
+ File.join(system_file.scope.scope_file_store.path, "files.tgz")
65
+ end
66
+ end
67
+ end
@@ -54,7 +54,7 @@ class System
54
54
  # Retrieves files specified in filelist from the remote system and create an archive.
55
55
  # To be able to deal with arbitrary filenames we use zero-terminated
56
56
  # filelist and the --null option of tar
57
- def create_archive(filelist, archive, exclude = [])
57
+ def create_archive(file_list, archive, exclude = [])
58
58
  created = !File.exists?(archive)
59
59
  out = File.open(archive, "w")
60
60
  begin
@@ -62,7 +62,7 @@ class System
62
62
  "tar", "--create", "--gzip", "--null", "--files-from=-",
63
63
  *exclude.flat_map { |f| ["--exclude", f]},
64
64
  stdout: out,
65
- stdin: filelist,
65
+ stdin: Array(file_list).join("\0"),
66
66
  privileged: true
67
67
  )
68
68
  rescue Cheetah::ExecutionFailed => e
@@ -26,7 +26,7 @@
26
26
  # The sub directories storing the data for specific scopes are handled by the
27
27
  # ScopeFileStore class.
28
28
  class SystemDescription < Machinery::Object
29
- CURRENT_FORMAT_VERSION = 3
29
+ CURRENT_FORMAT_VERSION = 4
30
30
  EXTRACTABLE_SCOPES = [
31
31
  "changed_managed_files",
32
32
  "config_files",
@@ -91,7 +91,7 @@ class SystemDescription < Machinery::Object
91
91
  def from_hash(name, store, hash)
92
92
  begin
93
93
  json_format_version = hash["meta"]["format_version"] if hash["meta"]
94
- description = SystemDescription.new(name, store, create_scopes(hash))
94
+ description = SystemDescription.new(name, store, hash)
95
95
  rescue NameError, TypeError
96
96
  if json_format_version && json_format_version != SystemDescription::CURRENT_FORMAT_VERSION
97
97
  raise Machinery::Errors::SystemDescriptionIncompatible.new(name, json_format_version)
@@ -108,35 +108,40 @@ class SystemDescription < Machinery::Object
108
108
 
109
109
  description
110
110
  end
111
+ end
111
112
 
112
- private
113
+ def initialize(name, store, hash = {})
114
+ @name = name
115
+ @store = store
116
+ @format_version = CURRENT_FORMAT_VERSION
117
+ @filter_definitions = {}
113
118
 
114
- def create_scopes(hash)
115
- scopes = hash.map do |scope_name, scope_json|
116
- next if scope_name == "meta"
119
+ super(create_scopes(hash))
120
+ end
121
+
122
+ def create_scopes(hash)
123
+ scopes = hash.map do |scope_name, json|
124
+ next if scope_name == "meta"
117
125
 
118
- scope_class = Machinery::Scope.class_for(scope_name)
119
- scope_object = scope_class.from_json(scope_json)
126
+ if store.persistent?
127
+ scope_file_store = scope_file_store(scope_name)
128
+ end
129
+
130
+ if json.is_a?(Hash) || json.is_a?(Array)
131
+ scope_object = Machinery::Scope.for(scope_name, json, scope_file_store)
120
132
 
121
133
  # Set metadata
122
134
  if hash["meta"] && hash["meta"][scope_name]
123
135
  scope_object.meta = Machinery::Object.from_json(hash["meta"][scope_name])
124
136
  end
137
+ else
138
+ scope_object = json
139
+ end
125
140
 
126
- [scope_name, scope_object]
127
- end.compact
128
-
129
- Hash[scopes]
130
- end
131
- end
132
-
133
- def initialize(name, store, hash = {})
134
- @name = name
135
- @store = store
136
- @format_version = CURRENT_FORMAT_VERSION
137
- @filter_definitions = {}
141
+ [scope_name, scope_object]
142
+ end.compact
138
143
 
139
- super(hash)
144
+ Hash[scopes]
140
145
  end
141
146
 
142
147
  def compatible?