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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +674 -0
  3. data/NEWS +143 -0
  4. data/bin/machinery +29 -0
  5. data/helpers/changed_managed_files.sh +32 -0
  6. data/helpers/filter-packages-for-build.yaml +6 -0
  7. data/html/assets/arrow_down.png +0 -0
  8. data/html/assets/arrow_up.png +0 -0
  9. data/html/assets/bootstrap-popover.js +113 -0
  10. data/html/assets/bootstrap-tooltip.js +457 -0
  11. data/html/assets/bootstrap.min.css +5 -0
  12. data/html/assets/collapse.js +174 -0
  13. data/html/assets/hogan-3.0.2.min.mustache.js +5 -0
  14. data/html/assets/jquery-2.1.1.min.js +4 -0
  15. data/html/assets/logo-changed-managed-files-small.png +0 -0
  16. data/html/assets/logo-changed-managed-files.png +0 -0
  17. data/html/assets/logo-config-files-small.png +0 -0
  18. data/html/assets/logo-config-files.png +0 -0
  19. data/html/assets/logo-groups-small.png +0 -0
  20. data/html/assets/logo-groups.png +0 -0
  21. data/html/assets/logo-os-small.png +0 -0
  22. data/html/assets/logo-os.png +0 -0
  23. data/html/assets/logo-packages-small.png +0 -0
  24. data/html/assets/logo-packages.png +0 -0
  25. data/html/assets/logo-patterns-small.png +0 -0
  26. data/html/assets/logo-patterns.png +0 -0
  27. data/html/assets/logo-repositories-small.png +0 -0
  28. data/html/assets/logo-repositories.png +0 -0
  29. data/html/assets/logo-services-small.png +0 -0
  30. data/html/assets/logo-services.png +0 -0
  31. data/html/assets/logo-unmanaged-files-small.png +0 -0
  32. data/html/assets/logo-unmanaged-files.png +0 -0
  33. data/html/assets/logo-users-small.png +0 -0
  34. data/html/assets/logo-users.png +0 -0
  35. data/html/assets/machinery-base.css +5767 -0
  36. data/html/assets/machinery.css +131 -0
  37. data/html/assets/machinery.js +148 -0
  38. data/html/assets/transition.js +59 -0
  39. data/html/assets/wheels_horizontal.png +0 -0
  40. data/html/index.html.haml +468 -0
  41. data/kiwi_helpers/kiwi_export_readme.md +22 -0
  42. data/kiwi_helpers/merge_users_and_groups.pl.erb +231 -0
  43. data/kiwi_helpers/unmanaged_files_build_excludes +5 -0
  44. data/lib/analyze_config_file_diffs_task.rb +130 -0
  45. data/lib/array.rb +98 -0
  46. data/lib/build_task.rb +124 -0
  47. data/lib/changed_rpm_files_helper.rb +96 -0
  48. data/lib/cli.rb +600 -0
  49. data/lib/compare_task.rb +68 -0
  50. data/lib/config.rb +33 -0
  51. data/lib/config_base.rb +117 -0
  52. data/lib/config_task.rb +56 -0
  53. data/lib/constants.rb +24 -0
  54. data/lib/copy_task.rb +22 -0
  55. data/lib/current_user.rb +23 -0
  56. data/lib/deploy_task.rb +89 -0
  57. data/lib/exceptions.rb +113 -0
  58. data/lib/generate_html_task.rb +22 -0
  59. data/lib/helper.rb +22 -0
  60. data/lib/hint.rb +39 -0
  61. data/lib/html.rb +103 -0
  62. data/lib/inspect_task.rb +93 -0
  63. data/lib/inspector.rb +65 -0
  64. data/lib/kiwi_config.rb +356 -0
  65. data/lib/kiwi_export_task.rb +36 -0
  66. data/lib/list_task.rb +64 -0
  67. data/lib/local_system.rb +127 -0
  68. data/lib/logged_cheetah.rb +25 -0
  69. data/lib/machinery.rb +85 -0
  70. data/lib/machinery_logger.rb +47 -0
  71. data/lib/migration.rb +128 -0
  72. data/lib/mountpoints.rb +72 -0
  73. data/lib/object.rb +138 -0
  74. data/lib/os.rb +78 -0
  75. data/lib/remote_system.rb +114 -0
  76. data/lib/remove_task.rb +43 -0
  77. data/lib/renderer.rb +243 -0
  78. data/lib/renderer_helper.rb +26 -0
  79. data/lib/rpm.rb +52 -0
  80. data/lib/scope_mixin.rb +38 -0
  81. data/lib/show_task.rb +65 -0
  82. data/lib/system.rb +81 -0
  83. data/lib/system_description.rb +228 -0
  84. data/lib/system_description_store.rb +167 -0
  85. data/lib/system_description_validator.rb +216 -0
  86. data/lib/tarball.rb +82 -0
  87. data/lib/ui.rb +74 -0
  88. data/lib/upgrade_format_task.rb +55 -0
  89. data/lib/validate_task.rb +23 -0
  90. data/lib/version.rb +22 -0
  91. data/lib/zypper.rb +70 -0
  92. data/man/generated/machinery.1.gz +0 -0
  93. data/man/generated/machinery.1.html +1056 -0
  94. data/plugins/docs/changed_managed_files.md +2 -0
  95. data/plugins/docs/config_files.md +5 -0
  96. data/plugins/docs/groups.md +2 -0
  97. data/plugins/docs/os.md +2 -0
  98. data/plugins/docs/packages.md +2 -0
  99. data/plugins/docs/patterns.md +5 -0
  100. data/plugins/docs/repositories.md +24 -0
  101. data/plugins/docs/services.md +6 -0
  102. data/plugins/docs/unmanaged_files.md +13 -0
  103. data/plugins/docs/users.md +3 -0
  104. data/plugins/inspect/changed_managed_files_inspector.rb +109 -0
  105. data/plugins/inspect/config_files_inspector.rb +117 -0
  106. data/plugins/inspect/groups_inspector.rb +46 -0
  107. data/plugins/inspect/os_inspector.rb +116 -0
  108. data/plugins/inspect/packages_inspector.rb +46 -0
  109. data/plugins/inspect/patterns_inspector.rb +67 -0
  110. data/plugins/inspect/repositories_inspector.rb +107 -0
  111. data/plugins/inspect/services_inspector.rb +88 -0
  112. data/plugins/inspect/unmanaged_files_inspector.rb +393 -0
  113. data/plugins/inspect/users_inspector.rb +87 -0
  114. data/plugins/model/changed_managed_files_model.rb +29 -0
  115. data/plugins/model/config_files_model.rb +29 -0
  116. data/plugins/model/groups_model.rb +26 -0
  117. data/plugins/model/os_model.rb +20 -0
  118. data/plugins/model/packages_model.rb +26 -0
  119. data/plugins/model/patterns_model.rb +26 -0
  120. data/plugins/model/repositories_model.rb +26 -0
  121. data/plugins/model/services_model.rb +48 -0
  122. data/plugins/model/unmanaged_files_model.rb +29 -0
  123. data/plugins/model/users_model.rb +26 -0
  124. data/plugins/schema/v1/system-description-changed-managed-files.schema.json +83 -0
  125. data/plugins/schema/v1/system-description-config-files.schema.json +83 -0
  126. data/plugins/schema/v1/system-description-groups.schema.json +30 -0
  127. data/plugins/schema/v1/system-description-os.schema.json +21 -0
  128. data/plugins/schema/v1/system-description-packages.schema.json +34 -0
  129. data/plugins/schema/v1/system-description-patterns.schema.json +24 -0
  130. data/plugins/schema/v1/system-description-repositories.schema.json +41 -0
  131. data/plugins/schema/v1/system-description-services.schema.json +30 -0
  132. data/plugins/schema/v1/system-description-unmanaged-files.schema.json +105 -0
  133. data/plugins/schema/v1/system-description-users.schema.json +61 -0
  134. data/plugins/schema/v2/system-description-changed-managed-files.schema.json +92 -0
  135. data/plugins/schema/v2/system-description-config-files.schema.json +92 -0
  136. data/plugins/schema/v2/system-description-groups.schema.json +30 -0
  137. data/plugins/schema/v2/system-description-os.schema.json +21 -0
  138. data/plugins/schema/v2/system-description-packages.schema.json +34 -0
  139. data/plugins/schema/v2/system-description-patterns.schema.json +24 -0
  140. data/plugins/schema/v2/system-description-repositories.schema.json +41 -0
  141. data/plugins/schema/v2/system-description-services.schema.json +30 -0
  142. data/plugins/schema/v2/system-description-unmanaged-files.schema.json +138 -0
  143. data/plugins/schema/v2/system-description-users.schema.json +61 -0
  144. data/plugins/show/changed_managed_files_renderer.rb +46 -0
  145. data/plugins/show/config_files_renderer.rb +62 -0
  146. data/plugins/show/groups_renderer.rb +36 -0
  147. data/plugins/show/os_renderer.rb +31 -0
  148. data/plugins/show/packages_renderer.rb +32 -0
  149. data/plugins/show/patterns_renderer.rb +32 -0
  150. data/plugins/show/repositories_renderer.rb +38 -0
  151. data/plugins/show/services_renderer.rb +32 -0
  152. data/plugins/show/unmanaged_files_renderer.rb +42 -0
  153. data/plugins/show/users_renderer.rb +35 -0
  154. data/schema/migrations/migrate1to2.rb +56 -0
  155. data/schema/v1/system-description-global.schema.json +31 -0
  156. data/schema/v2/system-description-global.schema.json +31 -0
  157. metadata +370 -0
@@ -0,0 +1,22 @@
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 GenerateHtmlTask
19
+ def generate(description)
20
+ Html.generate(description)
21
+ end
22
+ end
data/lib/helper.rb ADDED
@@ -0,0 +1,22 @@
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
+ module Machinery
19
+ def self.is_int?(string)
20
+ (string =~ /^\d+$/) != nil
21
+ end
22
+ end
data/lib/hint.rb ADDED
@@ -0,0 +1,39 @@
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 Hint
19
+ def self.get_started
20
+ output "You can get started by inspecting a system. Run:\n#{$0} inspect HOSTNAME"
21
+ end
22
+
23
+ def self.show_data(options)
24
+ output "To show the data of the system you just inspected run:\n#{$0} show #{options[:name]}"
25
+ end
26
+
27
+ def self.do_complete_inspection(options)
28
+ output "To do a full inspection containing all scopes and to extract files run:\n" \
29
+ "#{$0} inspect #{options[:host]} --name #{options[:name]} --extract-files"
30
+ end
31
+
32
+ private
33
+
34
+ def self.output(text)
35
+ if Machinery::Config.new.hints
36
+ Machinery::Ui.puts "\nHint: #{text}\n"
37
+ end
38
+ end
39
+ end
data/lib/html.rb ADDED
@@ -0,0 +1,103 @@
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 Html
19
+ def self.generate(description)
20
+ template = Haml::Engine.new(
21
+ File.read(File.join(Machinery::ROOT, "html", "index.html.haml"))
22
+ )
23
+ target = description.store.description_path(description.name)
24
+
25
+ diffs_dir = description.store.file_store(description.name, "config-file-diffs")
26
+ if description.config_files && diffs_dir
27
+ # Enrich description with the config file diffs
28
+ description.config_files.files.each do |file|
29
+ path = File.join(diffs_dir, file.name + ".diff")
30
+ file.diff = diff_to_object(File.read(path)) if File.exists?(path)
31
+ end
32
+ end
33
+
34
+ FileUtils.cp_r(File.join(Machinery::ROOT, "html", "assets"), target)
35
+ File.write(File.join(target, "index.html"), template.render(binding))
36
+ File.write(File.join(target, "assets/description.js"),<<-EOT
37
+ function getDescription() {
38
+ return JSON.parse('#{description.to_hash.to_json}')
39
+ }
40
+ EOT
41
+ )
42
+ end
43
+
44
+ # Template helpers
45
+
46
+ def self.scope_help(scope)
47
+ text = File.read(File.join(Machinery::ROOT, "plugins", "docs", "#{scope}.md"))
48
+ Kramdown::Document.new(text).to_html
49
+ end
50
+
51
+ def self.diff_to_object(diff)
52
+ lines = diff.lines[2..-1]
53
+ diff_object = {
54
+ file: diff[/--- a(.*)/, 1],
55
+ additions: lines.select { |l| l.start_with?("+") }.length,
56
+ deletions: lines.select { |l| l.start_with?("-") }.length
57
+ }
58
+
59
+ original_line_number = 0
60
+ new_line_number = 0
61
+ diff_object[:lines] = lines.map do |line|
62
+ line = ERB::Util.html_escape(line.chomp).
63
+ gsub("\\", "&#92;").
64
+ gsub("\t", "&nbsp;"*8)
65
+ case line
66
+ when /^@.*/
67
+ entry = {
68
+ type: "header",
69
+ content: line
70
+ }
71
+ original_line_number = line[/-(\d+)/, 1].to_i
72
+ new_line_number = line[/\+(\d+)/, 1].to_i
73
+ when /^ .*/, ""
74
+ entry = {
75
+ type: "common",
76
+ new_line_number: new_line_number,
77
+ original_line_number: original_line_number,
78
+ content: line[1..-1]
79
+ }
80
+ new_line_number += 1
81
+ original_line_number += 1
82
+ when /^\+.*/
83
+ entry = {
84
+ type: "addition",
85
+ new_line_number: new_line_number,
86
+ content: line[1..-1]
87
+ }
88
+ new_line_number += 1
89
+ when /^\-.*/
90
+ entry = {
91
+ type: "deletion",
92
+ original_line_number: original_line_number,
93
+ content: line[1..-1]
94
+ }
95
+ original_line_number += 1
96
+ end
97
+
98
+ entry
99
+ end
100
+
101
+ diff_object
102
+ end
103
+ end
@@ -0,0 +1,93 @@
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 InspectTask
19
+ def inspect_system(store, host, name, current_user, scopes, options = {})
20
+ system = System.for(host)
21
+ check_root(system, current_user)
22
+
23
+ description, failed_inspections = build_description(store, name, system, scopes, options)
24
+
25
+ if !description.attributes.empty?
26
+ description.name = name
27
+ store.save(description)
28
+ print_description(description, scopes) if options[:show]
29
+ end
30
+
31
+ if !failed_inspections.empty?
32
+ Machinery::Ui.puts "\n"
33
+ message = failed_inspections.map { |scope, msg|
34
+ "Errors while inspecting " \
35
+ "#{Machinery::Ui.internal_scope_list_to_string(scope)}:\n#{msg}" }.join("\n\n")
36
+ raise Machinery::Errors::InspectionFailed.new(message)
37
+ end
38
+ description
39
+ end
40
+
41
+ private
42
+
43
+ def check_root(system, current_user)
44
+ if system.requires_root? && !current_user.is_root?
45
+ raise Machinery::Errors::MissingRequirement,
46
+ "Need to be root to inspect local system."
47
+ end
48
+ end
49
+
50
+ def print_description(description, scopes)
51
+ return unless scopes
52
+
53
+ scopes.each do |scope|
54
+ renderer = Renderer.for(scope)
55
+ next unless renderer
56
+
57
+ output = renderer.render(description)
58
+ Machinery::Ui.puts output if output
59
+ end
60
+ end
61
+
62
+ def build_description(store, name, system, scopes, options)
63
+ begin
64
+ description = store.load(name)
65
+ rescue Machinery::Errors::SystemDescriptionNotFound
66
+ description = SystemDescription.new(name, {}, store)
67
+ end
68
+ timestring = Time.now.utc.iso8601
69
+ if system.class == LocalSystem
70
+ host = "localhost"
71
+ else
72
+ host = system.host
73
+ end
74
+
75
+ failed_inspections = {}
76
+
77
+ scopes.map { |s| Inspector.for(s) }.each do |inspector|
78
+ Machinery::Ui.puts "Inspecting #{Machinery::Ui.internal_scope_list_to_string(inspector.scope)}..."
79
+ begin
80
+ summary = inspector.inspect(system, description, options)
81
+ rescue Machinery::Errors::MachineryError => e
82
+ Machinery::Ui.puts "Inspection of scope " \
83
+ "#{Machinery::Ui.internal_scope_list_to_string(inspector.scope)} failed!"
84
+ failed_inspections[inspector.scope] = e
85
+ next
86
+ end
87
+ description[inspector.scope].set_metadata(timestring, host)
88
+ Machinery::Ui.puts " -> " + summary
89
+ end
90
+
91
+ return description, failed_inspections
92
+ end
93
+ end
data/lib/inspector.rb ADDED
@@ -0,0 +1,65 @@
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
+ # This is the base class for all the "inspect" plugin classes. It keeps
19
+ # track of the loaded subclasses and allows for querying plugins for a
20
+ # given scope or all available ones.
21
+ #
22
+ # The names of the subclasses are 1:1 mappings of the inspection areas, e.g.
23
+ # the PackagesInspector class would be used for inspection when the user
24
+ # specifies "--scope=packages".
25
+ #
26
+ # All subclasses have to implement the
27
+ #
28
+ # inspect(system, description)
29
+ #
30
+ # method which inspects the given system and stores the gathered information in
31
+ # the description. It returns a brief summary string of the inspection.
32
+ #
33
+ # The description object can also be used to store files in the description.
34
+ class Inspector
35
+ abstract_method :inspect
36
+
37
+ @inspectors = []
38
+
39
+ class << self
40
+ def inherited(klass)
41
+ @inspectors << klass
42
+ end
43
+
44
+ def for(scope)
45
+ class_name = "#{scope.split("_").map(&:capitalize).join}Inspector"
46
+
47
+ Object.const_get(class_name).new if Object.const_defined?(class_name)
48
+ end
49
+
50
+ def all
51
+ @inspectors.map(&:new)
52
+ end
53
+
54
+ def all_scopes
55
+ all.map(&:scope)
56
+ end
57
+ end
58
+
59
+ def scope
60
+ # Return the un-camelcased name of the inspector,
61
+ # e.g. "foo_bar" for "FooBarInspector"
62
+ scope = self.class.name.match(/^(.*)Inspector$/)[1]
63
+ scope.gsub(/([^A-Z])([A-Z])/, "\\1_\\2").downcase
64
+ end
65
+ end
@@ -0,0 +1,356 @@
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 KiwiConfig
19
+ attr_accessor :xml, :sh
20
+
21
+ def initialize(system_description, options = {})
22
+ @system_description = system_description
23
+ @name = system_description.name
24
+ @options = options
25
+
26
+ @system_description.assert_scopes(
27
+ "repositories",
28
+ "packages",
29
+ "os"
30
+ )
31
+ check_existance_of_extraced_files
32
+
33
+ generate_config
34
+ end
35
+
36
+ def write(output_location)
37
+ inject_users_and_groups(output_location)
38
+ inject_extracted_files(output_location)
39
+
40
+ @sh << "baseCleanMount\n"
41
+ @sh << "exit 0\n"
42
+ File.write(File.join(output_location, "config.xml") , @xml.to_xml)
43
+ File.write(File.join(output_location, "config.sh") , @sh)
44
+ FileUtils.cp(
45
+ File.join(Machinery::ROOT, "kiwi_helpers/kiwi_export_readme.md"),
46
+ File.join(output_location, "README.md")
47
+ )
48
+
49
+ post_process_config(output_location)
50
+ end
51
+
52
+ private
53
+
54
+ def pre_process_config
55
+ enable_ssh if @options[:enable_ssh]
56
+ end
57
+
58
+ def post_process_config(output_location)
59
+ enable_dhcp(output_location) if @options[:enable_dhcp]
60
+ end
61
+
62
+ def inject_users_and_groups(output_location)
63
+ return if !@system_description.users || !@system_description.groups
64
+
65
+ merge_script_name = "merge_users_and_groups.pl"
66
+
67
+ template = ERB.new(
68
+ File.read(File.join(Machinery::ROOT, "kiwi_helpers", "#{merge_script_name}.erb"))
69
+ )
70
+
71
+ passwd_entries = @system_description.users.map do |u|
72
+ passwd = [u.name, u.password, u.uid, u.gid, u.comment, u.home, u.shell].join(":")
73
+
74
+ # The shadow file contains an eigth reserved field at the end, so we have
75
+ # to manually add it, too.
76
+ shadow = [u.name, u.encrypted_password, u.last_changed_date, u.min_days,
77
+ u.max_days, u.warn_days, u.disable_days, u.disabled_date, ""].join(":")
78
+ "['#{passwd}', '#{shadow}']"
79
+ end.join(",\n")
80
+ group_entries = @system_description.groups.map do |g|
81
+ "'#{g.name}:#{g.password}:#{g.gid}:#{g.users.join(",")}'"
82
+ end.join(",\n")
83
+
84
+ FileUtils.mkdir_p(File.join(output_location, "root", "tmp"), mode: 01777)
85
+ script_path = File.join(output_location, "root", "tmp", merge_script_name)
86
+ File.write(script_path, template.result(binding))
87
+
88
+ @sh << "perl /tmp/#{merge_script_name} /etc/passwd /etc/shadow /etc/group\n"
89
+ @sh << "rm /tmp/#{merge_script_name}\n"
90
+ end
91
+
92
+ def inject_extracted_files(output_location)
93
+ ["config_files", "changed_managed_files"].each do |dir|
94
+ path = @system_description.file_store(dir)
95
+ if path
96
+ output_root_path = File.join(output_location, "root")
97
+ FileUtils.mkdir_p(output_root_path)
98
+ FileUtils.cp_r(Dir.glob("#{path}/*"), output_root_path)
99
+ end
100
+ end
101
+
102
+ unmanaged_files_path = @system_description.file_store("unmanaged_files")
103
+ if unmanaged_files_path
104
+ filter = "unmanaged_files_build_excludes"
105
+ destination = File.join(output_location, "root", "tmp")
106
+ FileUtils.mkdir_p(destination, mode: 01777)
107
+ FileUtils.cp_r(unmanaged_files_path, destination)
108
+ FileUtils.cp(
109
+ File.join(Machinery::ROOT, "kiwi_helpers/#{filter}"),
110
+ destination
111
+ )
112
+
113
+ @sh << "# Apply the extracted unmanaged files\n"
114
+ @sh << "find /tmp/unmanaged_files -name *.tgz -exec " \
115
+ "tar -C / -X '/tmp/#{filter}' -xf {} \\;\n"
116
+ @sh << "rm -rf '/tmp/unmanaged_files' '/tmp/#{filter}'\n"
117
+ end
118
+ end
119
+
120
+ def check_existance_of_extraced_files
121
+ missing_scopes = []
122
+ ["config_files", "changed_managed_files", "unmanaged_files"].each do |scope|
123
+
124
+ if @system_description[scope] && !@system_description.file_store(scope)
125
+ missing_scopes << scope
126
+ end
127
+ end
128
+
129
+ if !missing_scopes.empty?
130
+ raise Machinery::Errors::MissingExtractedFiles.new(@system_description, missing_scopes)
131
+ end
132
+ end
133
+
134
+ def generate_config
135
+ @sh = <<EOF
136
+ test -f /.kconfig && . /.kconfig
137
+ test -f /.profile && . /.profile
138
+ baseMount
139
+ suseSetupProduct
140
+ suseImportBuildKey
141
+ suseConfig
142
+ EOF
143
+ case @system_description.os_object
144
+ when OsOpenSuse13_1
145
+ boot = "vmxboot/suse-13.1"
146
+ bootloader = "grub2"
147
+ when OsSles12
148
+ boot = "vmxboot/suse-SLES12"
149
+ bootloader = "grub2"
150
+ when OsSles11
151
+ boot = "vmxboot/suse-SLES11"
152
+ bootloader = "grub"
153
+ else
154
+ raise Machinery::Errors::KiwiExportFailed.new(
155
+ "Building is not possible because the operating system " \
156
+ "'#{@system_description.os_object.name}' is not supported."
157
+ )
158
+ end
159
+
160
+ builder = Nokogiri::XML::Builder.new do |xml|
161
+ xml.image(schemaversion: "5.8", name: @system_description.name) do
162
+ xml.description(type: "system") do
163
+ xml.author "Machinery"
164
+ xml.contact ""
165
+ xml.specification "Description of system '#{@system_description.name}' exported by Machinery"
166
+ end
167
+
168
+ xml.preferences do
169
+ xml.packagemanager "zypper"
170
+ xml.version "0.0.1"
171
+ xml.type_(image: "vmx", filesystem: "ext3", installiso: "true",
172
+ boot: boot, format: "qcow2", bootloader: bootloader)
173
+ end
174
+
175
+ xml.users(group: "root") do
176
+ xml.user(password: "$1$wYJUgpM5$RXMMeASDc035eX.NbYWFl0",
177
+ home: "/root", name: "root")
178
+ end
179
+
180
+
181
+ apply_repositories(xml)
182
+ apply_packages(xml)
183
+ apply_extracted_files_attributes
184
+ apply_services
185
+ end
186
+ end
187
+
188
+ pre_process_config
189
+
190
+ @xml = builder.doc
191
+ end
192
+
193
+ def apply_packages(xml)
194
+ build_filter = YAML.load_file(File.join(
195
+ Machinery::ROOT, "helpers", "filter-packages-for-build.yaml")
196
+ )
197
+ filter = build_filter[@system_description.os_object.name] || []
198
+
199
+ xml.packages(type: "bootstrap") do
200
+ if @system_description.packages
201
+ @system_description.packages.each do |package|
202
+ next if filter.include?(package.name)
203
+ xml.package(name: "#{package.name}")
204
+ end
205
+ end
206
+ pattern_array = Array.new
207
+ if @system_description.patterns
208
+ @system_description.patterns.each do |pattern|
209
+ xml.namedCollection(name: "#{pattern.name}")
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ def apply_repositories(xml)
216
+ if @system_description.repositories
217
+ @system_description.repositories.each do |repo|
218
+ # only use accessible repositories as source for kiwi build
219
+ parameters = { alias: repo.alias, type: repo.type, priority: repo.priority }
220
+ if repo.username && repo.password
221
+ parameters[:username] = repo.username
222
+ parameters[:password] = repo.password
223
+ end
224
+ is_external_medium = repo.url.start_with?("cd://") ||
225
+ repo.url.start_with?("dvd://")
226
+ if repo.enabled && !repo.type.nil? && !is_external_medium
227
+ xml.repository(parameters) do
228
+ xml.source(path: repo.url)
229
+ end
230
+ end
231
+ if !repo.url.match(/^https:\/\/nu.novell.com|^https:\/\/update.suse.com/)
232
+ @sh << "zypper -n ar --name='#{repo.name}' "
233
+ @sh << "--type='#{repo.type}' " if repo.type
234
+ @sh << "--refresh " if repo.autorefresh
235
+ @sh << "--disable " unless repo.enabled
236
+ @sh << "'#{repo.url}' '#{repo.alias}'\n"
237
+ @sh << "zypper -n mr --priority=#{repo.priority} '#{repo.name}'\n"
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ def apply_extracted_files_attributes
244
+ ["config_files", "changed_managed_files"].each do |scope|
245
+ if @system_description[scope]
246
+ deleted, files = @system_description[scope].files.partition do |f|
247
+ f.changes == Machinery::Array.new(["deleted"])
248
+ end
249
+
250
+ files.each do |file|
251
+ @sh << "chmod #{file.mode} '#{file.name}'\n"
252
+ @sh << "chown #{file.user}:#{file.group} '#{file.name}'\n"
253
+ end
254
+
255
+ deleted.each do |file|
256
+ @sh << "rm -rf '#{file.name}'\n"
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ def apply_services
263
+ if @system_description["services"]
264
+ init_system = @system_description["services"].init_system
265
+
266
+ case init_system
267
+ when "sysvinit"
268
+ @system_description["services"].services.each do |service|
269
+ if service.state == "on"
270
+ @sh << "chkconfig #{service.name} on\n"
271
+ else
272
+ @sh << "chkconfig #{service.name} off\n"
273
+ end
274
+ end
275
+
276
+ when "systemd"
277
+ # possible systemd service states:
278
+ # http://www.freedesktop.org/software/systemd/man/systemctl.html#Unit%20File%20Commands
279
+ @system_description["services"].services.each do |service|
280
+ case service.state
281
+ when "enabled"
282
+ @sh << "systemctl enable #{service.name}\n"
283
+ when "disabled"
284
+ @sh << "systemctl disable #{service.name}\n"
285
+ when "masked"
286
+ @sh << "systemctl mask #{service.name}\n"
287
+ when "static"
288
+ # Don't do anything because the unit is not meant to be
289
+ # enabled/disabled manually.
290
+ when "linked"
291
+ # Don't do anything because linking doesn't mean enabling
292
+ # nor disabling.
293
+ when "enabled-runtime"
294
+ when "linked-runtime"
295
+ when "masked-runtime"
296
+ # Don't do anything because these states are not supposed
297
+ # to be permanent.
298
+ else
299
+ raise Machinery::Errors::KiwiExportFailed.new(
300
+ "The systemd unit state #{service.state} is unknown."
301
+ )
302
+ end
303
+ end
304
+
305
+ else
306
+ raise "Unsupported init system: #{init_system.inspect}."
307
+ end
308
+ end
309
+ end
310
+
311
+ def enable_dhcp(output_location)
312
+ case @system_description.os_object
313
+ when OsSles11
314
+ write_dhcp_network_config(output_location, "eth0")
315
+ when OsSles12
316
+ write_dhcp_network_config(output_location, "lan0")
317
+ write_persistent_net_rules(output_location)
318
+ when OsOpenSuse13_1
319
+ write_dhcp_network_config(output_location, "lan0")
320
+ write_persistent_net_rules(output_location)
321
+ end
322
+ puts "DHCP in built image will be enabled for the first device"
323
+ end
324
+
325
+ def write_dhcp_network_config(output_location, device)
326
+ network_location = File.join(output_location, "root/etc/sysconfig/network")
327
+ FileUtils.mkdir_p(network_location)
328
+ File.write(File.join(network_location, "ifcfg-#{device}"),
329
+ "BOOTPROTO='dhcp'\nSTARTMODE='onboot'"
330
+ )
331
+ end
332
+
333
+ def write_persistent_net_rules(output_location)
334
+ udev_location = File.join(output_location, "root/etc/udev/rules.d")
335
+ persistent_net_rule = [
336
+ 'SUBSYSTEM=="net"',
337
+ 'ACTION=="add"',
338
+ 'DRIVERS=="?*"',
339
+ 'ATTR{address}=="?*"',
340
+ 'ATTR{dev_id}=="0x0"',
341
+ 'ATTR{type}=="1"',
342
+ 'KERNEL=="?*"',
343
+ 'NAME="lan0"'
344
+ ]
345
+
346
+ FileUtils.mkdir_p(udev_location)
347
+ File.write(
348
+ File.join(udev_location, "70-persistent-net.rules"),
349
+ persistent_net_rule.join(", ")
350
+ )
351
+ end
352
+
353
+ def enable_ssh
354
+ @sh << "suseInsertService sshd\n"
355
+ end
356
+ end