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
data/lib/build_task.rb ADDED
@@ -0,0 +1,124 @@
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 BuildTask
19
+ def build(system_description, output_path, options = {})
20
+ LocalSystem.validate_build_compatibility(system_description)
21
+ LocalSystem.validate_existence_of_package("kiwi")
22
+ LocalSystem.validate_existence_of_package("kiwi-desc-vmxboot")
23
+
24
+ tmp_config_dir = Dir.mktmpdir("machinery-config", "/tmp")
25
+ tmp_image_dir = Dir.mktmpdir("machinery-image", "/tmp")
26
+ img_extension = "qcow2"
27
+
28
+ config = KiwiConfig.new(system_description, options)
29
+ config.write(tmp_config_dir)
30
+
31
+ if system_description["unmanaged_files"]
32
+ filters = File.read(
33
+ File.join(Machinery::ROOT, "kiwi_helpers/unmanaged_files_build_excludes")
34
+ )
35
+ Machinery::Ui.puts "\nUnmanaged files following these patterns are not added " \
36
+ "to the built image:"
37
+ Machinery::Ui.puts filters
38
+ Machinery::Ui.puts "\n"
39
+ end
40
+
41
+ FileUtils.mkdir_p(output_path)
42
+ if tmp_image_dir.start_with?("/tmp/") && tmp_config_dir.start_with?("/tmp/")
43
+ tmp_script = write_kiwi_wrapper(tmp_config_dir, tmp_image_dir,
44
+ output_path, img_extension)
45
+
46
+ begin
47
+ LoggedCheetah.run("sudo", tmp_script.path, :stdout => $stdout,
48
+ :stderr => $stderr)
49
+ rescue SignalException => e
50
+ # Handle SIGHUP(1), SIGINT(2) and SIGTERM(15) gracefully
51
+ if [1, 2, 15].include?(e.signo)
52
+ Machinery::Ui.warn "Warning: Interrupted by user. Waiting for build process to abort..."
53
+
54
+ # When we got a SIGHUP or a SIGTERM we send a SIGINT to all processes
55
+ # in our progress group (forked by Cheetah).
56
+ # For SIGINT that's not needed because it is propagated automatically.
57
+ #
58
+ # The reason for killing the child processes with SIGINT (vs SIGTERM)
59
+ # is that with SIGTERM the bash wrapper script around kiwi returns
60
+ # while the unmounting of /proc is still in progress. That would break
61
+ # the cleanup of the temporary kiwi directories below.
62
+ if [1, 15].include?(e.signo)
63
+ trap("INT") {}
64
+ `sudo kill -INT -#{Process.getpgrp}`
65
+ end
66
+ Process.waitall
67
+
68
+ Machinery::Ui.warn "Cleaning up temporary files..."
69
+ [tmp_config_dir, tmp_image_dir].each do |path|
70
+ LoggedCheetah.run("sudo", "rm", "-r", path) if Dir.exists?(path)
71
+ end
72
+ end
73
+ raise
74
+ ensure
75
+ tmp_script.delete
76
+ end
77
+ else
78
+ raise RuntimeError.new(
79
+ "The Kiwi temporary build directories are not in /tmp. This should " \
80
+ "never happen, so nothing is deleted."
81
+ )
82
+ end
83
+
84
+ image_file = Dir.glob(File.join(output_path, "*.#{img_extension}")).first
85
+
86
+ if !image_file
87
+ raise(Machinery::Errors::BuildFailed, "The image build process failed. Check " \
88
+ "build log '#{tmp_image_dir}/kiwi-terminal-output.log' for more " \
89
+ "details."
90
+ )
91
+ end
92
+
93
+ meta_data = {
94
+ description: system_description.name,
95
+ image_file: File.basename(image_file)
96
+ }
97
+ File.write(File.join(output_path, Machinery::IMAGE_META_DATA_FILE),
98
+ meta_data.to_yaml
99
+ )
100
+ end
101
+
102
+ def kiwi_wrapper(tmp_config_dir, tmp_image_dir, output_path, image_extension)
103
+ script = "#!/bin/bash\n"
104
+ script << "/usr/sbin/kiwi --build '#{tmp_config_dir}' --destdir '#{tmp_image_dir}' --logfile '#{tmp_image_dir}/kiwi-terminal-output.log'\n"
105
+ script << "if [ $? -eq 0 ]; then\n"
106
+ script << " mv '#{tmp_image_dir}/'*.#{image_extension} '#{output_path}'\n"
107
+ script << " rm -rf '#{tmp_image_dir}'\n"
108
+ script << "else\n"
109
+ script << " echo -e 'Building the Image with Kiwi failed.\nThe Kiwi build directory #{tmp_image_dir} was not removed.'\n"
110
+ script << "fi\n"
111
+ script << "rm -rf '#{tmp_config_dir}'\n"
112
+ end
113
+
114
+ def write_kiwi_wrapper(tmp_config_dir, tmp_image_dir, output_path, image_extension)
115
+ begin
116
+ script = Tempfile.new('machinery-kiwi-wrapper-script')
117
+ script << kiwi_wrapper(tmp_config_dir, tmp_image_dir, output_path, image_extension)
118
+ ensure
119
+ script.close unless script == nil
120
+ end
121
+ File.chmod(0755, script.path)
122
+ script
123
+ end
124
+ end
@@ -0,0 +1,96 @@
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 ChangedRpmFilesHelper
19
+ def parse_rpm_changes_line(line)
20
+ # rpm provides lines per config file where first 9 characters indicate which
21
+ # properties of the file are modified
22
+ rpm_changes, *fields = line.split(" ")
23
+
24
+ # For config or documentation files there's an additional field which
25
+ # contains "c" or "d"
26
+ type = fields[0].start_with?("/") ? "" : fields.shift
27
+ path = fields.join(" ")
28
+
29
+ changes = []
30
+ if rpm_changes == "missing"
31
+ changes << "deleted"
32
+ elsif rpm_changes == "........." && path.end_with?(" (replaced)")
33
+ changes << "replaced"
34
+ path.slice!(/ \(replaced\)$/)
35
+ else
36
+ changes << "mode" if rpm_changes[1] == "M"
37
+ changes << "md5" if rpm_changes[2] == "5"
38
+ changes << "user" if rpm_changes[5] == "U"
39
+ changes << "group" if rpm_changes[6] == "G"
40
+ end
41
+ [path, changes, type]
42
+ end
43
+
44
+ def parse_stat_line(line)
45
+ mode, user, group, uid, gid, *path = line.split(":")
46
+
47
+ user = uid if user == "UNKNOWN"
48
+ group = gid if group == "UNKNOWN"
49
+
50
+ [path.join(":").chomp,
51
+ {
52
+ :mode => mode,
53
+ :user => user,
54
+ :group => group
55
+ }
56
+ ]
57
+ end
58
+
59
+ # get path data for list of files
60
+ # cur_files is guaranteed to not exceed max command line length
61
+ def get_file_properties(system, cur_files)
62
+ ret = {}
63
+ out = system.run_command(
64
+ "stat", "--printf", "%a:%U:%G:%u:%g:%n\\n",
65
+ *cur_files,
66
+ :stdout => :capture
67
+ )
68
+ out.each_line do |l|
69
+ path, values = parse_stat_line(l)
70
+ ret[path] = values
71
+ end
72
+ ret
73
+ end
74
+
75
+ def get_path_data(system, paths)
76
+ ret = {}
77
+ path_index = 0
78
+ # arbitrary number for maximum command line length that should always work
79
+ max_len = 50000
80
+ cur_files = []
81
+ cur_len = 0
82
+ while path_index < paths.size
83
+ if cur_files.empty? || paths[path_index].size + cur_len + 1 < max_len
84
+ cur_files << paths[path_index]
85
+ cur_len += paths[path_index].size + 1
86
+ path_index += 1
87
+ else
88
+ ret.merge!(get_file_properties(system, cur_files))
89
+ cur_files.clear
90
+ cur_len = 0
91
+ end
92
+ end
93
+ ret.merge!(get_file_properties(system, cur_files)) unless cur_files.empty?
94
+ ret
95
+ end
96
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,600 @@
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 Cli
19
+ extend GLI::App
20
+
21
+ program_desc 'A systems management toolkit for Linux'
22
+ preserve_argv(true)
23
+ @version = Machinery::VERSION + " (system description format version " +
24
+ "#{SystemDescription::CURRENT_FORMAT_VERSION})"
25
+ switch :version, :negatable => false, :desc => "Show version"
26
+ switch :debug, :negatable => false, :desc => "Enable debug mode"
27
+ switch [:help, :h], :negatable => false, :desc => "Show help"
28
+
29
+ sort_help :manually
30
+ pre do |global_options,command,options,args|
31
+ if global_options[:debug]
32
+ Machinery.logger.level = Logger::DEBUG
33
+ else
34
+ Machinery.logger.level = Logger::INFO
35
+ end
36
+ end
37
+
38
+ post do |global_options,command,options,args|
39
+ if command.is_a?(GLI::Commands::Help) && !global_options[:version]
40
+
41
+ Machinery::Ui.puts "\nMachinery can show hints which guide through a typical workflow."
42
+ if Machinery::Config.new.hints
43
+ Machinery::Ui.puts "These hints can be switched off by '#{$0} config hints off'."
44
+ else
45
+ Machinery::Ui.puts "These hints can be switched on by '#{$0} config hints on'."
46
+ end
47
+
48
+ Hint.get_started
49
+ end
50
+ end
51
+
52
+ GLI::Commands::Help.skips_post = false
53
+
54
+ def self.handle_error(e)
55
+ case e
56
+ when GLI::UnknownCommandArgument, GLI::UnknownGlobalArgument,
57
+ GLI::UnknownCommand, GLI::BadCommandLine, OptionParser::MissingArgument
58
+ Machinery::Ui.error e.to_s + "\n\n"
59
+ command = ARGV & @commands.keys.map(&:to_s)
60
+ run(command << "--help")
61
+ exit 1
62
+ when Machinery::Errors::MachineryError
63
+ Machinery.logger.error(e.message)
64
+ Machinery::Ui.error e.message
65
+ exit 1
66
+ when SystemExit
67
+ raise
68
+ when SignalException
69
+ Machinery.logger.info "Machinery was aborted with signal #{e.signo}."
70
+ exit 1
71
+ else
72
+ Machinery::Ui.error "Machinery experienced an unexpected error. Please file a " \
73
+ "bug report at https://github.com/SUSE/machinery/issues/new.\n"
74
+ if e.is_a?(Cheetah::ExecutionFailed)
75
+ result = ""
76
+ result << "#{e.message}\n"
77
+ result << "\n"
78
+
79
+ if e.stderr && !e.stderr.empty?
80
+ result << "Error output:\n"
81
+ result << "#{e.stderr}\n"
82
+ end
83
+
84
+ if e.stdout && !e.stdout.empty?
85
+ result << "Standard output:\n"
86
+ result << "#{e.stdout}\n\n"
87
+ end
88
+
89
+ if e.backtrace && !e.backtrace.empty?
90
+ result << "Backtrace:\n"
91
+ result << "#{e.backtrace.join("\n")}\n\n"
92
+ end
93
+ Machinery.logger.error(result)
94
+ Machinery::Ui.error result
95
+ exit 1
96
+ else
97
+ Machinery.logger.error("Machinery experienced an unexpected error:")
98
+ Machinery.logger.error(e.message)
99
+ Machinery.logger.error(e.backtrace.join("\n"))
100
+ raise
101
+ end
102
+ end
103
+ true
104
+ end
105
+
106
+
107
+ on_error do |e|
108
+ Cli.handle_error(e)
109
+ end
110
+
111
+ def self.shift_arg(args, name)
112
+ if !res = args.shift
113
+ raise GLI::BadCommandLine.new("Machinery was called with missing argument #{name}.")
114
+ end
115
+ res
116
+ end
117
+
118
+ def self.process_scope_option(scopes, exclude_scopes)
119
+ if scopes
120
+ if exclude_scopes
121
+ # scope and exclude-scope
122
+ raise Machinery::Errors::InvalidCommandLine.new( "You cannot provide the --scope and --exclude-scope option at the same time.")
123
+ else
124
+ # scope only
125
+ scope_list = parse_scopes(scopes)
126
+ end
127
+ else
128
+ if exclude_scopes
129
+ # exclude-scope only
130
+ scope_list = Inspector.all_scopes - parse_scopes(exclude_scopes)
131
+ else
132
+ # neither scope nor exclude-scope
133
+ scope_list = Inspector.all_scopes
134
+ end
135
+ end
136
+ if scope_list.empty?
137
+ raise Machinery::Errors::InvalidCommandLine.new( "No scopes to process. Nothing to do.")
138
+ end
139
+ scope_list
140
+ end
141
+
142
+ def self.parse_scopes(scope_string)
143
+ unknown_scopes = []
144
+ invalid_scopes = []
145
+ scopes = []
146
+
147
+ scope_string.split(",").each do |scope|
148
+ if !(scope =~ /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/)
149
+ invalid_scopes << scope
150
+ next
151
+ end
152
+
153
+ # convert cli scope naming to internal one
154
+ scope.tr!("-", "_")
155
+
156
+ if Inspector.all_scopes.include?(scope) && Renderer.for(scope)
157
+ scopes << scope
158
+ else
159
+ unknown_scopes << scope
160
+ end
161
+ end
162
+
163
+ if invalid_scopes.length > 0
164
+ form = invalid_scopes.length > 1 ? "scopes are" : "scope is"
165
+ raise Machinery::Errors::UnknownScope.new(
166
+ "The following #{form} not valid:" \
167
+ " \"#{invalid_scopes.join("\", \"")}\"." \
168
+ " Scope names must start with a letter and contain only lowercase" \
169
+ " letters and digits separated by dashes (\"-\")."
170
+ )
171
+ end
172
+
173
+ if unknown_scopes.length > 0
174
+ form = unknown_scopes.length > 1 ? "scopes are" : "scope is"
175
+ raise Machinery::Errors::UnknownScope.new(
176
+ "The following #{form} not supported: " \
177
+ "#{Machinery::Ui.internal_scope_list_to_string(unknown_scopes)}. " \
178
+ "Valid scopes are: #{AVAILABLE_SCOPE_LIST}."
179
+ )
180
+ end
181
+
182
+ scopes
183
+ end
184
+
185
+ AVAILABLE_SCOPE_LIST = Machinery::Ui.internal_scope_list_to_string(
186
+ Inspector.all_scopes
187
+ )
188
+
189
+ desc "Analyze system description"
190
+ long_desc <<-LONGDESC
191
+ Analyze stored system description.
192
+
193
+ The supported operations are:
194
+
195
+ - config-file-diffs: Generate diffs against the original version from
196
+ the package for the modified config files
197
+ LONGDESC
198
+ arg "NAME"
199
+ command :analyze do |c|
200
+ c.flag [:operation, :o], :type => String, :required => true,
201
+ :desc => "The analyze operation to perform", :arg_name => "OPERATION"
202
+
203
+ c.action do |global_options,options,args|
204
+ name = shift_arg(args, "NAME")
205
+ store = SystemDescriptionStore.new
206
+ description = store.load(name)
207
+
208
+ case options[:operation]
209
+ when "config-file-diffs"
210
+ task = AnalyzeConfigFileDiffsTask.new
211
+ task.analyze(description)
212
+ else
213
+ raise Machinery::Errors::InvalidCommandLine.new(
214
+ "The operation '#{options[:operation]}' is not supported. " \
215
+ "Valid operations are: config-file-diffs."
216
+ )
217
+ end
218
+ end
219
+ end
220
+
221
+
222
+
223
+ desc "Build image from system description"
224
+ long_desc <<-LONGDESC
225
+ Build image from a given system description and store it to the given
226
+ location.
227
+ LONGDESC
228
+ arg "NAME"
229
+ command :build do |c|
230
+ c.flag ["image-dir", :i], :type => String, :required => true,
231
+ :desc => "Store the image under the specified path", :arg_name => "DIRECTORY"
232
+ c.switch ["enable-dhcp", :d], :required => false, :negatable => false,
233
+ :desc => "Enable DCHP client on first network card of built image"
234
+ c.switch ["enable-ssh", :s], :required => false, :negatable => false,
235
+ :desc => "Enable SSH service in built image"
236
+
237
+ c.action do |global_options,options,args|
238
+ name = shift_arg(args, "NAME")
239
+ store = SystemDescriptionStore.new
240
+ description = store.load(name)
241
+
242
+ task = BuildTask.new
243
+ task.build(description, File.expand_path(options["image-dir"]), {:enable_dhcp => options["enable-dhcp"], :enable_ssh => options["enable-ssh"]})
244
+ end
245
+ end
246
+
247
+
248
+
249
+ desc "Compare system descriptions"
250
+ long_desc <<-LONGDESC
251
+ Compare system descriptions stored under specified names.
252
+
253
+ Multiple scopes can be passed as comma-separated list. If no specific scopes
254
+ are given, all scopes are compared.
255
+
256
+ Available scopes: #{AVAILABLE_SCOPE_LIST}
257
+ LONGDESC
258
+ arg "NAME1"
259
+ arg "NAME2"
260
+ command :compare do |c|
261
+ c.flag [:scope, :s], :type => String, :required => false,
262
+ :desc => "Compare specified scopes", :arg_name => "SCOPE_LIST"
263
+ c.flag ["exclude-scope", :e], :type => String, :required => false,
264
+ :desc => "Exclude specified scopes", :arg_name => "SCOPE_LIST"
265
+ c.switch "show-all", :required => false, :negatable => false,
266
+ :desc => "Show also common properties"
267
+ c.switch "pager", :required => false, :default_value => true,
268
+ :desc => "Pipe output into a pager"
269
+
270
+ c.action do |global_options,options,args|
271
+ name1 = shift_arg(args, "NAME1")
272
+ name2 = shift_arg(args, "NAME2")
273
+ store = SystemDescriptionStore.new
274
+ description1 = store.load(name1)
275
+ description2 = store.load(name2)
276
+ scope_list = process_scope_option(options[:scope], options["exclude-scope"])
277
+
278
+ task = CompareTask.new
279
+ opts = {
280
+ show_all: options["show-all"],
281
+ no_pager: !options["pager"]
282
+ }
283
+ task.compare(description1, description2, scope_list, opts)
284
+ end
285
+ end
286
+
287
+
288
+
289
+ desc "Copy system description"
290
+ long_desc <<-LONGDESC
291
+ Copy a system description.
292
+
293
+ The system description is copied and stored under the provided name.
294
+ LONGDESC
295
+ arg_name "FROM_NAME TO_NAME"
296
+ command :copy do |c|
297
+ c.action do |global_options,options,args|
298
+ from = shift_arg(args, "FROM_NAME")
299
+ to = shift_arg(args, "TO_NAME")
300
+ store = SystemDescriptionStore.new
301
+ task = CopyTask.new
302
+ task.copy(store, from, to)
303
+ end
304
+ end
305
+
306
+
307
+
308
+ desc "Deploy image to OpenStack cloud"
309
+ long_desc <<-LONGDESC
310
+ Deploy system description as image to OpenStack cloud.
311
+
312
+ The image will be deployed to the OpenStack cloud. If no --image-dir is
313
+ specified an image will be built from the description before deployment.
314
+ LONGDESC
315
+ arg "NAME"
316
+ command :deploy do |c|
317
+ c.flag ["cloud-config", :c], :type => String, :required => true, :arg_name => "FILE",
318
+ :desc => "Path to file where the cloud config (openrc.sh) is located"
319
+ c.flag ["image-dir", :i], :type => String, :required => false,
320
+ :desc => "Directory where the image is located", :arg_name => "DIRECTORY"
321
+ c.switch [:insecure, :s], :required => false, :negatable => false,
322
+ :desc => "Explicitly allow glanceclient to perform 'insecure SSL' (https) requests."
323
+ c.flag ["cloud-image-name", :n], :type => String, :required => false,
324
+ :desc => "Name of the image in the cloud", :arg_name => "NAME"
325
+
326
+ c.action do |global_options,options,args|
327
+ name = shift_arg(args, "NAME")
328
+ store = SystemDescriptionStore.new
329
+ description = store.load(name)
330
+
331
+ task = DeployTask.new
332
+ opts = {
333
+ image_name: options[:cloud_image_name],
334
+ insecure: options[:insecure]
335
+ }
336
+ opts[:image_dir] = File.expand_path(options["image-dir"]) if options["image-dir"]
337
+ task.deploy(description, File.expand_path(options["cloud-config"]), opts)
338
+ end
339
+ end
340
+
341
+
342
+
343
+ desc "Export system description as KIWI image description"
344
+ long_desc <<-LONGDESC
345
+ Export system description as KIWI image description.
346
+
347
+ The description will be placed in the given location. The image format in the
348
+ description is 'vmx'.
349
+ LONGDESC
350
+ arg "NAME"
351
+ command "export-kiwi" do |c|
352
+ c.flag ["kiwi-dir", :k], :type => String, :required => true,
353
+ :desc => "Location where the description will be stored", :arg_name => "DIRECTORY"
354
+ c.switch :force, :default_value => false, :required => false, :negatable => false,
355
+ :desc => "Overwrite existing description"
356
+
357
+ c.action do |global_options,options,args|
358
+ name = shift_arg(args, "NAME")
359
+ store = SystemDescriptionStore.new
360
+ description = store.load(name)
361
+
362
+ task = KiwiExportTask.new
363
+ task.export(description, File.expand_path(options["kiwi-dir"]), force: options[:force])
364
+ end
365
+ end
366
+
367
+
368
+
369
+ desc "Inspect running system"
370
+ long_desc <<-LONGDESC
371
+ Inspect running system and generate system descripton from inspected data.
372
+
373
+ Multiple scopes can be passed as comma-separated list. If no specific scopes
374
+ are given, all scopes are inspected.
375
+
376
+ Available scopes: #{AVAILABLE_SCOPE_LIST}
377
+ LONGDESC
378
+ arg "HOSTNAME"
379
+ command :inspect do |c|
380
+ c.flag [:name, :n], :type => String, :required => false, :arg_name => "NAME",
381
+ :desc => "Store system description under the specified name"
382
+ c.flag [:scope, :s], :type => String, :required => false,
383
+ :desc => "Show specified scopes", :arg_name => "SCOPE_LIST"
384
+ c.flag ["exclude-scope", :e], :type => String, :required => false,
385
+ :desc => "Exclude specified scopes", :arg_name => "SCOPE_LIST"
386
+ c.switch ["extract-files", :x], :required => false, :negatable => false,
387
+ :desc => "Extract changed configuration files and unmanaged files from inspected system"
388
+ c.switch "extract-changed-config-files", :required => false, :negatable => false,
389
+ :desc => "Extract changed configuration files from inspected system"
390
+ c.switch "extract-unmanaged-files", :required => false, :negatable => false,
391
+ :desc => "Extract unmanaged files from inspected system"
392
+ c.switch "extract-changed-managed-files", :required => false, :negatable => false,
393
+ :desc => "Extract changed managed files from inspected system"
394
+ c.switch :show, :required => false, :negatable => false,
395
+ :desc => "Print inspection result"
396
+
397
+ c.action do |global_options,options,args|
398
+ host = shift_arg(args, "HOSTNAME")
399
+ store = SystemDescriptionStore.new
400
+ inspector_task = InspectTask.new
401
+ scope_list = process_scope_option(options[:scope], options["exclude-scope"])
402
+ name = options[:name] || host
403
+
404
+
405
+ if !scope_list.empty?
406
+ inspected_scopes = " for #{Machinery::Ui.internal_scope_list_to_string(scope_list)}"
407
+ end
408
+ Machinery::Ui.puts "Inspecting #{host}#{inspected_scopes}..."
409
+
410
+ inspect_options = {}
411
+ if options["show"]
412
+ inspect_options[:show] = true
413
+ end
414
+ if options["extract-files"] || options["extract-changed-config-files"]
415
+ inspect_options[:extract_changed_config_files] = true
416
+ end
417
+ if options["extract-files"] || options["extract-changed-managed-files"]
418
+ inspect_options[:extract_changed_managed_files] = true
419
+ end
420
+ if options["extract-files"] || options["extract-unmanaged-files"]
421
+ inspect_options[:extract_unmanaged_files] = true
422
+ end
423
+
424
+ inspector_task.inspect_system(
425
+ store, host, name, CurrentUser.new, scope_list, inspect_options
426
+ )
427
+
428
+ Hint.show_data(:name => name)
429
+
430
+ if !options["extract-files"] || Inspector.all_scopes.count != scope_list.count
431
+ Hint.do_complete_inspection(:name => name, :host => host)
432
+ end
433
+ end
434
+ end
435
+
436
+
437
+
438
+ desc "List system descriptions"
439
+ long_desc <<-LONGDESC
440
+ List system descriptions and their stored scopes.
441
+
442
+ The date of modification for each scope can be shown with the verbose
443
+ option.
444
+ LONGDESC
445
+ command :list do |c|
446
+ c.switch :verbose, :required => false, :negatable => false,
447
+ :desc => "Display additional information about origin of scopes"
448
+
449
+ c.action do |global_options,options,args|
450
+ store = SystemDescriptionStore.new
451
+ task = ListTask.new
452
+ task.list(store, options)
453
+ end
454
+ end
455
+
456
+
457
+
458
+ desc "Remove system description"
459
+ long_desc <<-LONGDESC
460
+ Remove system description stored under the specified name.
461
+
462
+ The success of a removal can be shown with the verbose option.
463
+ LONGDESC
464
+ arg "NAME"
465
+ command :remove do |c|
466
+ c.switch :all, :negatable => false,
467
+ :desc => "Remove all system descriptions"
468
+ c.switch :verbose, :required => false, :negatable => false,
469
+ :desc => "Explain what is being done"
470
+
471
+ c.action do |global_options,options,args|
472
+ name = shift_arg(args, "NAME") if !options[:all]
473
+
474
+ store = SystemDescriptionStore.new
475
+ task = RemoveTask.new
476
+ task.remove(store, name, :verbose => options[:verbose], :all => options[:all])
477
+ end
478
+ end
479
+
480
+
481
+
482
+ desc "Show system description"
483
+ long_desc <<-LONGDESC
484
+ Show system description stored under the specified name.
485
+
486
+ Multiple scopes can be passed as comma-separated list. If no specific scopes
487
+ are given, all scopes are shown.
488
+
489
+ Available scopes: #{AVAILABLE_SCOPE_LIST}
490
+ LONGDESC
491
+ arg "NAME"
492
+ command :show do |c|
493
+ c.flag [:scope, :s], :type => String, :required => false,
494
+ :desc => "Show specified scopes", :arg_name => "SCOPE_LIST"
495
+ c.flag ["exclude-scope", :e], :type => String, :required => false,
496
+ :desc => "Exclude specified scopes", :arg_name => "SCOPE_LIST"
497
+ c.switch "pager", :required => false, :default_value => true,
498
+ :desc => "Pipe output into a pager"
499
+ c.switch "show-diffs", :required => false, :negatable => false,
500
+ :desc => "Show diffs of configuration files changes."
501
+ c.switch "html", :required => false, :negatable => false,
502
+ :desc => "Open system description in HTML format in your web browser."
503
+
504
+ c.action do |global_options,options,args|
505
+ name = shift_arg(args, "NAME")
506
+ if name == "localhost" && !CurrentUser.new.is_root?
507
+ Machinery::Ui.puts "You need root rights to access the system description of your locally inspected system."
508
+ end
509
+
510
+ store = SystemDescriptionStore.new
511
+ description = store.load(name)
512
+ scope_list = process_scope_option(options[:scope], options["exclude-scope"])
513
+
514
+
515
+ task = ShowTask.new
516
+ opts = {
517
+ no_pager: !options["pager"],
518
+ show_diffs: options["show-diffs"],
519
+ show_html: options["html"]
520
+ }
521
+ task.show(description, scope_list, opts)
522
+ end
523
+ end
524
+
525
+
526
+ desc "Validate system description"
527
+ long_desc <<-LONGDESC
528
+ Validate system description stored under the specified name.
529
+ LONGDESC
530
+ arg "NAME"
531
+ command :validate do |c|
532
+ c.action do |global_options,options,args|
533
+ name = shift_arg(args, "NAME")
534
+ if name == "localhost" && !CurrentUser.new.is_root?
535
+ Machinery::Ui.puts "You need root rights to access the system description of your locally inspected system."
536
+ end
537
+
538
+ store = SystemDescriptionStore.new
539
+ task = ValidateTask.new
540
+ task.validate(store, name)
541
+ end
542
+ end
543
+
544
+ desc "Upgrade format of system description"
545
+ long_desc <<-LONGDESC
546
+ Upgrade the format of one or all system descriptions.
547
+ LONGDESC
548
+ arg "NAME"
549
+ command "upgrade-format" do |c|
550
+ c.switch :all, :negatable => false,
551
+ :desc => "Upgrade all system descriptions"
552
+
553
+ c.action do |global_options,options,args|
554
+ name = shift_arg(args, "NAME") if !options[:all]
555
+
556
+ store = SystemDescriptionStore.new
557
+ task = UpgradeFormatTask.new
558
+ task.upgrade(store, name, :all => options[:all])
559
+ end
560
+ end
561
+
562
+ desc "Generate an HTML view for a system description"
563
+ long_desc <<-LONGDESC
564
+ Generates an HTML view for a system description.
565
+ LONGDESC
566
+ arg "NAME"
567
+ command "generate-html" do |c|
568
+ c.action do |global_options,options,args|
569
+ name = shift_arg(args, "NAME")
570
+
571
+ store = SystemDescriptionStore.new
572
+ description = store.load(name)
573
+ task = GenerateHtmlTask.new
574
+ task.generate(description)
575
+ end
576
+ end
577
+
578
+ desc "Show or change machinery's configuration"
579
+ long_desc <<-LONGDESC
580
+ Show or change machinery's configuration.
581
+
582
+ The value of a key is shown when no value argument is passed.
583
+ If neither the key argument nor the value argument are specified a list of all keys and their values are shown.
584
+ LONGDESC
585
+ arg "KEY", :optional
586
+ arg "VALUE", :optional
587
+ command "config" do |c|
588
+ c.action do |global_options,options,args|
589
+ key = args[0]
590
+ value = args[1]
591
+
592
+ task = ConfigTask.new
593
+ task.config(key, value)
594
+
595
+ if key == "hints" && !Machinery::Config.new.hints
596
+ Machinery::Ui.puts "Hints can be switched on again by '#{$0} config hints on'."
597
+ end
598
+ end
599
+ end
600
+ end