test-kitchen 1.2.1 → 1.3.0
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.
- checksums.yaml +4 -4
- data/.cane +1 -1
- data/.rubocop.yml +3 -0
- data/.travis.yml +20 -9
- data/CHANGELOG.md +219 -108
- data/Gemfile +10 -6
- data/Guardfile +38 -9
- data/README.md +11 -1
- data/Rakefile +21 -37
- data/bin/kitchen +4 -4
- data/features/kitchen_action_commands.feature +161 -0
- data/features/kitchen_console_command.feature +34 -0
- data/features/kitchen_diagnose_command.feature +64 -0
- data/features/kitchen_init_command.feature +29 -17
- data/features/kitchen_list_command.feature +2 -2
- data/features/kitchen_login_command.feature +56 -0
- data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
- data/features/kitchen_test_command.feature +88 -0
- data/features/step_definitions/gem_steps.rb +8 -6
- data/features/step_definitions/git_steps.rb +4 -2
- data/features/step_definitions/output_steps.rb +5 -0
- data/features/support/env.rb +12 -9
- data/lib/kitchen.rb +60 -38
- data/lib/kitchen/base64_stream.rb +55 -0
- data/lib/kitchen/busser.rb +124 -58
- data/lib/kitchen/cli.rb +121 -38
- data/lib/kitchen/collection.rb +3 -3
- data/lib/kitchen/color.rb +4 -4
- data/lib/kitchen/command.rb +78 -11
- data/lib/kitchen/command/action.rb +3 -2
- data/lib/kitchen/command/console.rb +12 -5
- data/lib/kitchen/command/diagnose.rb +17 -3
- data/lib/kitchen/command/driver_discover.rb +26 -7
- data/lib/kitchen/command/exec.rb +41 -0
- data/lib/kitchen/command/list.rb +44 -14
- data/lib/kitchen/command/login.rb +2 -1
- data/lib/kitchen/command/sink.rb +2 -1
- data/lib/kitchen/command/test.rb +5 -4
- data/lib/kitchen/config.rb +146 -14
- data/lib/kitchen/configurable.rb +314 -0
- data/lib/kitchen/data_munger.rb +522 -18
- data/lib/kitchen/diagnostic.rb +43 -4
- data/lib/kitchen/driver.rb +4 -4
- data/lib/kitchen/driver/base.rb +80 -115
- data/lib/kitchen/driver/dummy.rb +34 -6
- data/lib/kitchen/driver/proxy.rb +14 -3
- data/lib/kitchen/driver/ssh_base.rb +61 -7
- data/lib/kitchen/errors.rb +109 -9
- data/lib/kitchen/generator/driver_create.rb +39 -5
- data/lib/kitchen/generator/init.rb +130 -45
- data/lib/kitchen/instance.rb +162 -28
- data/lib/kitchen/lazy_hash.rb +79 -7
- data/lib/kitchen/loader/yaml.rb +159 -27
- data/lib/kitchen/logger.rb +267 -21
- data/lib/kitchen/logging.rb +30 -3
- data/lib/kitchen/login_command.rb +11 -2
- data/lib/kitchen/metadata_chopper.rb +2 -2
- data/lib/kitchen/provisioner.rb +4 -4
- data/lib/kitchen/provisioner/base.rb +107 -103
- data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
- data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
- data/lib/kitchen/provisioner/chef_base.rb +206 -167
- data/lib/kitchen/provisioner/chef_solo.rb +25 -7
- data/lib/kitchen/provisioner/chef_zero.rb +105 -29
- data/lib/kitchen/provisioner/dummy.rb +1 -1
- data/lib/kitchen/provisioner/shell.rb +21 -6
- data/lib/kitchen/rake_tasks.rb +8 -3
- data/lib/kitchen/shell_out.rb +15 -18
- data/lib/kitchen/ssh.rb +122 -27
- data/lib/kitchen/state_file.rb +24 -7
- data/lib/kitchen/thor_tasks.rb +9 -4
- data/lib/kitchen/util.rb +43 -118
- data/lib/kitchen/version.rb +1 -1
- data/lib/vendor/hash_recursive_merge.rb +10 -2
- data/spec/kitchen/base64_stream_spec.rb +77 -0
- data/spec/kitchen/busser_spec.rb +490 -0
- data/spec/kitchen/collection_spec.rb +10 -10
- data/spec/kitchen/color_spec.rb +2 -2
- data/spec/kitchen/config_spec.rb +234 -62
- data/spec/kitchen/configurable_spec.rb +490 -0
- data/spec/kitchen/data_munger_spec.rb +1070 -862
- data/spec/kitchen/diagnostic_spec.rb +79 -0
- data/spec/kitchen/driver/base_spec.rb +80 -85
- data/spec/kitchen/driver/dummy_spec.rb +43 -14
- data/spec/kitchen/driver/proxy_spec.rb +134 -0
- data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
- data/spec/kitchen/driver_spec.rb +15 -15
- data/spec/kitchen/errors_spec.rb +309 -0
- data/spec/kitchen/instance_spec.rb +143 -46
- data/spec/kitchen/lazy_hash_spec.rb +36 -9
- data/spec/kitchen/loader/yaml_spec.rb +237 -226
- data/spec/kitchen/logger_spec.rb +419 -0
- data/spec/kitchen/logging_spec.rb +59 -0
- data/spec/kitchen/login_command_spec.rb +49 -0
- data/spec/kitchen/metadata_chopper_spec.rb +82 -0
- data/spec/kitchen/platform_spec.rb +4 -4
- data/spec/kitchen/provisioner/base_spec.rb +65 -125
- data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
- data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
- data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
- data/spec/kitchen/provisioner/shell_spec.rb +269 -0
- data/spec/kitchen/provisioner_spec.rb +6 -6
- data/spec/kitchen/shell_out_spec.rb +143 -0
- data/spec/kitchen/ssh_spec.rb +683 -0
- data/spec/kitchen/state_file_spec.rb +28 -21
- data/spec/kitchen/suite_spec.rb +7 -7
- data/spec/kitchen/util_spec.rb +68 -10
- data/spec/kitchen_spec.rb +107 -0
- data/spec/spec_helper.rb +18 -13
- data/support/chef-client-zero.rb +10 -9
- data/support/chef_helpers.sh +16 -0
- data/support/download_helpers.sh +109 -0
- data/test-kitchen.gemspec +42 -33
- metadata +107 -33
data/lib/kitchen/lazy_hash.rb
CHANGED
@@ -16,35 +16,107 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
19
|
+
require "delegate"
|
20
20
|
|
21
21
|
module Kitchen
|
22
22
|
|
23
|
-
# A modifed Hash object that may contain
|
24
|
-
# executed in the context of another object.
|
23
|
+
# A modifed Hash object that may contain callables as a value which must be
|
24
|
+
# executed in the context of another object. This allows for delayed
|
25
|
+
# evaluation of a hash value while still looking and largely feeling like a
|
26
|
+
# normal Ruby Hash.
|
27
|
+
#
|
28
|
+
# @example normal hash accessing with regular values
|
29
|
+
#
|
30
|
+
# data = {
|
31
|
+
# :symbol => true,
|
32
|
+
# "string" => "stuff"
|
33
|
+
# }
|
34
|
+
# context = "any object"
|
35
|
+
# lazy = Kitchen::Hash.new(data, context)
|
36
|
+
#
|
37
|
+
# lazy[:symbol] # => true
|
38
|
+
# lazy.fetch("string") # => "stuff"
|
39
|
+
#
|
40
|
+
# @example hash with callable blocks as values
|
41
|
+
#
|
42
|
+
# data = {
|
43
|
+
# :lambda => ->(c) { c.length },
|
44
|
+
# :proc => Proc.new { |c| c.reverse },
|
45
|
+
# :simple => "value"
|
46
|
+
# }
|
47
|
+
# context = "any object"
|
48
|
+
# lazy = Kitchen::Hash.new(data, context)
|
49
|
+
#
|
50
|
+
# lazy[:lambda] # => 10
|
51
|
+
# lazy.fetch(:proc) # => "tcejbo yna"
|
52
|
+
# lazy[:simple] # => "value"
|
25
53
|
#
|
26
54
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
27
55
|
class LazyHash < SimpleDelegator
|
28
56
|
|
57
|
+
# Creates a new LazyHash using a Hash-like object to populate itself and
|
58
|
+
# an object that can be used as context in value-callable blocks. The
|
59
|
+
# context object can be used to compute values for keys at the time of
|
60
|
+
# fetching the value.
|
61
|
+
#
|
62
|
+
# @param obj [Hash, Object] a hash-like object
|
63
|
+
# @param context [Object] an object that can be used to compute values
|
29
64
|
def initialize(obj, context)
|
30
65
|
@context = context
|
31
66
|
super(obj)
|
32
67
|
end
|
33
68
|
|
69
|
+
# Retrieves the rendered value object corresponding to the key object. If
|
70
|
+
# not found, returns the default value.
|
71
|
+
#
|
72
|
+
# @param key [Object] hash key
|
73
|
+
# @return [Object, nil] the value for key or the default value if key is
|
74
|
+
# not found
|
34
75
|
def [](key)
|
35
|
-
proc_or_val
|
76
|
+
proc_or_val(__getobj__[key])
|
77
|
+
end
|
36
78
|
|
37
|
-
|
38
|
-
|
79
|
+
# Returns a rendered value from the hash for the given key. If the key
|
80
|
+
# can't be found, there are several options: With no other arguments, it
|
81
|
+
# will raise an KeyError exception; if default is given, then that will be
|
82
|
+
# returned; if the optional code block is specified, then that will be run
|
83
|
+
# and its result returned.
|
84
|
+
#
|
85
|
+
# @param key [Object] hash key
|
86
|
+
# @param default [Object] default value if key is not set (optional)
|
87
|
+
# @return [Object, nil] the value for the key or the default value if key
|
88
|
+
# is not found
|
89
|
+
# @raise [KeyError] if the key is not found
|
90
|
+
def fetch(key, default = :__undefined__, &block)
|
91
|
+
case default
|
92
|
+
when :__undefined__
|
93
|
+
proc_or_val(__getobj__.fetch(key, &block))
|
39
94
|
else
|
40
|
-
proc_or_val
|
95
|
+
proc_or_val(__getobj__.fetch(key, default, &block))
|
41
96
|
end
|
42
97
|
end
|
43
98
|
|
99
|
+
# Returns a new Hash with all keys and rendered values of the LazyHash.
|
100
|
+
#
|
101
|
+
# @return [Hash] a new hash
|
44
102
|
def to_hash
|
45
103
|
hash = Hash.new
|
46
104
|
__getobj__.keys.each { |key| hash[key] = self[key] }
|
47
105
|
hash
|
48
106
|
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Returns an object or invokes call with context if object is callable.
|
111
|
+
#
|
112
|
+
# @return [Object] an object
|
113
|
+
# @api private
|
114
|
+
def proc_or_val(thing)
|
115
|
+
if thing.respond_to?(:call)
|
116
|
+
thing.call(@context)
|
117
|
+
else
|
118
|
+
thing
|
119
|
+
end
|
120
|
+
end
|
49
121
|
end
|
50
122
|
end
|
data/lib/kitchen/loader/yaml.rb
CHANGED
@@ -16,15 +16,15 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
20
|
-
require
|
19
|
+
require "erb"
|
20
|
+
require "vendor/hash_recursive_merge"
|
21
21
|
|
22
22
|
if RUBY_VERSION <= "1.9.3"
|
23
23
|
# ensure that Psych and not Syck is used for Ruby 1.9.2
|
24
|
-
require
|
25
|
-
YAML::ENGINE.yamler =
|
24
|
+
require "yaml"
|
25
|
+
YAML::ENGINE.yamler = "psych"
|
26
26
|
end
|
27
|
-
require
|
27
|
+
require "safe_yaml/load"
|
28
28
|
|
29
29
|
module Kitchen
|
30
30
|
|
@@ -46,7 +46,7 @@ module Kitchen
|
|
46
46
|
# config YAML file (default: `./.kitchen.yml`)
|
47
47
|
# @option options [String] :local_config path to the Kitchen local
|
48
48
|
# config YAML file (default: `./.kitchen.local.yml`)
|
49
|
-
# @option options [String] :
|
49
|
+
# @option options [String] :global_config path to the Kitchen global
|
50
50
|
# config YAML file (default: `$HOME/.kitchen/config.yml`)
|
51
51
|
# @option options [String] :process_erb whether or not to process YAML
|
52
52
|
# through an ERB processor (default: `true`)
|
@@ -70,7 +70,7 @@ module Kitchen
|
|
70
70
|
#
|
71
71
|
# @return [Hash] merged configuration data
|
72
72
|
def read
|
73
|
-
if !
|
73
|
+
if !File.exist?(config_file)
|
74
74
|
raise UserError, "Kitchen YAML file #{config_file} does not exist."
|
75
75
|
end
|
76
76
|
|
@@ -92,64 +92,146 @@ module Kitchen
|
|
92
92
|
result
|
93
93
|
end
|
94
94
|
|
95
|
-
|
95
|
+
private
|
96
96
|
|
97
|
-
|
97
|
+
# @return [String] the absolute path to the Kitchen config YAML file
|
98
|
+
# @api private
|
99
|
+
attr_reader :config_file
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
101
|
+
# @return [String] the absolute path to the Kitchen local config YAML
|
102
|
+
# file
|
103
|
+
# @api private
|
104
|
+
attr_reader :local_config_file
|
105
|
+
|
106
|
+
# @return [String] the absolute path to the Kitchen global config YAML
|
107
|
+
# file
|
108
|
+
# @api private
|
109
|
+
attr_reader :global_config_file
|
102
110
|
|
111
|
+
# Performed a prioritized recursive merge of several source Hashes and
|
112
|
+
# returns a new merged Hash. There are 3 sources of configuration data:
|
113
|
+
#
|
114
|
+
# 1. local config
|
115
|
+
# 2. project config
|
116
|
+
# 3. global config
|
117
|
+
#
|
118
|
+
# The merge order is local -> project -> global, meaning that elements at
|
119
|
+
# the top of the above list will be merged last, and have greater
|
120
|
+
# precedence than elements at the bottom of the list.
|
121
|
+
#
|
122
|
+
# @return [Hash] a new merged Hash
|
123
|
+
# @api private
|
103
124
|
def combined_hash
|
104
|
-
y = if @
|
105
|
-
normalize(
|
125
|
+
y = if @process_global
|
126
|
+
normalize(global_yaml).rmerge(normalize(yaml))
|
106
127
|
else
|
107
128
|
normalize(yaml)
|
108
129
|
end
|
109
|
-
@
|
130
|
+
@process_local ? y.rmerge(normalize(local_yaml)) : y
|
110
131
|
end
|
111
132
|
|
133
|
+
# Loads and returns the Kitchen config YAML as a Hash.
|
134
|
+
#
|
135
|
+
# @return [Hash] the config hash
|
136
|
+
# @api private
|
112
137
|
def yaml
|
113
138
|
parse_yaml_string(yaml_string(config_file), config_file)
|
114
139
|
end
|
115
140
|
|
141
|
+
# Loads and returns the Kitchen local config YAML as a Hash.
|
142
|
+
#
|
143
|
+
# @return [Hash] the config hash
|
144
|
+
# @api private
|
116
145
|
def local_yaml
|
117
146
|
parse_yaml_string(yaml_string(local_config_file), local_config_file)
|
118
147
|
end
|
119
148
|
|
149
|
+
# Loads and returns the Kitchen global config YAML as a Hash.
|
150
|
+
#
|
151
|
+
# @return [Hash] the config hash
|
152
|
+
# @api private
|
120
153
|
def global_yaml
|
121
154
|
parse_yaml_string(yaml_string(global_config_file), global_config_file)
|
122
155
|
end
|
123
156
|
|
157
|
+
# Loads a file to a string and optionally passes it through an ERb
|
158
|
+
# process.
|
159
|
+
#
|
160
|
+
# @return [String] a file's contents as a string
|
161
|
+
# @api private
|
124
162
|
def yaml_string(file)
|
125
163
|
string = read_file(file)
|
126
164
|
|
127
165
|
@process_erb ? process_erb(string, file) : string
|
128
166
|
end
|
129
167
|
|
168
|
+
# Passes a string through ERb to evaulate any ERb blocks.
|
169
|
+
#
|
170
|
+
# @param string [String] the string to process
|
171
|
+
# @param file [String] an absolute path to the file represented as the
|
172
|
+
# passed in string, used for error reporting
|
173
|
+
# @return [String] a new string, passed through an ERb process
|
174
|
+
# @raise [UserError] if an ERb parsing error occurs
|
175
|
+
# @api private
|
130
176
|
def process_erb(string, file)
|
131
|
-
ERB.new(string)
|
177
|
+
tpl = ERB.new(string)
|
178
|
+
tpl.filename = file
|
179
|
+
tpl.result
|
132
180
|
rescue => e
|
133
|
-
raise UserError, "Error parsing ERB content in #{file} "
|
134
|
-
"(#{e.class}: #{e.message}).\n"
|
135
|
-
"Please run `kitchen diagnose --no-instances --loader' to help "
|
181
|
+
raise UserError, "Error parsing ERB content in #{file} " \
|
182
|
+
"(#{e.class}: #{e.message}).\n" \
|
183
|
+
"Please run `kitchen diagnose --no-instances --loader' to help " \
|
136
184
|
"debug your issue."
|
137
185
|
end
|
138
186
|
|
187
|
+
# Reads a file and returns its contents as a string.
|
188
|
+
#
|
189
|
+
# @param file [String] a path to a file
|
190
|
+
# @return [String] the files contents, or an empty string if the file
|
191
|
+
# does not exist
|
192
|
+
# @api private
|
139
193
|
def read_file(file)
|
140
|
-
File.
|
194
|
+
File.exist?(file.to_s) ? IO.read(file) : ""
|
195
|
+
end
|
196
|
+
|
197
|
+
# Determines the default absolute path to the Kitchen config YAML file,
|
198
|
+
# based on current working directory.
|
199
|
+
#
|
200
|
+
# @return [String] an absolute path to a Kitchen config YAML file
|
201
|
+
# @api private
|
202
|
+
def default_config_file
|
203
|
+
File.join(Dir.pwd, ".kitchen.yml")
|
141
204
|
end
|
142
205
|
|
206
|
+
# Determines the default absolute path to the Kitchen local YAML file,
|
207
|
+
# based on the base Kitchen config YAML file.
|
208
|
+
#
|
209
|
+
# @return [String] an absolute path to a Kitchen local YAML file
|
210
|
+
# @api private
|
143
211
|
def default_local_config_file
|
144
212
|
config_file.sub(/(#{File.extname(config_file)})$/, '.local\1')
|
145
213
|
end
|
146
214
|
|
215
|
+
# Determines the default absolute path to the Kitchen global YAML file,
|
216
|
+
# based on the base Kitchen config YAML file.
|
217
|
+
#
|
218
|
+
# @return [String] an absolute path to a Kitchen global YAML file
|
219
|
+
# @api private
|
147
220
|
def default_global_config_file
|
148
221
|
File.join(File.expand_path(ENV["HOME"]), ".kitchen", "config.yml")
|
149
222
|
end
|
150
223
|
|
224
|
+
# Generate a diganose Hash for a particular YAML file Hash. If an error
|
225
|
+
# occurs when loading the data, then a failure hash will be inserted
|
226
|
+
# into the `:raw_data` sub-hash.
|
227
|
+
#
|
228
|
+
# @param component [Symbol] a YAML source component
|
229
|
+
# @param file [String] the absolute path to a file which is used for
|
230
|
+
# reporting (default: `nil`)
|
231
|
+
# @return [Hash] a hash data structure
|
232
|
+
# @api private
|
151
233
|
def diagnose_component(component, file = nil)
|
152
|
-
return if file && !File.
|
234
|
+
return if file && !File.exist?(file)
|
153
235
|
|
154
236
|
hash = begin
|
155
237
|
send(component)
|
@@ -160,6 +242,12 @@ module Kitchen
|
|
160
242
|
{ :filename => file, :raw_data => hash }
|
161
243
|
end
|
162
244
|
|
245
|
+
# Generates a Hash respresenting a failure, given an Exception object.
|
246
|
+
#
|
247
|
+
# @param e [Exception] an exception
|
248
|
+
# @param file [String] the absolute path to a file (default: `nil`)
|
249
|
+
# @return [Hash] a hash data structure
|
250
|
+
# @api private
|
163
251
|
def failure_hash(e, file = nil)
|
164
252
|
result = {
|
165
253
|
:error => {
|
@@ -172,14 +260,50 @@ module Kitchen
|
|
172
260
|
result
|
173
261
|
end
|
174
262
|
|
263
|
+
# Destructively modify an object containing one or more hashes so that
|
264
|
+
# the resulting formatted data can be consumed upstream.
|
265
|
+
#
|
266
|
+
# @param obj [Object] an object
|
267
|
+
# @return [Object] an object
|
268
|
+
# @api private
|
175
269
|
def normalize(obj)
|
176
270
|
if obj.is_a?(Hash)
|
177
|
-
obj.inject(Hash.new) { |h, (k, v)| normalize_hash(h, k, v)
|
271
|
+
obj.inject(Hash.new) { |h, (k, v)| normalize_hash(h, k, v); h }
|
178
272
|
else
|
179
273
|
obj
|
180
274
|
end
|
181
275
|
end
|
182
276
|
|
277
|
+
# Normalizes certain keys in the root of a data hash to be a proper
|
278
|
+
# sub-hash in all cases. Specifically handled are the following cases:
|
279
|
+
#
|
280
|
+
# * If the value for certain keys (`"driver"`, `"provisioner"`,
|
281
|
+
# `"busser"`) are set to `nil`, a new Hash will be put in its place.
|
282
|
+
# * If the value for certain keys is a String, then the value is
|
283
|
+
# converted to a new Hash with a default key pointing to the original
|
284
|
+
# String.
|
285
|
+
#
|
286
|
+
# Given a hash:
|
287
|
+
#
|
288
|
+
# { "driver" => nil }
|
289
|
+
#
|
290
|
+
# this method would return:
|
291
|
+
#
|
292
|
+
# { "driver" => {} }
|
293
|
+
#
|
294
|
+
# Given a hash:
|
295
|
+
#
|
296
|
+
# { :driver => "coolbeans" }
|
297
|
+
#
|
298
|
+
# this method would return:
|
299
|
+
#
|
300
|
+
# { :name => { "driver" => "coolbeans" } }
|
301
|
+
#
|
302
|
+
#
|
303
|
+
# @param hash [Hash] the Hash to normalize
|
304
|
+
# @param key [Symbol] the key to normalize
|
305
|
+
# @param value [Object] the value to normalize
|
306
|
+
# @api private
|
183
307
|
def normalize_hash(hash, key, value)
|
184
308
|
case key
|
185
309
|
when "driver", "provisioner", "busser"
|
@@ -196,20 +320,28 @@ module Kitchen
|
|
196
320
|
end
|
197
321
|
end
|
198
322
|
|
323
|
+
# Parses a YAML string and returns a Hash.
|
324
|
+
#
|
325
|
+
# @param string [String] a yaml document as a string
|
326
|
+
# @param file_name [String] an absolute path to the file represented as
|
327
|
+
# the passed in string, used for error reporting
|
328
|
+
# @return [Hash] a hash
|
329
|
+
# @raise [UserError] if the string document cannot be parsed
|
330
|
+
# @api private
|
199
331
|
def parse_yaml_string(string, file_name)
|
200
332
|
return Hash.new if string.nil? || string.empty?
|
201
333
|
|
202
334
|
result = SafeYAML.load(string) || Hash.new
|
203
335
|
unless result.is_a?(Hash)
|
204
|
-
raise UserError, "Error parsing #{file_name} as YAML "
|
205
|
-
"(Result of parse was not a Hash, but was a #{result.class}).\n"
|
206
|
-
"Please run `kitchen diagnose --no-instances --loader' to help "
|
336
|
+
raise UserError, "Error parsing #{file_name} as YAML " \
|
337
|
+
"(Result of parse was not a Hash, but was a #{result.class}).\n" \
|
338
|
+
"Please run `kitchen diagnose --no-instances --loader' to help " \
|
207
339
|
"debug your issue."
|
208
340
|
end
|
209
341
|
result
|
210
342
|
rescue SyntaxError, Psych::SyntaxError
|
211
|
-
raise UserError, "Error parsing #{file_name} as YAML.\n"
|
212
|
-
"Please run `kitchen diagnose --no-instances --loader' to help "
|
343
|
+
raise UserError, "Error parsing #{file_name} as YAML.\n" \
|
344
|
+
"Please run `kitchen diagnose --no-instances --loader' to help " \
|
213
345
|
"debug your issue."
|
214
346
|
end
|
215
347
|
end
|
data/lib/kitchen/logger.rb
CHANGED
@@ -16,8 +16,8 @@
|
|
16
16
|
# See the License for the specific language governing permissions and
|
17
17
|
# limitations under the License.
|
18
18
|
|
19
|
-
require
|
20
|
-
require
|
19
|
+
require "fileutils"
|
20
|
+
require "logger"
|
21
21
|
|
22
22
|
module Kitchen
|
23
23
|
|
@@ -31,55 +31,261 @@ module Kitchen
|
|
31
31
|
|
32
32
|
include ::Logger::Severity
|
33
33
|
|
34
|
+
# @return [IO] the log device
|
34
35
|
attr_reader :logdev
|
35
36
|
|
37
|
+
# Constructs a new logger.
|
38
|
+
#
|
39
|
+
# @param options [Hash] configuration for a new logger
|
40
|
+
# @option options [Symbol] :color color to use when when outputting
|
41
|
+
# messages
|
42
|
+
# @option options [Integer] :level the logging severity threshold
|
43
|
+
# (default: `Kitchen::DEFAULT_LOG_LEVEL`)
|
44
|
+
# @option options [String,IO] :logdev filepath String or IO object to be
|
45
|
+
# used for logging (default: `nil`)
|
46
|
+
# @option options [String] :progname program name to include in log
|
47
|
+
# messages (default: `"Kitchen"`)
|
48
|
+
# @option options [IO] :stdout a standard out IO object to use
|
49
|
+
# (default: `$stdout`)
|
36
50
|
def initialize(options = {})
|
37
51
|
color = options[:color]
|
38
52
|
|
39
53
|
@loggers = []
|
40
54
|
@loggers << @logdev = logdev_logger(options[:logdev]) if options[:logdev]
|
41
55
|
@loggers << stdout_logger(options[:stdout], color) if options[:stdout]
|
42
|
-
@loggers << stdout_logger(
|
56
|
+
@loggers << stdout_logger($stdout, color) if @loggers.empty?
|
43
57
|
|
44
58
|
self.progname = options[:progname] || "Kitchen"
|
45
59
|
self.level = options[:level] || default_log_level
|
46
60
|
end
|
47
61
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
62
|
+
class << self
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
# @!macro delegate_to_first_logger
|
68
|
+
# @method $1()
|
69
|
+
def delegate_to_first_logger(meth)
|
70
|
+
define_method(meth) { |*args| @loggers.first.public_send(meth, *args) }
|
52
71
|
end
|
53
|
-
end
|
54
72
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
73
|
+
# @api private
|
74
|
+
# @!macro delegate_to_all_loggers
|
75
|
+
# @method $1()
|
76
|
+
def delegate_to_all_loggers(meth)
|
77
|
+
define_method(meth) do |*args|
|
78
|
+
result = nil
|
79
|
+
@loggers.each { |l| result = l.public_send(meth, *args) }
|
80
|
+
result
|
81
|
+
end
|
62
82
|
end
|
63
83
|
end
|
64
84
|
|
85
|
+
# @return [Integer] the logging severity threshold
|
86
|
+
# @see http://is.gd/Okuy5p
|
87
|
+
delegate_to_first_logger :level
|
88
|
+
|
89
|
+
# Sets the logging severity threshold.
|
90
|
+
#
|
91
|
+
# @param level [Integer] the logging severity threshold
|
92
|
+
# @see http://is.gd/H1VBFH
|
93
|
+
delegate_to_all_loggers :level=
|
94
|
+
|
95
|
+
# @return [String] program name to include in log messages
|
96
|
+
# @see http://is.gd/5uHGK0
|
97
|
+
delegate_to_first_logger :progname
|
98
|
+
|
99
|
+
# Sets the program name to include in log messages.
|
100
|
+
#
|
101
|
+
# @param progname [String] the program name to include in log messages
|
102
|
+
# @see http://is.gd/f2U5Xj
|
103
|
+
delegate_to_all_loggers :progname=
|
104
|
+
|
105
|
+
# @return [String] the date format being used
|
106
|
+
# @see http://is.gd/btmFWJ
|
107
|
+
delegate_to_first_logger :datetime_format
|
108
|
+
|
109
|
+
# Sets the date format being used.
|
110
|
+
#
|
111
|
+
# @param format [String] the date format
|
112
|
+
# @see http://is.gd/M36ml8
|
113
|
+
delegate_to_all_loggers :datetime_format=
|
114
|
+
|
115
|
+
# Log a message if the given severity is high enough.
|
116
|
+
#
|
117
|
+
# @see http://is.gd/5opBW0
|
118
|
+
delegate_to_all_loggers :add
|
119
|
+
|
120
|
+
# Dump one or more messages to info.
|
121
|
+
#
|
122
|
+
# @param message [#to_s] the message to log
|
123
|
+
# @see http://is.gd/BCp5KV
|
124
|
+
delegate_to_all_loggers :<<
|
125
|
+
|
126
|
+
# Log a message with severity of banner (high level).
|
127
|
+
#
|
128
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
129
|
+
# form, this is the progname to use in the log message.
|
130
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
131
|
+
# logger's level is sufficient to log the message. This allows you to
|
132
|
+
# create potentially expensive logging messages that are only called when
|
133
|
+
# the logger is configured to show them.
|
134
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
135
|
+
# particular logger), log no message, and return true
|
136
|
+
# @see http://is.gd/pYUCYU
|
137
|
+
delegate_to_all_loggers :banner
|
138
|
+
|
139
|
+
# Log a message with severity of debug.
|
140
|
+
#
|
141
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
142
|
+
# form, this is the progname to use in the log message.
|
143
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
144
|
+
# logger's level is sufficient to log the message. This allows you to
|
145
|
+
# create potentially expensive logging messages that are only called when
|
146
|
+
# the logger is configured to show them.
|
147
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
148
|
+
# particular logger), log no message, and return true
|
149
|
+
# @see http://is.gd/Re97Zp
|
150
|
+
delegate_to_all_loggers :debug
|
151
|
+
|
152
|
+
# @return [true,false] whether or not the current severity level
|
153
|
+
# allows for the printing of debug messages
|
154
|
+
# @see http://is.gd/Iq08xB
|
155
|
+
delegate_to_first_logger :debug?
|
156
|
+
|
157
|
+
# Log a message with severity of info.
|
158
|
+
#
|
159
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
160
|
+
# form, this is the progname to use in the log message.
|
161
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
162
|
+
# logger's level is sufficient to log the message. This allows you to
|
163
|
+
# create potentially expensive logging messages that are only called when
|
164
|
+
# the logger is configured to show them.
|
165
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
166
|
+
# particular logger), log no message, and return true
|
167
|
+
# @see http://is.gd/pYUCYU
|
168
|
+
delegate_to_all_loggers :info
|
169
|
+
|
170
|
+
# @return [true,false] whether or not the current severity level
|
171
|
+
# allows for the printing of info messages
|
172
|
+
# @see http://is.gd/lBtJkT
|
173
|
+
delegate_to_first_logger :info?
|
174
|
+
|
175
|
+
# Log a message with severity of error.
|
176
|
+
#
|
177
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
178
|
+
# form, this is the progname to use in the log message.
|
179
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
180
|
+
# logger's level is sufficient to log the message. This allows you to
|
181
|
+
# create potentially expensive logging messages that are only called when
|
182
|
+
# the logger is configured to show them.
|
183
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
184
|
+
# particular logger), log no message, and return true
|
185
|
+
# @see http://is.gd/mLwYMl
|
186
|
+
delegate_to_all_loggers :error
|
187
|
+
|
188
|
+
# @return [true,false] whether or not the current severity level
|
189
|
+
# allows for the printing of error messages
|
190
|
+
# @see http://is.gd/QY19JL
|
191
|
+
delegate_to_first_logger :error?
|
192
|
+
|
193
|
+
# Log a message with severity of warn.
|
194
|
+
#
|
195
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
196
|
+
# form, this is the progname to use in the log message.
|
197
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
198
|
+
# logger's level is sufficient to log the message. This allows you to
|
199
|
+
# create potentially expensive logging messages that are only called when
|
200
|
+
# the logger is configured to show them.
|
201
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
202
|
+
# particular logger), log no message, and return true
|
203
|
+
# @see http://is.gd/PX9AIS
|
204
|
+
delegate_to_all_loggers :warn
|
205
|
+
|
206
|
+
# @return [true,false] whether or not the current severity level
|
207
|
+
# allows for the printing of warn messages
|
208
|
+
# @see http://is.gd/Gdr4lD
|
209
|
+
delegate_to_first_logger :warn?
|
210
|
+
|
211
|
+
# Log a message with severity of fatal.
|
212
|
+
#
|
213
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
214
|
+
# form, this is the progname to use in the log message.
|
215
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
216
|
+
# logger's level is sufficient to log the message. This allows you to
|
217
|
+
# create potentially expensive logging messages that are only called when
|
218
|
+
# the logger is configured to show them.
|
219
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
220
|
+
# particular logger), log no message, and return true
|
221
|
+
# @see http://is.gd/5ElFPK
|
222
|
+
delegate_to_all_loggers :fatal
|
223
|
+
|
224
|
+
# @return [true,false] whether or not the current severity level
|
225
|
+
# allows for the printing of fatal messages
|
226
|
+
# @see http://is.gd/7PgwRl
|
227
|
+
delegate_to_first_logger :fatal?
|
228
|
+
|
229
|
+
# Log a message with severity of unknown.
|
230
|
+
#
|
231
|
+
# @param message_or_progname [#to_s] the message to log. In the block
|
232
|
+
# form, this is the progname to use in the log message.
|
233
|
+
# @yield evaluates to the message to log. This is not evaluated unless the
|
234
|
+
# logger's level is sufficient to log the message. This allows you to
|
235
|
+
# create potentially expensive logging messages that are only called when
|
236
|
+
# the logger is configured to show them.
|
237
|
+
# @return [nil,true] when the given severity is not high enough (for this
|
238
|
+
# particular logger), log no message, and return true
|
239
|
+
# @see http://is.gd/Y4hqpf
|
240
|
+
delegate_to_all_loggers :unknown
|
241
|
+
|
242
|
+
# Close the logging devices.
|
243
|
+
#
|
244
|
+
# @see http://is.gd/b13cVn
|
245
|
+
delegate_to_all_loggers :close
|
246
|
+
|
65
247
|
private
|
66
248
|
|
249
|
+
# @return [Integer] the default logger level
|
250
|
+
# @api private
|
67
251
|
def default_log_level
|
68
252
|
Util.to_logger_level(Kitchen::DEFAULT_LOG_LEVEL)
|
69
253
|
end
|
70
254
|
|
255
|
+
# Construct a new standard out logger.
|
256
|
+
#
|
257
|
+
# @param stdout [IO] the IO object that represents stdout (or similar)
|
258
|
+
# @param color [Symbol] color to use when outputing messages
|
259
|
+
# @return [StdoutLogger] a new logger
|
260
|
+
# @api private
|
71
261
|
def stdout_logger(stdout, color)
|
72
262
|
logger = StdoutLogger.new(stdout)
|
73
|
-
|
74
|
-
|
263
|
+
if Kitchen.tty?
|
264
|
+
logger.formatter = proc do |_severity, _datetime, _progname, msg|
|
265
|
+
Color.colorize("#{msg}", color).concat("\n")
|
266
|
+
end
|
267
|
+
else
|
268
|
+
logger.formatter = proc do |_severity, _datetime, _progname, msg|
|
269
|
+
msg.concat("\n")
|
270
|
+
end
|
75
271
|
end
|
76
272
|
logger
|
77
273
|
end
|
78
274
|
|
275
|
+
# Construct a new logdev logger.
|
276
|
+
#
|
277
|
+
# @param filepath_or_logdev [String,IO] a filepath String or IO object
|
278
|
+
# @return [LogdevLogger] a new logger
|
279
|
+
# @api private
|
79
280
|
def logdev_logger(filepath_or_logdev)
|
80
281
|
LogdevLogger.new(resolve_logdev(filepath_or_logdev))
|
81
282
|
end
|
82
283
|
|
284
|
+
# Return an IO object from a filepath String or the IO object itself.
|
285
|
+
#
|
286
|
+
# @param filepath_or_logdev [String,IO] a filepath String or IO object
|
287
|
+
# @return [IO] an IO object
|
288
|
+
# @api private
|
83
289
|
def resolve_logdev(filepath_or_logdev)
|
84
290
|
if filepath_or_logdev.is_a? String
|
85
291
|
FileUtils.mkdir_p(File.dirname(filepath_or_logdev))
|
@@ -97,21 +303,39 @@ module Kitchen
|
|
97
303
|
|
98
304
|
alias_method :super_info, :info
|
99
305
|
|
306
|
+
# Dump one or more messages to info.
|
307
|
+
#
|
308
|
+
# @param msg [String] a message
|
100
309
|
def <<(msg)
|
101
|
-
|
310
|
+
@buffer ||= ""
|
311
|
+
lines, _, remainder = msg.rpartition("\n")
|
312
|
+
if lines.empty?
|
313
|
+
@buffer << remainder
|
314
|
+
else
|
315
|
+
lines.insert(0, @buffer)
|
316
|
+
lines.split("\n").each { |l| format_line(l.chomp) }
|
317
|
+
@buffer = ""
|
318
|
+
end
|
102
319
|
end
|
103
320
|
|
321
|
+
# Log a banner message.
|
322
|
+
#
|
323
|
+
# @param msg [String] a message
|
104
324
|
def banner(msg = nil, &block)
|
105
325
|
super_info("-----> #{msg}", &block)
|
106
326
|
end
|
107
327
|
|
108
328
|
private
|
109
329
|
|
330
|
+
# Reformat a line if it already contains log formatting.
|
331
|
+
#
|
332
|
+
# @param line [String] a message line
|
333
|
+
# @api private
|
110
334
|
def format_line(line)
|
111
335
|
case line
|
112
|
-
when %r{^-----> } then banner(line.gsub(%r{^[ >-]{6} },
|
113
|
-
when %r{^>>>>>> } then error(line.gsub(%r{^[ >-]{6} },
|
114
|
-
when %r{^ } then info(line.gsub(%r{^[ >-]{6} },
|
336
|
+
when %r{^-----> } then banner(line.gsub(%r{^[ >-]{6} }, ""))
|
337
|
+
when %r{^>>>>>> } then error(line.gsub(%r{^[ >-]{6} }, ""))
|
338
|
+
when %r{^ } then info(line.gsub(%r{^[ >-]{6} }, ""))
|
115
339
|
else info(line)
|
116
340
|
end
|
117
341
|
end
|
@@ -121,25 +345,47 @@ module Kitchen
|
|
121
345
|
# output.
|
122
346
|
class StdoutLogger < LogdevLogger
|
123
347
|
|
348
|
+
# Log a debug message
|
349
|
+
#
|
350
|
+
# @param msg [String] a message
|
124
351
|
def debug(msg = nil, &block)
|
125
352
|
super("D #{msg}", &block)
|
126
353
|
end
|
127
354
|
|
355
|
+
# Log an info message
|
356
|
+
#
|
357
|
+
# @param msg [String] a message
|
128
358
|
def info(msg = nil, &block)
|
129
359
|
super(" #{msg}", &block)
|
130
360
|
end
|
131
361
|
|
362
|
+
# Log a warn message
|
363
|
+
#
|
364
|
+
# @param msg [String] a message
|
132
365
|
def warn(msg = nil, &block)
|
133
366
|
super("$$$$$$ #{msg}", &block)
|
134
367
|
end
|
135
368
|
|
369
|
+
# Log an error message
|
370
|
+
#
|
371
|
+
# @param msg [String] a message
|
136
372
|
def error(msg = nil, &block)
|
137
373
|
super(">>>>>> #{msg}", &block)
|
138
374
|
end
|
139
375
|
|
376
|
+
# Log a fatal message
|
377
|
+
#
|
378
|
+
# @param msg [String] a message
|
140
379
|
def fatal(msg = nil, &block)
|
141
380
|
super("!!!!!! #{msg}", &block)
|
142
381
|
end
|
382
|
+
|
383
|
+
# Log an unknown message
|
384
|
+
#
|
385
|
+
# @param msg [String] a message
|
386
|
+
def unknown(msg = nil, &block)
|
387
|
+
super("?????? #{msg}", &block)
|
388
|
+
end
|
143
389
|
end
|
144
390
|
end
|
145
391
|
end
|