pdk-akerl 1.8.0.1

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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +826 -0
  3. data/LICENSE +201 -0
  4. data/README.md +133 -0
  5. data/exe/pdk +10 -0
  6. data/lib/pdk.rb +10 -0
  7. data/lib/pdk/answer_file.rb +121 -0
  8. data/lib/pdk/cli.rb +113 -0
  9. data/lib/pdk/cli/build.rb +76 -0
  10. data/lib/pdk/cli/bundle.rb +42 -0
  11. data/lib/pdk/cli/convert.rb +41 -0
  12. data/lib/pdk/cli/errors.rb +23 -0
  13. data/lib/pdk/cli/exec.rb +246 -0
  14. data/lib/pdk/cli/exec_group.rb +67 -0
  15. data/lib/pdk/cli/module.rb +14 -0
  16. data/lib/pdk/cli/module/build.rb +14 -0
  17. data/lib/pdk/cli/module/generate.rb +45 -0
  18. data/lib/pdk/cli/new.rb +17 -0
  19. data/lib/pdk/cli/new/class.rb +32 -0
  20. data/lib/pdk/cli/new/defined_type.rb +30 -0
  21. data/lib/pdk/cli/new/module.rb +41 -0
  22. data/lib/pdk/cli/new/provider.rb +27 -0
  23. data/lib/pdk/cli/new/task.rb +31 -0
  24. data/lib/pdk/cli/test.rb +12 -0
  25. data/lib/pdk/cli/test/unit.rb +88 -0
  26. data/lib/pdk/cli/update.rb +32 -0
  27. data/lib/pdk/cli/util.rb +193 -0
  28. data/lib/pdk/cli/util/command_redirector.rb +26 -0
  29. data/lib/pdk/cli/util/interview.rb +63 -0
  30. data/lib/pdk/cli/util/option_normalizer.rb +53 -0
  31. data/lib/pdk/cli/util/option_validator.rb +56 -0
  32. data/lib/pdk/cli/validate.rb +124 -0
  33. data/lib/pdk/generate.rb +11 -0
  34. data/lib/pdk/generate/defined_type.rb +49 -0
  35. data/lib/pdk/generate/module.rb +318 -0
  36. data/lib/pdk/generate/provider.rb +82 -0
  37. data/lib/pdk/generate/puppet_class.rb +48 -0
  38. data/lib/pdk/generate/puppet_object.rb +288 -0
  39. data/lib/pdk/generate/task.rb +86 -0
  40. data/lib/pdk/i18n.rb +4 -0
  41. data/lib/pdk/logger.rb +28 -0
  42. data/lib/pdk/module.rb +21 -0
  43. data/lib/pdk/module/build.rb +214 -0
  44. data/lib/pdk/module/convert.rb +209 -0
  45. data/lib/pdk/module/metadata.rb +193 -0
  46. data/lib/pdk/module/templatedir.rb +313 -0
  47. data/lib/pdk/module/update.rb +111 -0
  48. data/lib/pdk/module/update_manager.rb +210 -0
  49. data/lib/pdk/report.rb +112 -0
  50. data/lib/pdk/report/event.rb +357 -0
  51. data/lib/pdk/template_file.rb +89 -0
  52. data/lib/pdk/tests/unit.rb +213 -0
  53. data/lib/pdk/util.rb +271 -0
  54. data/lib/pdk/util/bundler.rb +253 -0
  55. data/lib/pdk/util/filesystem.rb +12 -0
  56. data/lib/pdk/util/git.rb +74 -0
  57. data/lib/pdk/util/puppet_version.rb +242 -0
  58. data/lib/pdk/util/ruby_version.rb +147 -0
  59. data/lib/pdk/util/vendored_file.rb +88 -0
  60. data/lib/pdk/util/version.rb +42 -0
  61. data/lib/pdk/util/windows.rb +13 -0
  62. data/lib/pdk/util/windows/api_types.rb +57 -0
  63. data/lib/pdk/util/windows/file.rb +36 -0
  64. data/lib/pdk/util/windows/string.rb +16 -0
  65. data/lib/pdk/validate.rb +14 -0
  66. data/lib/pdk/validate/base_validator.rb +209 -0
  67. data/lib/pdk/validate/metadata/metadata_json_lint.rb +86 -0
  68. data/lib/pdk/validate/metadata/metadata_syntax.rb +109 -0
  69. data/lib/pdk/validate/metadata_validator.rb +30 -0
  70. data/lib/pdk/validate/puppet/puppet_lint.rb +67 -0
  71. data/lib/pdk/validate/puppet/puppet_syntax.rb +112 -0
  72. data/lib/pdk/validate/puppet_validator.rb +30 -0
  73. data/lib/pdk/validate/ruby/rubocop.rb +77 -0
  74. data/lib/pdk/validate/ruby_validator.rb +29 -0
  75. data/lib/pdk/validate/tasks/metadata_lint.rb +126 -0
  76. data/lib/pdk/validate/tasks/name.rb +88 -0
  77. data/lib/pdk/validate/tasks_validator.rb +33 -0
  78. data/lib/pdk/version.rb +4 -0
  79. data/locales/config.yaml +21 -0
  80. data/locales/pdk.pot +1283 -0
  81. metadata +304 -0
data/lib/pdk/cli.rb ADDED
@@ -0,0 +1,113 @@
1
+ require 'cri'
2
+
3
+ require 'pdk/cli/errors'
4
+ require 'pdk/cli/util'
5
+ require 'pdk/cli/util/command_redirector'
6
+ require 'pdk/cli/util/option_normalizer'
7
+ require 'pdk/cli/util/option_validator'
8
+ require 'pdk/cli/exec_group'
9
+ require 'pdk/generate/module'
10
+ require 'pdk/i18n'
11
+ require 'pdk/logger'
12
+ require 'pdk/report'
13
+ require 'pdk/util/version'
14
+ require 'pdk/util/puppet_version'
15
+
16
+ module PDK::CLI
17
+ def self.run(args)
18
+ @base_cmd.run(args)
19
+ rescue PDK::CLI::ExitWithError => e
20
+ PDK.logger.send(e.log_level, e.message)
21
+
22
+ exit e.exit_code
23
+ rescue PDK::CLI::FatalError => e
24
+ PDK.logger.fatal(e.message) if e.message
25
+
26
+ # If FatalError was raised as the result of another exception, send the
27
+ # details of that exception to the debug log. If there was no cause
28
+ # (FatalError raised on its own outside a rescue block), send the details
29
+ # of the FatalError exception to the debug log.
30
+ cause = e.cause
31
+ if cause.nil?
32
+ e.backtrace.each { |line| PDK.logger.debug(line) }
33
+ else
34
+ PDK.logger.debug("#{cause.class}: #{cause.message}")
35
+ cause.backtrace.each { |line| PDK.logger.debug(line) }
36
+ end
37
+
38
+ exit e.exit_code
39
+ end
40
+
41
+ def self.template_url_option(dsl)
42
+ desc = _('Specifies the URL to the template to use when creating new modules or classes. (default: %{default_url})') % { default_url: PDK::Util.default_template_url }
43
+
44
+ dsl.option nil, 'template-url', desc, argument: :required
45
+ end
46
+
47
+ def self.skip_interview_option(dsl)
48
+ dsl.option nil, 'skip-interview', _('When specified, skips interactive querying of metadata.')
49
+ end
50
+
51
+ def self.full_interview_option(dsl)
52
+ dsl.option nil, 'full-interview', _('When specified, interactive querying of metadata will include all optional questions.')
53
+ end
54
+
55
+ def self.puppet_version_options(dsl)
56
+ dsl.option nil, 'puppet-version', _('Puppet version to run tests or validations against.'), argument: :required
57
+ dsl.option nil, 'pe-version', _('Puppet Enterprise version to run tests or validations against.'), argument: :required
58
+ end
59
+
60
+ def self.puppet_dev_option(dsl)
61
+ dsl.option nil,
62
+ 'puppet-dev',
63
+ _('When specified, PDK will validate or test against the current Puppet source from github.com. To use this option, you must have network access to https://github.com.')
64
+ end
65
+
66
+ @base_cmd = Cri::Command.define do
67
+ name 'pdk'
68
+ usage _('pdk command [options]')
69
+ summary _('Puppet Development Kit')
70
+ description _('The shortest path to better modules.')
71
+ default_subcommand 'help'
72
+
73
+ flag nil, :version, _('Show version of pdk.') do |_, _|
74
+ puts PDK::Util::Version.version_string
75
+ exit 0
76
+ end
77
+
78
+ flag :h, :help, _('Show help for this command.') do |_, c|
79
+ puts c.help
80
+ exit 0
81
+ end
82
+
83
+ format_desc = _(
84
+ "Specify desired output format. Valid formats are '%{available_formats}'. " \
85
+ 'You may also specify a file to which the formatted output is sent, ' \
86
+ "for example: '--format=junit:report.xml'. This option may be specified " \
87
+ 'multiple times if each option specifies a distinct target file.',
88
+ ) % { available_formats: PDK::Report.formats.join("', '") }
89
+
90
+ option :f, :format, format_desc, argument: :required, multiple: true do |values|
91
+ PDK::CLI::Util::OptionNormalizer.report_formats(values.compact)
92
+ end
93
+
94
+ flag :d, :debug, _('Enable debug output.') do |_, _|
95
+ PDK.logger.enable_debug_output
96
+ end
97
+
98
+ option nil, 'answer-file', _('Path to an answer file.'), argument: :required, hidden: true do |value|
99
+ PDK.answer_file = value
100
+ end
101
+ end
102
+
103
+ require 'pdk/cli/bundle'
104
+ require 'pdk/cli/build'
105
+ require 'pdk/cli/convert'
106
+ require 'pdk/cli/new'
107
+ require 'pdk/cli/test'
108
+ require 'pdk/cli/update'
109
+ require 'pdk/cli/validate'
110
+ require 'pdk/cli/module'
111
+
112
+ @base_cmd.add_command Cri::Command.new_basic_help
113
+ end
@@ -0,0 +1,76 @@
1
+ require 'pdk/cli/util'
2
+
3
+ module PDK::CLI
4
+ @build_cmd = @base_cmd.define_command do
5
+ name 'build'
6
+ usage _('build [options]')
7
+ summary _('Builds a package from the module that can be published to the Puppet Forge.')
8
+
9
+ option nil, 'target-dir',
10
+ _('The target directory where you want PDK to write the package.'),
11
+ argument: :required, default: File.join(Dir.pwd, 'pkg')
12
+
13
+ option nil, 'force', _('Skips the prompts and builds the module package.')
14
+
15
+ run do |opts, _args, _cmd|
16
+ require 'pdk/module/build'
17
+
18
+ # Make sure build is being run in a valid module directory with a metadata.json
19
+ PDK::CLI::Util.ensure_in_module!(
20
+ message: _('`pdk build` can only be run from inside a valid module with a metadata.json.'),
21
+ log_level: :info,
22
+ )
23
+
24
+ module_metadata = PDK::Module::Metadata.from_file('metadata.json')
25
+
26
+ # TODO: Ensure forge metadata has been set, or call out to interview
27
+ # to set it.
28
+ #
29
+ unless module_metadata.forge_ready?
30
+ if opts[:force]
31
+ PDK.logger.error _('This module is missing required fields in the metadata.json. Re-run the build command without --force to add this information.')
32
+ exit 1
33
+ else
34
+ module_metadata.interview_for_forge!
35
+ module_metadata.write!('metadata.json')
36
+ end
37
+ end
38
+
39
+ builder = PDK::Module::Build.new(opts)
40
+
41
+ unless opts[:force]
42
+ if builder.package_already_exists?
43
+ PDK.logger.info _("The file '%{package}' already exists.") % { package: builder.package_file }
44
+
45
+ unless PDK::CLI::Util.prompt_for_yes(_('Overwrite?'), default: false)
46
+ PDK.logger.info _('Build cancelled; exiting.')
47
+ exit 0
48
+ end
49
+ end
50
+
51
+ unless builder.module_pdk_compatible?
52
+ PDK.logger.info _('This module is not compatible with PDK, so PDK can not validate or test this build. ' \
53
+ 'Unvalidated modules may have errors when uploading to the Forge. ' \
54
+ 'To make this module PDK compatible and use validate features, cancel the build and run `pdk convert`.')
55
+
56
+ unless PDK::CLI::Util.prompt_for_yes(_('Continue build without converting?'))
57
+ PDK.logger.info _('Build cancelled; exiting.')
58
+ exit 0
59
+ end
60
+ end
61
+ end
62
+
63
+ PDK.logger.info _('Building %{module_name} version %{module_version}') % {
64
+ module_name: module_metadata.data['name'],
65
+ module_version: module_metadata.data['version'],
66
+ }
67
+
68
+ builder.build
69
+
70
+ PDK.logger.info _('Build of %{package_name} has completed successfully. Built package can be found here: %{package_path}') % {
71
+ package_name: module_metadata.data['name'],
72
+ package_path: builder.package_file,
73
+ }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module PDK::CLI
3
+ @bundle_cmd = @base_cmd.define_command do
4
+ name 'bundle'
5
+ usage _('bundle [bundler_options]')
6
+ summary _('(Experimental) Command pass-through to bundler')
7
+ description _(<<-EOF
8
+ [experimental] For advanced users, pdk bundle runs arbitrary commands in the bundler environment that pdk manages.
9
+ Careless use of this command can lead to errors that pdk can't help recover from.
10
+
11
+ Note that for PowerShell the '--' needs to be escaped using a backtick: '`--' to avoid it being parsed by the shell.
12
+ EOF
13
+ )
14
+ skip_option_parsing
15
+
16
+ run do |_opts, args, _cmd|
17
+ PDK::CLI::Util.ensure_in_module!(
18
+ message: _('`pdk bundle` can only be run from inside a valid module directory.'),
19
+ )
20
+
21
+ PDK::CLI::Util.validate_puppet_version_opts({})
22
+
23
+ # Ensure that the correct Ruby is activated before running commend.
24
+ puppet_env = PDK::CLI::Util.puppet_from_opts_or_env({})
25
+ PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
26
+
27
+ gemfile_env = PDK::Util::Bundler::BundleHelper.gemfile_env(puppet_env[:gemset])
28
+
29
+ command = PDK::CLI::Exec::Command.new(PDK::CLI::Exec.bundle_bin, *args).tap do |c|
30
+ c.context = :module
31
+ c.update_environment(gemfile_env)
32
+ end
33
+
34
+ result = command.execute!
35
+
36
+ $stderr.puts result[:stdout]
37
+ $stderr.puts result[:stderr]
38
+
39
+ exit result[:exit_code]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ require 'pdk/cli/util'
2
+
3
+ module PDK::CLI
4
+ @convert_cmd = @base_cmd.define_command do
5
+ name 'convert'
6
+ usage _('convert [options]')
7
+ summary _('Convert an existing module to be compatible with the PDK.')
8
+
9
+ PDK::CLI.template_url_option(self)
10
+ PDK::CLI.skip_interview_option(self)
11
+ PDK::CLI.full_interview_option(self)
12
+ flag nil, :noop, _('Do not convert the module, just output what would be done.')
13
+ flag nil, :force, _('Convert the module automatically, with no prompts.')
14
+
15
+ run do |opts, _args, _cmd|
16
+ require 'pdk/module/convert'
17
+
18
+ PDK::CLI::Util.ensure_in_module!(
19
+ check_module_layout: true,
20
+ message: _('`pdk convert` can only be run from inside a valid module directory.'),
21
+ log_level: :info,
22
+ )
23
+
24
+ if opts[:noop] && opts[:force]
25
+ raise PDK::CLI::ExitWithError, _('You can not specify --noop and --force when converting a module')
26
+ end
27
+
28
+ if opts[:'skip-interview'] && opts[:'full-interview']
29
+ PDK.logger.info _('Ignoring --full-interview and continuing with --skip-interview.')
30
+ opts[:'full-interview'] = false
31
+ end
32
+
33
+ if opts[:force] && opts[:'full-interview']
34
+ PDK.logger.info _('Ignoring --full-interview and continuing with --force.')
35
+ opts[:'full-interview'] = false
36
+ end
37
+
38
+ PDK::Module::Convert.invoke(opts)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ module PDK
2
+ module CLI
3
+ class FatalError < StandardError
4
+ attr_reader :exit_code
5
+
6
+ def initialize(msg = _('An unexpected error has occurred. Try running the command again with --debug'), opts = {})
7
+ @exit_code = opts.fetch(:exit_code, 1)
8
+ super(msg)
9
+ end
10
+ end
11
+
12
+ class ExitWithError < StandardError
13
+ attr_reader :exit_code
14
+ attr_reader :log_level
15
+
16
+ def initialize(msg, opts = {})
17
+ @exit_code = opts.fetch(:exit_code, 1)
18
+ @log_level = opts.fetch(:log_level, :error)
19
+ super(msg)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,246 @@
1
+ require 'bundler'
2
+ require 'childprocess'
3
+ require 'tempfile'
4
+ require 'tty-spinner'
5
+ require 'tty-which'
6
+
7
+ require 'pdk/util'
8
+ require 'pdk/util/git'
9
+ require 'pdk/util/ruby_version'
10
+
11
+ module PDK
12
+ module CLI
13
+ module Exec
14
+ def self.execute(*cmd)
15
+ Command.new(*cmd).execute!
16
+ end
17
+
18
+ def self.execute_with_env(env, *cmd)
19
+ Command.new(*cmd).tap { |c| c.environment = env }.execute!
20
+ end
21
+
22
+ def self.ensure_bin_present!(bin_path, bin_name)
23
+ message = _('Unable to find `%{name}`. Check that it is installed and try again.') % {
24
+ name: bin_name,
25
+ }
26
+
27
+ raise PDK::CLI::FatalError, message unless TTY::Which.exist?(bin_path)
28
+ end
29
+
30
+ def self.bundle(*args)
31
+ ensure_bin_present!(bundle_bin, 'bundler')
32
+
33
+ execute(bundle_bin, *args)
34
+ end
35
+
36
+ def self.bundle_bin
37
+ bundle_bin = Gem.win_platform? ? 'bundle.bat' : 'bundle'
38
+ vendored_bin_path = File.join('private', 'ruby', PDK::Util::RubyVersion.active_ruby_version, 'bin', bundle_bin)
39
+
40
+ try_vendored_bin(vendored_bin_path, bundle_bin)
41
+ end
42
+
43
+ def self.try_vendored_bin(vendored_bin_path, fallback)
44
+ unless PDK::Util.package_install?
45
+ PDK.logger.debug(_("PDK package installation not found. Trying '%{fallback}' from the system PATH instead.") % {
46
+ fallback: fallback,
47
+ })
48
+ return fallback
49
+ end
50
+
51
+ vendored_bin_full_path = File.join(PDK::Util.pdk_package_basedir, vendored_bin_path)
52
+
53
+ unless File.exist?(vendored_bin_full_path)
54
+ PDK.logger.debug(_("Could not find '%{vendored_bin}' in PDK package. Trying '%{fallback}' from the system PATH instead.") % {
55
+ fallback: fallback,
56
+ vendored_bin: vendored_bin_full_path,
57
+ })
58
+ return fallback
59
+ end
60
+
61
+ PDK.logger.debug(_("Using '%{vendored_bin}' from PDK package.") % { vendored_bin: vendored_bin_full_path })
62
+ vendored_bin_full_path
63
+ end
64
+
65
+ # TODO: decide how/when to connect stdin to child process for things like pry
66
+ # TODO: need a way to set callbacks on new stdout/stderr data
67
+ class Command
68
+ attr_reader :argv
69
+ attr_reader :context
70
+ attr_accessor :timeout
71
+ attr_accessor :environment
72
+ attr_writer :exec_group
73
+
74
+ def initialize(*argv)
75
+ @argv = argv
76
+
77
+ @process = ChildProcess.build(*@argv)
78
+ @process.leader = true
79
+
80
+ @stdout = Tempfile.new('stdout').tap { |io| io.sync = true }
81
+ @stderr = Tempfile.new('stderr').tap { |io| io.sync = true }
82
+
83
+ @process.io.stdout = @stdout
84
+ @process.io.stderr = @stderr
85
+
86
+ # Default to running things in the system context.
87
+ @context = :system
88
+
89
+ # Extra environment vars to add to base set.
90
+ @environment = {}
91
+
92
+ # Register the ExecGroup when running in parallel
93
+ @exec_group = nil
94
+ end
95
+
96
+ def context=(new_context)
97
+ unless [:system, :module].include?(new_context)
98
+ raise ArgumentError, _("Expected execution context to be :system or :module but got '%{context}'.") % { context: new_context }
99
+ end
100
+
101
+ @context = new_context
102
+ end
103
+
104
+ def register_spinner(spinner, opts = {})
105
+ return unless PDK::CLI::Util.interactive?
106
+ @success_message = opts.delete(:success)
107
+ @failure_message = opts.delete(:failure)
108
+
109
+ @spinner = spinner
110
+ end
111
+
112
+ def add_spinner(message, opts = {})
113
+ return unless PDK::CLI::Util.interactive?
114
+ @success_message = opts.delete(:success)
115
+ @failure_message = opts.delete(:failure)
116
+
117
+ @spinner = TTY::Spinner.new("[:spinner] #{message}", opts.merge(PDK::CLI::Util.spinner_opts_for_platform))
118
+ end
119
+
120
+ def update_environment(additional_env)
121
+ @environment.merge!(additional_env)
122
+ end
123
+
124
+ def execute!
125
+ # Start spinning if configured.
126
+ @spinner.auto_spin if @spinner
127
+
128
+ # Add custom env vars.
129
+ @environment.each do |k, v|
130
+ @process.environment[k] = v
131
+ end
132
+
133
+ @process.environment['BUNDLE_IGNORE_CONFIG'] = '1'
134
+
135
+ if context == :module
136
+ @process.environment['GEM_HOME'] = PDK::Util::RubyVersion.gem_home
137
+ @process.environment['GEM_PATH'] = PDK::Util::RubyVersion.gem_path
138
+
139
+ # Make sure invocation of Ruby prefers our private installation.
140
+ package_binpath = PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil
141
+ @process.environment['PATH'] = [
142
+ PDK::Util::RubyVersion.bin_path,
143
+ File.join(@process.environment['GEM_HOME'], 'bin'),
144
+ PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path| File.join(gem_path, 'bin') },
145
+ package_binpath,
146
+ ENV['PATH'],
147
+ PDK::Util.package_install? ? PDK::Util::Git.git_paths : nil,
148
+ ].compact.flatten.join(File::PATH_SEPARATOR)
149
+
150
+ mod_root = PDK::Util.module_root
151
+
152
+ unless mod_root
153
+ @spinner.error if @spinner
154
+
155
+ raise PDK::CLI::FatalError, _('Current working directory is not part of a module. (No metadata.json was found.)')
156
+ end
157
+
158
+ if Dir.pwd == mod_root
159
+ run_process_in_clean_env!
160
+ else
161
+ Dir.chdir(mod_root) do
162
+ run_process_in_clean_env!
163
+ end
164
+ end
165
+ else
166
+ run_process!
167
+ end
168
+
169
+ # Stop spinning when done (if configured).
170
+ stop_spinner
171
+
172
+ @stdout.rewind
173
+ @stderr.rewind
174
+
175
+ process_data = {
176
+ stdout: @stdout.read,
177
+ stderr: @stderr.read,
178
+ exit_code: @process.exit_code,
179
+ duration: @duration,
180
+ }
181
+
182
+ return process_data
183
+ ensure
184
+ @stdout.close
185
+ @stderr.close
186
+ end
187
+
188
+ protected
189
+
190
+ def stop_spinner
191
+ return unless @spinner
192
+
193
+ # If it is a single spinner, we need to send it a success/error message
194
+ if @process.exit_code.zero?
195
+ @spinner.success(@success_message || '')
196
+ else
197
+ @spinner.error(@failure_message || '')
198
+ end
199
+ end
200
+
201
+ def run_process_in_clean_env!
202
+ ::Bundler.with_clean_env do
203
+ run_process!
204
+ end
205
+ end
206
+
207
+ def run_process!
208
+ command_string = argv.join(' ')
209
+
210
+ PDK.logger.debug(_("Executing '%{command}'") % { command: command_string })
211
+
212
+ if context == :module
213
+ PDK.logger.debug(_('Command environment:'))
214
+ @process.environment.each do |var, val|
215
+ PDK.logger.debug(" #{var}: #{val}")
216
+ end
217
+ end
218
+
219
+ start_time = Time.now
220
+
221
+ begin
222
+ @process.start
223
+ rescue ChildProcess::LaunchError => e
224
+ raise PDK::CLI::FatalError, _("Failed to execute '%{command}': %{message}") % { command: command_string, message: e.message }
225
+ end
226
+
227
+ if timeout
228
+ begin
229
+ @process.poll_for_exit(timeout)
230
+ rescue ChildProcess::TimeoutError
231
+ @process.stop # tries increasingly harsher methods to kill the process.
232
+ end
233
+ else
234
+ # Wait indfinitely if no timeout set.
235
+ @process.wait
236
+ end
237
+
238
+ @duration = Time.now - start_time
239
+
240
+ PDK.logger.debug(_("Execution of '%{command}' complete (duration: %{duration_in_seconds}s; exit code: %{exit_code})") %
241
+ { command: command_string, duration_in_seconds: @duration, exit_code: @process.exit_code })
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end