test-kitchen-rsync 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/LICENSE +15 -0
  4. data/Rakefile +53 -0
  5. data/bin/zl-kitchen +11 -0
  6. data/lib/kitchen/base64_stream.rb +48 -0
  7. data/lib/kitchen/chef_utils_wiring.rb +40 -0
  8. data/lib/kitchen/cli.rb +413 -0
  9. data/lib/kitchen/collection.rb +52 -0
  10. data/lib/kitchen/color.rb +63 -0
  11. data/lib/kitchen/command/action.rb +41 -0
  12. data/lib/kitchen/command/console.rb +54 -0
  13. data/lib/kitchen/command/diagnose.rb +84 -0
  14. data/lib/kitchen/command/doctor.rb +39 -0
  15. data/lib/kitchen/command/exec.rb +37 -0
  16. data/lib/kitchen/command/list.rb +148 -0
  17. data/lib/kitchen/command/login.rb +39 -0
  18. data/lib/kitchen/command/package.rb +32 -0
  19. data/lib/kitchen/command/sink.rb +50 -0
  20. data/lib/kitchen/command/test.rb +47 -0
  21. data/lib/kitchen/command.rb +207 -0
  22. data/lib/kitchen/config.rb +344 -0
  23. data/lib/kitchen/configurable.rb +616 -0
  24. data/lib/kitchen/data_munger.rb +1024 -0
  25. data/lib/kitchen/diagnostic.rb +138 -0
  26. data/lib/kitchen/driver/base.rb +133 -0
  27. data/lib/kitchen/driver/dummy.rb +105 -0
  28. data/lib/kitchen/driver/exec.rb +70 -0
  29. data/lib/kitchen/driver/proxy.rb +70 -0
  30. data/lib/kitchen/driver/ssh_base.rb +351 -0
  31. data/lib/kitchen/driver.rb +40 -0
  32. data/lib/kitchen/errors.rb +243 -0
  33. data/lib/kitchen/generator/init.rb +254 -0
  34. data/lib/kitchen/instance.rb +726 -0
  35. data/lib/kitchen/lazy_hash.rb +148 -0
  36. data/lib/kitchen/lifecycle_hook/base.rb +78 -0
  37. data/lib/kitchen/lifecycle_hook/local.rb +53 -0
  38. data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
  39. data/lib/kitchen/lifecycle_hooks.rb +92 -0
  40. data/lib/kitchen/loader/yaml.rb +377 -0
  41. data/lib/kitchen/logger.rb +422 -0
  42. data/lib/kitchen/logging.rb +52 -0
  43. data/lib/kitchen/login_command.rb +49 -0
  44. data/lib/kitchen/metadata_chopper.rb +49 -0
  45. data/lib/kitchen/platform.rb +64 -0
  46. data/lib/kitchen/plugin.rb +76 -0
  47. data/lib/kitchen/plugin_base.rb +60 -0
  48. data/lib/kitchen/provisioner/base.rb +269 -0
  49. data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
  50. data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
  51. data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
  52. data/lib/kitchen/provisioner/chef_apply.rb +121 -0
  53. data/lib/kitchen/provisioner/chef_base.rb +705 -0
  54. data/lib/kitchen/provisioner/chef_infra.rb +167 -0
  55. data/lib/kitchen/provisioner/chef_solo.rb +82 -0
  56. data/lib/kitchen/provisioner/chef_zero.rb +12 -0
  57. data/lib/kitchen/provisioner/dummy.rb +75 -0
  58. data/lib/kitchen/provisioner/shell.rb +157 -0
  59. data/lib/kitchen/provisioner.rb +42 -0
  60. data/lib/kitchen/rake_tasks.rb +80 -0
  61. data/lib/kitchen/shell_out.rb +90 -0
  62. data/lib/kitchen/ssh.rb +289 -0
  63. data/lib/kitchen/state_file.rb +112 -0
  64. data/lib/kitchen/suite.rb +48 -0
  65. data/lib/kitchen/thor_tasks.rb +63 -0
  66. data/lib/kitchen/transport/base.rb +236 -0
  67. data/lib/kitchen/transport/dummy.rb +78 -0
  68. data/lib/kitchen/transport/exec.rb +145 -0
  69. data/lib/kitchen/transport/ssh.rb +579 -0
  70. data/lib/kitchen/transport/winrm.rb +546 -0
  71. data/lib/kitchen/transport.rb +40 -0
  72. data/lib/kitchen/util.rb +229 -0
  73. data/lib/kitchen/verifier/base.rb +243 -0
  74. data/lib/kitchen/verifier/busser.rb +275 -0
  75. data/lib/kitchen/verifier/dummy.rb +75 -0
  76. data/lib/kitchen/verifier/shell.rb +99 -0
  77. data/lib/kitchen/verifier.rb +39 -0
  78. data/lib/kitchen/version.rb +20 -0
  79. data/lib/kitchen/which.rb +26 -0
  80. data/lib/kitchen.rb +152 -0
  81. data/lib/vendor/hash_recursive_merge.rb +79 -0
  82. data/support/busser_install_command.ps1 +14 -0
  83. data/support/busser_install_command.sh +21 -0
  84. data/support/chef-client-fail-if-update-handler.rb +15 -0
  85. data/support/chef_base_init_command.ps1 +18 -0
  86. data/support/chef_base_init_command.sh +1 -0
  87. data/support/chef_base_install_command.ps1 +85 -0
  88. data/support/chef_base_install_command.sh +229 -0
  89. data/support/download_helpers.sh +109 -0
  90. data/support/dummy-validation.pem +27 -0
  91. data/templates/driver/CHANGELOG.md.erb +3 -0
  92. data/templates/driver/Gemfile.erb +3 -0
  93. data/templates/driver/README.md.erb +64 -0
  94. data/templates/driver/Rakefile.erb +21 -0
  95. data/templates/driver/driver.rb.erb +23 -0
  96. data/templates/driver/gemspec.erb +29 -0
  97. data/templates/driver/gitignore.erb +17 -0
  98. data/templates/driver/license_apachev2.erb +15 -0
  99. data/templates/driver/license_lgplv3.erb +16 -0
  100. data/templates/driver/license_mit.erb +22 -0
  101. data/templates/driver/license_reserved.erb +5 -0
  102. data/templates/driver/tailor.erb +4 -0
  103. data/templates/driver/travis.yml.erb +11 -0
  104. data/templates/driver/version.rb.erb +12 -0
  105. data/templates/init/chefignore.erb +2 -0
  106. data/templates/init/kitchen.yml.erb +18 -0
  107. data/test-kitchen.gemspec +52 -0
  108. metadata +528 -0
@@ -0,0 +1,229 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2012, 2013, 2014, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require_relative "errors"
19
+ require "thor/util"
20
+
21
+ module Kitchen
22
+ # Stateless utility methods used in different contexts. Essentially a mini
23
+ # PassiveSupport library.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ module Util
27
+ # Returns the standard library Logger level constants for a given symbol
28
+ # representation.
29
+ #
30
+ # @param symbol [Symbol] symbol representation of a logger level (:debug,
31
+ # :info, :warn, :error, :fatal)
32
+ # @return [Integer] Logger::Severity constant value or nil if input is not
33
+ # valid
34
+ def self.to_logger_level(symbol)
35
+ return nil unless %i{debug info warn error fatal}.include?(symbol)
36
+
37
+ Logger.const_get(symbol.to_s.upcase)
38
+ end
39
+
40
+ # Returns the symbol represenation of a logging levels for a given
41
+ # standard library Logger::Severity constant.
42
+ #
43
+ # @param const [Integer] Logger::Severity constant value for a logging
44
+ # level (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR,
45
+ # Logger::FATAL)
46
+ # @return [Symbol] symbol representation of the logging level
47
+ def self.from_logger_level(const)
48
+ case const
49
+ when Logger::DEBUG then :debug
50
+ when Logger::INFO then :info
51
+ when Logger::WARN then :warn
52
+ when Logger::ERROR then :error
53
+ else :fatal
54
+ end
55
+ end
56
+
57
+ # Returns a new Hash with all key values coerced to symbols. All keys
58
+ # within a Hash are coerced by calling #to_sym and hashes within arrays
59
+ # and other hashes are traversed.
60
+ #
61
+ # @param obj [Object] the hash to be processed. While intended for
62
+ # hashes, this method safely processes arbitrary objects
63
+ # @return [Object] a converted hash with all keys as symbols
64
+ def self.symbolized_hash(obj)
65
+ if obj.is_a?(Hash)
66
+ obj.inject({}) { |h, (k, v)| h[k.to_sym] = symbolized_hash(v); h }
67
+ elsif obj.is_a?(Array)
68
+ obj.inject([]) { |a, e| a << symbolized_hash(e); a }
69
+ else
70
+ obj
71
+ end
72
+ end
73
+
74
+ # Returns a new Hash with all key values coerced to strings. All keys
75
+ # within a Hash are coerced by calling #to_s and hashes with arrays
76
+ # and other hashes are traversed.
77
+ #
78
+ # @param obj [Object] the hash to be processed. While intended for
79
+ # hashes, this method safely processes arbitrary objects
80
+ # @return [Object] a converted hash with all keys as strings
81
+ def self.stringified_hash(obj)
82
+ if obj.is_a?(Hash)
83
+ obj.inject({}) { |h, (k, v)| h[k.to_s] = stringified_hash(v); h }
84
+ elsif obj.is_a?(Array)
85
+ obj.inject([]) { |a, e| a << stringified_hash(e); a }
86
+ else
87
+ obj
88
+ end
89
+ end
90
+
91
+ # Returns a formatted string representing a duration in seconds.
92
+ #
93
+ # @param total [Integer] the total number of seconds
94
+ # @return [String] a formatted string of the form (XmYY.00s)
95
+ def self.duration(total)
96
+ total = 0 if total.nil?
97
+ minutes = (total / 60).to_i
98
+ seconds = (total - (minutes * 60))
99
+ format("(%dm%.2fs)", minutes, seconds)
100
+ end
101
+
102
+ # Generates a command (or series of commands) wrapped so that it can be
103
+ # invoked on a remote instance or locally.
104
+ #
105
+ # This method uses the Bourne shell (/bin/sh) to maximize the chance of
106
+ # cross platform portability on Unixlike systems.
107
+ #
108
+ # @param [String] the command
109
+ # @return [String] a wrapped command string
110
+ def self.wrap_command(cmd)
111
+ cmd = "false" if cmd.nil?
112
+ cmd = "true" if cmd.to_s.empty?
113
+ cmd = cmd.sub(/\n\Z/, "") if /\n\Z/.match?(cmd)
114
+
115
+ "sh -c '\n#{cmd}\n'"
116
+ end
117
+
118
+ # Modifes the given string to strip leading whitespace on each line, the
119
+ # amount which is calculated by using the first line of text.
120
+ #
121
+ # @example
122
+ #
123
+ # string = <<-STRING
124
+ # a
125
+ # b
126
+ # c
127
+ # STRING
128
+ # Util.outdent!(string) # => "a\n b\nc\n"
129
+ #
130
+ # @param string [String] the string that will be modified
131
+ # @return [String] the modified string
132
+ def self.outdent!(string)
133
+ string.gsub!(/^ {#{string.index(/[^ ]/)}}/, "")
134
+ end
135
+
136
+ # Returns a set of Bourne Shell (AKA /bin/sh) compatible helper
137
+ # functions. This function is usually called inline in a string that
138
+ # will be executed remotely on a test instance.
139
+ #
140
+ # @return [String] a string representation of useful helper functions
141
+ def self.shell_helpers
142
+ IO.read(File.join(
143
+ File.dirname(__FILE__), %w{.. .. support download_helpers.sh}
144
+ ))
145
+ end
146
+
147
+ # Lists the contents of the given directory. path will be prepended
148
+ # to the list returned. '.' and '..' are never returned.
149
+ #
150
+ # @param path [String] the directory to list
151
+ # @param include_dot [Boolean] if true, dot files will be included
152
+ # @param recurse [Boolean] if true, listing will be recursive
153
+ # @return A listing of the specified path
154
+ #
155
+ # @note You should prefer this method to using Dir.glob directly. The reason is
156
+ # because Dir.glob behaves strangely on Windows. It wont accept '\'
157
+ # and doesn't like fake directories (C:\Documents and Settings)
158
+ # It also does not do any sort of error checking, so things one would
159
+ # expect to fail just return an empty list
160
+ #
161
+ # @note Dir.chdir is applied to the process, thus it is not thread-safe
162
+ # and must be synchronized.
163
+ def self.list_directory(path, include_dot: false, recurse: false)
164
+ # Things (such as tests) are relying on this to not blow up if
165
+ # the directory does not exist
166
+ return [] unless Dir.exist?(path)
167
+
168
+ Kitchen.mutex_chdir.synchronize do
169
+ Dir.chdir(path) do
170
+ glob_pattern = if recurse
171
+ "**/*"
172
+ else
173
+ "*"
174
+ end
175
+ flags = if include_dot
176
+ [File::FNM_DOTMATCH]
177
+ else
178
+ []
179
+ end
180
+ Dir.glob(glob_pattern, *flags)
181
+ .reject { |f| [".", ".."].include?(f) }
182
+ .map { |f| File.join(path, f) }
183
+ end
184
+ end
185
+ end
186
+
187
+ # Similar to Dir.glob.
188
+ #
189
+ # The difference is this function forces you to specify where to glob from.
190
+ # You should glob from the path closest to what you want. The reason for this
191
+ # is because if you have symlinks on windows of any kind, Dir.glob will not
192
+ # traverse them.
193
+ #
194
+ # @param path [String] the directory to glob from
195
+ # @param pattern [String] The pattern to match
196
+ # @param flags [Integer] You can specify flags you would have passed to Dir.glob
197
+ # @return Files matching the specified pattern in the given path
198
+ #
199
+ # @note Dir.chdir is applied to the process, thus it is not thread-safe
200
+ # and must be synchronized.
201
+ def self.safe_glob(path, pattern, *flags)
202
+ return [] unless Dir.exist?(path)
203
+
204
+ Kitchen.mutex_chdir.synchronize do
205
+ Dir.chdir(path) do
206
+ Dir.glob(pattern, *flags).map { |f| File.join(path, f) }
207
+ end
208
+ end
209
+ end
210
+
211
+ def self.camel_case(a_string)
212
+ Thor::Util.camel_case(a_string)
213
+ end
214
+
215
+ def self.snake_case(a_string)
216
+ Thor::Util.snake_case(a_string)
217
+ end
218
+
219
+ # Check if a cmd exists on the PATH
220
+ def self.command_exists?(cmd)
221
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
222
+ paths.each do |path|
223
+ filename = File.join(path, cmd)
224
+ return filename if File.executable?(filename)
225
+ end
226
+ false
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,243 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2015, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require_relative "../errors"
19
+ require_relative "../configurable"
20
+ require_relative "../logging"
21
+ require_relative "../plugin_base"
22
+
23
+ module Kitchen
24
+ module Verifier
25
+ # Base class for a verifier.
26
+ #
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ class Base < Kitchen::Plugin::Base
29
+ include Configurable
30
+ include Logging
31
+
32
+ default_config :http_proxy, nil
33
+ default_config :https_proxy, nil
34
+ default_config :ftp_proxy, nil
35
+
36
+ default_config :root_path do |verifier|
37
+ verifier.windows_os? ? '$env:TEMP\\verifier' : "/tmp/verifier"
38
+ end
39
+
40
+ default_config :sudo do |verifier|
41
+ verifier.windows_os? ? nil : true
42
+ end
43
+
44
+ default_config :chef_omnibus_root, "/opt/chef"
45
+
46
+ default_config :sudo_command do |verifier|
47
+ verifier.windows_os? ? nil : "sudo -E"
48
+ end
49
+
50
+ default_config :command_prefix, nil
51
+
52
+ default_config(:suite_name) { |busser| busser.instance.suite.name }
53
+
54
+ # Creates a new Verifier object using the provided configuration data
55
+ # which will be merged with any default configuration.
56
+ #
57
+ # @param config [Hash] provided verifier configuration
58
+ def initialize(config = {})
59
+ init_config(config)
60
+ end
61
+
62
+ # Runs the verifier on the instance.
63
+ #
64
+ # @param state [Hash] mutable instance state
65
+ # @raise [ActionFailed] if the action could not be completed
66
+ def call(state)
67
+ create_sandbox
68
+ sandbox_dirs = Util.list_directory(sandbox_path)
69
+
70
+ instance.transport.connection(state) do |conn|
71
+ conn.execute(install_command)
72
+ conn.execute(init_command)
73
+ info("Transferring files to #{instance.to_str}")
74
+ conn.upload(sandbox_dirs, config[:root_path])
75
+ debug("Transfer complete")
76
+ conn.execute(prepare_command)
77
+ conn.execute(run_command)
78
+
79
+ info("Downloading files from #{instance.to_str}")
80
+ config[:downloads].to_h.each do |remotes, local|
81
+ debug("Downloading #{Array(remotes).join(", ")} to #{local}")
82
+ conn.download(remotes, local)
83
+ end
84
+ debug("Download complete")
85
+ end
86
+ rescue Kitchen::Transport::TransportFailed => ex
87
+ raise ActionFailed, ex.message
88
+ ensure
89
+ cleanup_sandbox
90
+ end
91
+
92
+ # Check system and configuration for common errors.
93
+ #
94
+ # @param state [Hash] mutable instance state
95
+ # @returns [Boolean] Return true if a problem is found.
96
+ def doctor(state)
97
+ false
98
+ end
99
+
100
+ # Deletes the sandbox path. Without calling this method, the sandbox path
101
+ # will persist after the process terminates. In other words, cleanup is
102
+ # explicit. This method is safe to call multiple times.
103
+ def cleanup_sandbox
104
+ return if sandbox_path.nil?
105
+
106
+ debug("Cleaning up local sandbox in #{sandbox_path}")
107
+ FileUtils.rmtree(sandbox_path)
108
+ end
109
+
110
+ # Creates a temporary directory on the local workstation into which
111
+ # verifier related files and directories can be copied or created. The
112
+ # contents of this directory will be copied over to the instance before
113
+ # invoking the verifier's run command. After this method completes, it
114
+ # is expected that the contents of the sandbox is complete and ready for
115
+ # copy to the remote instance.
116
+ #
117
+ # **Note:** any subclasses would be well advised to call super first when
118
+ # overriding this method, for example:
119
+ #
120
+ # @example overriding `#create_sandbox`
121
+ #
122
+ # class MyVerifier < Kitchen::Verifier::Base
123
+ # def create_sandbox
124
+ # super
125
+ # # any further file copies, preparations, etc.
126
+ # end
127
+ # end
128
+ def create_sandbox
129
+ @sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-")
130
+ File.chmod(0755, sandbox_path)
131
+ info("Preparing files for transfer")
132
+ debug("Creating local sandbox in #{sandbox_path}")
133
+ end
134
+
135
+ # Generates a command string which will install and configure the
136
+ # verifier software on an instance. If no work is required, then `nil`
137
+ # will be returned.
138
+ #
139
+ # @return [String] a command string
140
+ def install_command; end
141
+
142
+ # Generates a command string which will perform any data initialization
143
+ # or configuration required after the verifier software is installed
144
+ # but before the sandbox has been transferred to the instance. If no work
145
+ # is required, then `nil` will be returned.
146
+ #
147
+ # @return [String] a command string
148
+ def init_command; end
149
+
150
+ # Generates a command string which will perform any commands or
151
+ # configuration required just before the main verifier run command but
152
+ # after the sandbox has been transferred to the instance. If no work is
153
+ # required, then `nil` will be returned.
154
+ #
155
+ # @return [String] a command string
156
+ def prepare_command; end
157
+
158
+ # Generates a command string which will invoke the main verifier
159
+ # command on the prepared instance. If no work is required, then `nil`
160
+ # will be returned.
161
+ #
162
+ # @return [String] a command string
163
+ def run_command; end
164
+
165
+ # Returns the absolute path to the sandbox directory or raises an
166
+ # exception if `#create_sandbox` has not yet been called.
167
+ #
168
+ # @return [String] the absolute path to the sandbox directory
169
+ # @raise [ClientError] if the sandbox directory has no yet been created
170
+ # by calling `#create_sandbox`
171
+ def sandbox_path
172
+ @sandbox_path ||= raise ClientError, "Sandbox directory has not yet " \
173
+ "been created. Please run #{self.class}#create_sandox before " \
174
+ "trying to access the path."
175
+ end
176
+
177
+ # Sets the API version for this verifier. If the verifier does not set
178
+ # this value, then `nil` will be used and reported.
179
+ #
180
+ # Sets the API version for this verifier
181
+ #
182
+ # @example setting an API version
183
+ #
184
+ # module Kitchen
185
+ # module Verifier
186
+ # class NewVerifier < Kitchen::Verifier::Base
187
+ #
188
+ # kitchen_verifier_api_version 2
189
+ #
190
+ # end
191
+ # end
192
+ # end
193
+ #
194
+ # @param version [Integer,String] a version number
195
+ #
196
+ def self.kitchen_verifier_api_version(version)
197
+ @api_version = version
198
+ end
199
+
200
+ private
201
+
202
+ # Builds a complete command given a variables String preamble and a file
203
+ # containing shell code.
204
+ #
205
+ # @param vars [String] shell variables, as a String
206
+ # @param file [String] file basename (without extension) containing
207
+ # shell code
208
+ # @return [String] command
209
+ # @api private
210
+ def shell_code_from_file(vars, file)
211
+ src_file = File.join(
212
+ File.dirname(__FILE__),
213
+ %w{.. .. .. support},
214
+ file + (powershell_shell? ? ".ps1" : ".sh")
215
+ )
216
+
217
+ wrap_shell_code([vars, "", IO.read(src_file)].join("\n"))
218
+ end
219
+
220
+ # Conditionally prefixes a command with a sudo command.
221
+ #
222
+ # @param command [String] command to be prefixed
223
+ # @return [String] the command, conditionaly prefixed with sudo
224
+ # @api private
225
+ def sudo(script)
226
+ config[:sudo] ? "#{config[:sudo_command]} #{script}" : script
227
+ end
228
+
229
+ # Conditionally prefixes a command with a command prefix.
230
+ # This should generally be done after a command has been
231
+ # conditionally prefixed by #sudo as certain platforms, such as
232
+ # Cisco Nexus, require all commands to be run with a prefix to
233
+ # obtain outbound network access.
234
+ #
235
+ # @param command [String] command to be prefixed
236
+ # @return [String] the command, conditionally prefixed with the configured prefix
237
+ # @api private
238
+ def prefix_command(script)
239
+ config[:command_prefix] ? "#{config[:command_prefix]} #{script}" : script
240
+ end
241
+ end
242
+ end
243
+ end