test-kitchen 1.7.0 → 1.7.1.dev

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +8 -8
  3. data/.gitattributes +3 -0
  4. data/.github/ISSUE_TEMPLATE.md +55 -55
  5. data/.gitignore +28 -28
  6. data/.kitchen.ci.yml +23 -23
  7. data/.kitchen.proxy.yml +27 -27
  8. data/.rubocop.yml +3 -3
  9. data/.travis.yml +70 -70
  10. data/.yardopts +3 -3
  11. data/Berksfile +3 -3
  12. data/CHANGELOG.md +1090 -1083
  13. data/CONTRIBUTING.md +14 -14
  14. data/Gemfile +19 -19
  15. data/Gemfile.proxy_tests +4 -4
  16. data/Guardfile +42 -42
  17. data/LICENSE +15 -15
  18. data/MAINTAINERS.md +23 -23
  19. data/README.md +135 -135
  20. data/Rakefile +61 -61
  21. data/appveyor.yml +44 -44
  22. data/features/kitchen_action_commands.feature +164 -164
  23. data/features/kitchen_command.feature +16 -16
  24. data/features/kitchen_console_command.feature +34 -34
  25. data/features/kitchen_defaults.feature +38 -38
  26. data/features/kitchen_diagnose_command.feature +96 -96
  27. data/features/kitchen_driver_create_command.feature +64 -64
  28. data/features/kitchen_driver_discover_command.feature +25 -25
  29. data/features/kitchen_help_command.feature +16 -16
  30. data/features/kitchen_init_command.feature +274 -274
  31. data/features/kitchen_list_command.feature +104 -104
  32. data/features/kitchen_login_command.feature +62 -62
  33. data/features/kitchen_sink_command.feature +30 -30
  34. data/features/kitchen_test_command.feature +88 -88
  35. data/features/step_definitions/gem_steps.rb +36 -36
  36. data/features/step_definitions/git_steps.rb +5 -5
  37. data/features/step_definitions/output_steps.rb +5 -5
  38. data/features/support/env.rb +75 -75
  39. data/lib/kitchen.rb +150 -150
  40. data/lib/kitchen/base64_stream.rb +55 -55
  41. data/lib/kitchen/cli.rb +419 -419
  42. data/lib/kitchen/collection.rb +55 -55
  43. data/lib/kitchen/color.rb +65 -65
  44. data/lib/kitchen/command.rb +185 -185
  45. data/lib/kitchen/command/action.rb +45 -45
  46. data/lib/kitchen/command/console.rb +58 -58
  47. data/lib/kitchen/command/diagnose.rb +92 -92
  48. data/lib/kitchen/command/driver_discover.rb +105 -105
  49. data/lib/kitchen/command/exec.rb +41 -41
  50. data/lib/kitchen/command/list.rb +119 -119
  51. data/lib/kitchen/command/login.rb +43 -43
  52. data/lib/kitchen/command/sink.rb +54 -54
  53. data/lib/kitchen/command/test.rb +51 -51
  54. data/lib/kitchen/config.rb +322 -322
  55. data/lib/kitchen/configurable.rb +529 -529
  56. data/lib/kitchen/data_munger.rb +959 -959
  57. data/lib/kitchen/diagnostic.rb +141 -141
  58. data/lib/kitchen/driver.rb +56 -56
  59. data/lib/kitchen/driver/base.rb +134 -134
  60. data/lib/kitchen/driver/dummy.rb +108 -108
  61. data/lib/kitchen/driver/proxy.rb +72 -72
  62. data/lib/kitchen/driver/ssh_base.rb +357 -357
  63. data/lib/kitchen/errors.rb +229 -229
  64. data/lib/kitchen/generator/driver_create.rb +177 -177
  65. data/lib/kitchen/generator/init.rb +296 -296
  66. data/lib/kitchen/instance.rb +662 -662
  67. data/lib/kitchen/lazy_hash.rb +142 -142
  68. data/lib/kitchen/loader/yaml.rb +349 -349
  69. data/lib/kitchen/logger.rb +423 -423
  70. data/lib/kitchen/logging.rb +56 -56
  71. data/lib/kitchen/login_command.rb +52 -52
  72. data/lib/kitchen/metadata_chopper.rb +52 -52
  73. data/lib/kitchen/platform.rb +67 -67
  74. data/lib/kitchen/provisioner.rb +54 -54
  75. data/lib/kitchen/provisioner/base.rb +236 -236
  76. data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
  77. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
  78. data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
  79. data/lib/kitchen/provisioner/chef_apply.rb +124 -124
  80. data/lib/kitchen/provisioner/chef_base.rb +341 -341
  81. data/lib/kitchen/provisioner/chef_solo.rb +88 -88
  82. data/lib/kitchen/provisioner/chef_zero.rb +245 -245
  83. data/lib/kitchen/provisioner/dummy.rb +79 -79
  84. data/lib/kitchen/provisioner/shell.rb +138 -138
  85. data/lib/kitchen/rake_tasks.rb +63 -63
  86. data/lib/kitchen/shell_out.rb +93 -93
  87. data/lib/kitchen/ssh.rb +276 -276
  88. data/lib/kitchen/state_file.rb +120 -120
  89. data/lib/kitchen/suite.rb +51 -51
  90. data/lib/kitchen/thor_tasks.rb +66 -66
  91. data/lib/kitchen/transport.rb +54 -54
  92. data/lib/kitchen/transport/base.rb +176 -176
  93. data/lib/kitchen/transport/dummy.rb +79 -79
  94. data/lib/kitchen/transport/ssh.rb +364 -364
  95. data/lib/kitchen/transport/winrm.rb +486 -486
  96. data/lib/kitchen/util.rb +147 -147
  97. data/lib/kitchen/verifier.rb +55 -55
  98. data/lib/kitchen/verifier/base.rb +235 -235
  99. data/lib/kitchen/verifier/busser.rb +277 -277
  100. data/lib/kitchen/verifier/dummy.rb +79 -79
  101. data/lib/kitchen/verifier/shell.rb +101 -101
  102. data/lib/kitchen/version.rb +21 -21
  103. data/lib/vendor/hash_recursive_merge.rb +82 -82
  104. data/spec/kitchen/base64_stream_spec.rb +77 -77
  105. data/spec/kitchen/cli_spec.rb +56 -56
  106. data/spec/kitchen/collection_spec.rb +80 -80
  107. data/spec/kitchen/color_spec.rb +54 -54
  108. data/spec/kitchen/config_spec.rb +408 -408
  109. data/spec/kitchen/configurable_spec.rb +1095 -1095
  110. data/spec/kitchen/data_munger_spec.rb +2694 -2694
  111. data/spec/kitchen/diagnostic_spec.rb +129 -129
  112. data/spec/kitchen/driver/base_spec.rb +121 -121
  113. data/spec/kitchen/driver/dummy_spec.rb +199 -199
  114. data/spec/kitchen/driver/proxy_spec.rb +138 -138
  115. data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
  116. data/spec/kitchen/driver_spec.rb +112 -112
  117. data/spec/kitchen/errors_spec.rb +309 -309
  118. data/spec/kitchen/instance_spec.rb +1419 -1419
  119. data/spec/kitchen/lazy_hash_spec.rb +117 -117
  120. data/spec/kitchen/loader/yaml_spec.rb +774 -774
  121. data/spec/kitchen/logger_spec.rb +429 -429
  122. data/spec/kitchen/logging_spec.rb +59 -59
  123. data/spec/kitchen/login_command_spec.rb +68 -68
  124. data/spec/kitchen/metadata_chopper_spec.rb +82 -82
  125. data/spec/kitchen/platform_spec.rb +89 -89
  126. data/spec/kitchen/provisioner/base_spec.rb +386 -386
  127. data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
  128. data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
  129. data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
  130. data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
  131. data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
  132. data/spec/kitchen/provisioner/shell_spec.rb +566 -566
  133. data/spec/kitchen/provisioner_spec.rb +107 -107
  134. data/spec/kitchen/shell_out_spec.rb +150 -150
  135. data/spec/kitchen/ssh_spec.rb +693 -693
  136. data/spec/kitchen/state_file_spec.rb +129 -129
  137. data/spec/kitchen/suite_spec.rb +62 -62
  138. data/spec/kitchen/transport/base_spec.rb +89 -89
  139. data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
  140. data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
  141. data/spec/kitchen/transport_spec.rb +112 -112
  142. data/spec/kitchen/util_spec.rb +165 -165
  143. data/spec/kitchen/verifier/base_spec.rb +362 -362
  144. data/spec/kitchen/verifier/busser_spec.rb +610 -610
  145. data/spec/kitchen/verifier/dummy_spec.rb +99 -99
  146. data/spec/kitchen/verifier/shell_spec.rb +160 -160
  147. data/spec/kitchen/verifier_spec.rb +120 -120
  148. data/spec/kitchen_spec.rb +114 -114
  149. data/spec/spec_helper.rb +85 -85
  150. data/spec/support/powershell_max_size_spec.rb +40 -40
  151. data/support/busser_install_command.ps1 +14 -14
  152. data/support/busser_install_command.sh +14 -14
  153. data/support/chef-client-zero.rb +77 -77
  154. data/support/chef_base_init_command.ps1 +18 -18
  155. data/support/chef_base_init_command.sh +2 -2
  156. data/support/chef_base_install_command.ps1 +85 -85
  157. data/support/chef_base_install_command.sh +229 -229
  158. data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
  159. data/support/chef_zero_prepare_command_legacy.sh +10 -10
  160. data/support/download_helpers.sh +109 -109
  161. data/support/dummy-validation.pem +27 -27
  162. data/templates/driver/CHANGELOG.md.erb +3 -3
  163. data/templates/driver/Gemfile.erb +3 -3
  164. data/templates/driver/README.md.erb +64 -64
  165. data/templates/driver/Rakefile.erb +21 -21
  166. data/templates/driver/driver.rb.erb +23 -23
  167. data/templates/driver/gemspec.erb +29 -29
  168. data/templates/driver/gitignore.erb +17 -17
  169. data/templates/driver/license_apachev2.erb +15 -15
  170. data/templates/driver/license_lgplv3.erb +16 -16
  171. data/templates/driver/license_mit.erb +22 -22
  172. data/templates/driver/license_reserved.erb +5 -5
  173. data/templates/driver/tailor.erb +4 -4
  174. data/templates/driver/travis.yml.erb +11 -11
  175. data/templates/driver/version.rb.erb +12 -12
  176. data/templates/init/chefignore.erb +1 -1
  177. data/templates/init/kitchen.yml.erb +18 -18
  178. data/test-kitchen.gemspec +62 -62
  179. data/test/integration/default/default_spec.rb +3 -3
  180. data/testing_windows.md +37 -37
  181. metadata +5 -4
@@ -1,142 +1,142 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
- #
5
- # Copyright (C) 2013, Fletcher Nichol
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
-
19
- require "delegate"
20
-
21
- module Kitchen
22
-
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"
53
- #
54
- # @author Fletcher Nichol <fnichol@nichol.ca>
55
- class LazyHash < SimpleDelegator
56
- include Enumerable
57
-
58
- # Creates a new LazyHash using a Hash-like object to populate itself and
59
- # an object that can be used as context in value-callable blocks. The
60
- # context object can be used to compute values for keys at the time of
61
- # fetching the value.
62
- #
63
- # @param obj [Hash, Object] a hash-like object
64
- # @param context [Object] an object that can be used to compute values
65
- def initialize(obj, context)
66
- @context = context
67
- super(obj)
68
- end
69
-
70
- # Retrieves the rendered value object corresponding to the key object. If
71
- # not found, returns the default value.
72
- #
73
- # @param key [Object] hash key
74
- # @return [Object, nil] the value for key or the default value if key is
75
- # not found
76
- def [](key)
77
- proc_or_val(__getobj__[key])
78
- end
79
-
80
- # Returns a rendered value from the hash for the given key. If the key
81
- # can't be found, there are several options: With no other arguments, it
82
- # will raise an KeyError exception; if default is given, then that will be
83
- # returned; if the optional code block is specified, then that will be run
84
- # and its result returned.
85
- #
86
- # @param key [Object] hash key
87
- # @param default [Object] default value if key is not set (optional)
88
- # @return [Object, nil] the value for the key or the default value if key
89
- # is not found
90
- # @raise [KeyError] if the key is not found
91
- def fetch(key, default = :__undefined__, &block)
92
- case default
93
- when :__undefined__
94
- proc_or_val(__getobj__.fetch(key, &block))
95
- else
96
- proc_or_val(__getobj__.fetch(key, default, &block))
97
- end
98
- end
99
-
100
- # Returns a new Hash with all keys and rendered values of the LazyHash.
101
- #
102
- # @return [Hash] a new hash
103
- def to_hash
104
- hash = Hash.new
105
- __getobj__.keys.each { |key| hash[key] = self[key] }
106
- hash
107
- end
108
-
109
- # Yields each key/value pair to the provided block. Returns a new
110
- # Hash with only the keys and rendered values for which the block
111
- # returns true.
112
- #
113
- # @return [Hash] a new hash
114
- def select(&block)
115
- to_hash.select(&block)
116
- end
117
-
118
- # If no block provided, returns an enumerator over the keys and
119
- # rendered values in the underlying object. If a block is
120
- # provided, calls the block once for each [key, rendered_value]
121
- # pair in the underlying object.
122
- #
123
- # @return [Enumerator, Array]
124
- def each(&block)
125
- to_hash.each(&block)
126
- end
127
-
128
- private
129
-
130
- # Returns an object or invokes call with context if object is callable.
131
- #
132
- # @return [Object] an object
133
- # @api private
134
- def proc_or_val(thing)
135
- if thing.respond_to?(:call)
136
- thing.call(@context)
137
- else
138
- thing
139
- end
140
- end
141
- end
142
- end
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "delegate"
20
+
21
+ module Kitchen
22
+
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"
53
+ #
54
+ # @author Fletcher Nichol <fnichol@nichol.ca>
55
+ class LazyHash < SimpleDelegator
56
+ include Enumerable
57
+
58
+ # Creates a new LazyHash using a Hash-like object to populate itself and
59
+ # an object that can be used as context in value-callable blocks. The
60
+ # context object can be used to compute values for keys at the time of
61
+ # fetching the value.
62
+ #
63
+ # @param obj [Hash, Object] a hash-like object
64
+ # @param context [Object] an object that can be used to compute values
65
+ def initialize(obj, context)
66
+ @context = context
67
+ super(obj)
68
+ end
69
+
70
+ # Retrieves the rendered value object corresponding to the key object. If
71
+ # not found, returns the default value.
72
+ #
73
+ # @param key [Object] hash key
74
+ # @return [Object, nil] the value for key or the default value if key is
75
+ # not found
76
+ def [](key)
77
+ proc_or_val(__getobj__[key])
78
+ end
79
+
80
+ # Returns a rendered value from the hash for the given key. If the key
81
+ # can't be found, there are several options: With no other arguments, it
82
+ # will raise an KeyError exception; if default is given, then that will be
83
+ # returned; if the optional code block is specified, then that will be run
84
+ # and its result returned.
85
+ #
86
+ # @param key [Object] hash key
87
+ # @param default [Object] default value if key is not set (optional)
88
+ # @return [Object, nil] the value for the key or the default value if key
89
+ # is not found
90
+ # @raise [KeyError] if the key is not found
91
+ def fetch(key, default = :__undefined__, &block)
92
+ case default
93
+ when :__undefined__
94
+ proc_or_val(__getobj__.fetch(key, &block))
95
+ else
96
+ proc_or_val(__getobj__.fetch(key, default, &block))
97
+ end
98
+ end
99
+
100
+ # Returns a new Hash with all keys and rendered values of the LazyHash.
101
+ #
102
+ # @return [Hash] a new hash
103
+ def to_hash
104
+ hash = Hash.new
105
+ __getobj__.keys.each { |key| hash[key] = self[key] }
106
+ hash
107
+ end
108
+
109
+ # Yields each key/value pair to the provided block. Returns a new
110
+ # Hash with only the keys and rendered values for which the block
111
+ # returns true.
112
+ #
113
+ # @return [Hash] a new hash
114
+ def select(&block)
115
+ to_hash.select(&block)
116
+ end
117
+
118
+ # If no block provided, returns an enumerator over the keys and
119
+ # rendered values in the underlying object. If a block is
120
+ # provided, calls the block once for each [key, rendered_value]
121
+ # pair in the underlying object.
122
+ #
123
+ # @return [Enumerator, Array]
124
+ def each(&block)
125
+ to_hash.each(&block)
126
+ end
127
+
128
+ private
129
+
130
+ # Returns an object or invokes call with context if object is callable.
131
+ #
132
+ # @return [Object] an object
133
+ # @api private
134
+ def proc_or_val(thing)
135
+ if thing.respond_to?(:call)
136
+ thing.call(@context)
137
+ else
138
+ thing
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,349 +1,349 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
- #
5
- # Copyright (C) 2013, Fletcher Nichol
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
-
19
- require "erb"
20
- require "vendor/hash_recursive_merge"
21
-
22
- if RUBY_VERSION <= "1.9.3"
23
- # ensure that Psych and not Syck is used for Ruby 1.9.2
24
- require "yaml"
25
- YAML::ENGINE.yamler = "psych"
26
- end
27
- require "safe_yaml/load"
28
-
29
- module Kitchen
30
-
31
- module Loader
32
-
33
- # YAML file loader for Test Kitchen configuration. This class is
34
- # responisble for parsing the main YAML file and the local YAML if it
35
- # exists. Local file configuration will win over the default configuration.
36
- # The client of this class should not require any YAML loading or parsing
37
- # logic.
38
- #
39
- # @author Fletcher Nichol <fnichol@nichol.ca>
40
- class YAML
41
-
42
- # Creates a new loader that can parse and load YAML files.
43
- #
44
- # @param options [Hash] configuration for a new loader
45
- # @option options [String] :project_config path to the Kitchen
46
- # config YAML file (default: `./.kitchen.yml`)
47
- # @option options [String] :local_config path to the Kitchen local
48
- # config YAML file (default: `./.kitchen.local.yml`)
49
- # @option options [String] :global_config path to the Kitchen global
50
- # config YAML file (default: `$HOME/.kitchen/config.yml`)
51
- # @option options [String] :process_erb whether or not to process YAML
52
- # through an ERB processor (default: `true`)
53
- # @option options [String] :process_local whether or not to process a
54
- # local kitchen YAML file, if it exists (default: `true`)
55
- def initialize(options = {})
56
- @config_file =
57
- File.expand_path(options[:project_config] || default_config_file)
58
- @local_config_file =
59
- File.expand_path(options[:local_config] || default_local_config_file)
60
- @global_config_file =
61
- File.expand_path(options[:global_config] || default_global_config_file)
62
-
63
- @process_erb = options.fetch(:process_erb, true)
64
- @process_local = options.fetch(:process_local, true)
65
- @process_global = options.fetch(:process_global, true)
66
- end
67
-
68
- # Reads, parses, and merges YAML configuration files and returns a Hash
69
- # of tne merged data.
70
- #
71
- # @return [Hash] merged configuration data
72
- def read
73
- if !File.exist?(config_file)
74
- raise UserError, "Kitchen YAML file #{config_file} does not exist."
75
- end
76
-
77
- Util.symbolized_hash(combined_hash)
78
- end
79
-
80
- # Returns a Hash of configuration and other useful diagnostic information.
81
- #
82
- # @return [Hash] a diagnostic hash
83
- def diagnose
84
- result = Hash.new
85
- result[:process_erb] = @process_erb
86
- result[:process_local] = @process_local
87
- result[:process_global] = @process_global
88
- result[:global_config] = diagnose_component(:global_yaml, global_config_file)
89
- result[:project_config] = diagnose_component(:yaml, config_file)
90
- result[:local_config] = diagnose_component(:local_yaml, local_config_file)
91
- result[:combined_config] = diagnose_component(:combined_hash)
92
- result
93
- end
94
-
95
- private
96
-
97
- # @return [String] the absolute path to the Kitchen config YAML file
98
- # @api private
99
- attr_reader :config_file
100
-
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
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
124
- def combined_hash
125
- y = if @process_global
126
- normalize(global_yaml).rmerge(normalize(yaml))
127
- else
128
- normalize(yaml)
129
- end
130
- @process_local ? y.rmerge(normalize(local_yaml)) : y
131
- end
132
-
133
- # Loads and returns the Kitchen config YAML as a Hash.
134
- #
135
- # @return [Hash] the config hash
136
- # @api private
137
- def yaml
138
- parse_yaml_string(yaml_string(config_file), config_file)
139
- end
140
-
141
- # Loads and returns the Kitchen local config YAML as a Hash.
142
- #
143
- # @return [Hash] the config hash
144
- # @api private
145
- def local_yaml
146
- parse_yaml_string(yaml_string(local_config_file), local_config_file)
147
- end
148
-
149
- # Loads and returns the Kitchen global config YAML as a Hash.
150
- #
151
- # @return [Hash] the config hash
152
- # @api private
153
- def global_yaml
154
- parse_yaml_string(yaml_string(global_config_file), global_config_file)
155
- end
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
162
- def yaml_string(file)
163
- string = read_file(file)
164
-
165
- @process_erb ? process_erb(string, file) : string
166
- end
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
176
- def process_erb(string, file)
177
- tpl = ERB.new(string)
178
- tpl.filename = file
179
- tpl.result
180
- rescue => e
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 " \
184
- "debug your issue."
185
- end
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
193
- def read_file(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")
204
- end
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
211
- def default_local_config_file
212
- config_file.sub(/(#{File.extname(config_file)})$/, '.local\1')
213
- end
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
220
- def default_global_config_file
221
- File.join(File.expand_path(ENV["HOME"]), ".kitchen", "config.yml")
222
- end
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
233
- def diagnose_component(component, file = nil)
234
- return if file && !File.exist?(file)
235
-
236
- hash = begin
237
- send(component)
238
- rescue => e
239
- failure_hash(e, file)
240
- end
241
-
242
- { :filename => file, :raw_data => hash }
243
- end
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
251
- def failure_hash(e, file = nil)
252
- result = {
253
- :error => {
254
- :exception => e.inspect,
255
- :message => e.message,
256
- :backtrace => e.backtrace
257
- }
258
- }
259
- result[:error][:raw_file] = IO.read(file) unless file.nil?
260
- result
261
- end
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
269
- def normalize(obj)
270
- if obj.is_a?(Hash)
271
- obj.inject(Hash.new) { |h, (k, v)| normalize_hash(h, k, v); h }
272
- else
273
- obj
274
- end
275
- end
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
307
- def normalize_hash(hash, key, value)
308
- case key
309
- when "driver", "provisioner", "busser"
310
- hash[key] = if value.nil?
311
- Hash.new
312
- elsif value.is_a?(String)
313
- default_key = key == "busser" ? "version" : "name"
314
- { default_key => value }
315
- else
316
- normalize(value)
317
- end
318
- else
319
- hash[key] = normalize(value)
320
- end
321
- end
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
331
- def parse_yaml_string(string, file_name)
332
- return Hash.new if string.nil? || string.empty?
333
-
334
- result = SafeYAML.load(string) || Hash.new
335
- unless result.is_a?(Hash)
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 " \
339
- "debug your issue."
340
- end
341
- result
342
- rescue SyntaxError, Psych::SyntaxError
343
- raise UserError, "Error parsing #{file_name} as YAML.\n" \
344
- "Please run `kitchen diagnose --no-instances --loader' to help " \
345
- "debug your issue."
346
- end
347
- end
348
- end
349
- end
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "erb"
20
+ require "vendor/hash_recursive_merge"
21
+
22
+ if RUBY_VERSION <= "1.9.3"
23
+ # ensure that Psych and not Syck is used for Ruby 1.9.2
24
+ require "yaml"
25
+ YAML::ENGINE.yamler = "psych"
26
+ end
27
+ require "safe_yaml/load"
28
+
29
+ module Kitchen
30
+
31
+ module Loader
32
+
33
+ # YAML file loader for Test Kitchen configuration. This class is
34
+ # responisble for parsing the main YAML file and the local YAML if it
35
+ # exists. Local file configuration will win over the default configuration.
36
+ # The client of this class should not require any YAML loading or parsing
37
+ # logic.
38
+ #
39
+ # @author Fletcher Nichol <fnichol@nichol.ca>
40
+ class YAML
41
+
42
+ # Creates a new loader that can parse and load YAML files.
43
+ #
44
+ # @param options [Hash] configuration for a new loader
45
+ # @option options [String] :project_config path to the Kitchen
46
+ # config YAML file (default: `./.kitchen.yml`)
47
+ # @option options [String] :local_config path to the Kitchen local
48
+ # config YAML file (default: `./.kitchen.local.yml`)
49
+ # @option options [String] :global_config path to the Kitchen global
50
+ # config YAML file (default: `$HOME/.kitchen/config.yml`)
51
+ # @option options [String] :process_erb whether or not to process YAML
52
+ # through an ERB processor (default: `true`)
53
+ # @option options [String] :process_local whether or not to process a
54
+ # local kitchen YAML file, if it exists (default: `true`)
55
+ def initialize(options = {})
56
+ @config_file =
57
+ File.expand_path(options[:project_config] || default_config_file)
58
+ @local_config_file =
59
+ File.expand_path(options[:local_config] || default_local_config_file)
60
+ @global_config_file =
61
+ File.expand_path(options[:global_config] || default_global_config_file)
62
+
63
+ @process_erb = options.fetch(:process_erb, true)
64
+ @process_local = options.fetch(:process_local, true)
65
+ @process_global = options.fetch(:process_global, true)
66
+ end
67
+
68
+ # Reads, parses, and merges YAML configuration files and returns a Hash
69
+ # of tne merged data.
70
+ #
71
+ # @return [Hash] merged configuration data
72
+ def read
73
+ if !File.exist?(config_file)
74
+ raise UserError, "Kitchen YAML file #{config_file} does not exist."
75
+ end
76
+
77
+ Util.symbolized_hash(combined_hash)
78
+ end
79
+
80
+ # Returns a Hash of configuration and other useful diagnostic information.
81
+ #
82
+ # @return [Hash] a diagnostic hash
83
+ def diagnose
84
+ result = Hash.new
85
+ result[:process_erb] = @process_erb
86
+ result[:process_local] = @process_local
87
+ result[:process_global] = @process_global
88
+ result[:global_config] = diagnose_component(:global_yaml, global_config_file)
89
+ result[:project_config] = diagnose_component(:yaml, config_file)
90
+ result[:local_config] = diagnose_component(:local_yaml, local_config_file)
91
+ result[:combined_config] = diagnose_component(:combined_hash)
92
+ result
93
+ end
94
+
95
+ private
96
+
97
+ # @return [String] the absolute path to the Kitchen config YAML file
98
+ # @api private
99
+ attr_reader :config_file
100
+
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
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
124
+ def combined_hash
125
+ y = if @process_global
126
+ normalize(global_yaml).rmerge(normalize(yaml))
127
+ else
128
+ normalize(yaml)
129
+ end
130
+ @process_local ? y.rmerge(normalize(local_yaml)) : y
131
+ end
132
+
133
+ # Loads and returns the Kitchen config YAML as a Hash.
134
+ #
135
+ # @return [Hash] the config hash
136
+ # @api private
137
+ def yaml
138
+ parse_yaml_string(yaml_string(config_file), config_file)
139
+ end
140
+
141
+ # Loads and returns the Kitchen local config YAML as a Hash.
142
+ #
143
+ # @return [Hash] the config hash
144
+ # @api private
145
+ def local_yaml
146
+ parse_yaml_string(yaml_string(local_config_file), local_config_file)
147
+ end
148
+
149
+ # Loads and returns the Kitchen global config YAML as a Hash.
150
+ #
151
+ # @return [Hash] the config hash
152
+ # @api private
153
+ def global_yaml
154
+ parse_yaml_string(yaml_string(global_config_file), global_config_file)
155
+ end
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
162
+ def yaml_string(file)
163
+ string = read_file(file)
164
+
165
+ @process_erb ? process_erb(string, file) : string
166
+ end
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
176
+ def process_erb(string, file)
177
+ tpl = ERB.new(string)
178
+ tpl.filename = file
179
+ tpl.result
180
+ rescue => e
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 " \
184
+ "debug your issue."
185
+ end
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
193
+ def read_file(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")
204
+ end
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
211
+ def default_local_config_file
212
+ config_file.sub(/(#{File.extname(config_file)})$/, '.local\1')
213
+ end
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
220
+ def default_global_config_file
221
+ File.join(File.expand_path(ENV["HOME"]), ".kitchen", "config.yml")
222
+ end
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
233
+ def diagnose_component(component, file = nil)
234
+ return if file && !File.exist?(file)
235
+
236
+ hash = begin
237
+ send(component)
238
+ rescue => e
239
+ failure_hash(e, file)
240
+ end
241
+
242
+ { :filename => file, :raw_data => hash }
243
+ end
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
251
+ def failure_hash(e, file = nil)
252
+ result = {
253
+ :error => {
254
+ :exception => e.inspect,
255
+ :message => e.message,
256
+ :backtrace => e.backtrace
257
+ }
258
+ }
259
+ result[:error][:raw_file] = IO.read(file) unless file.nil?
260
+ result
261
+ end
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
269
+ def normalize(obj)
270
+ if obj.is_a?(Hash)
271
+ obj.inject(Hash.new) { |h, (k, v)| normalize_hash(h, k, v); h }
272
+ else
273
+ obj
274
+ end
275
+ end
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
307
+ def normalize_hash(hash, key, value)
308
+ case key
309
+ when "driver", "provisioner", "busser"
310
+ hash[key] = if value.nil?
311
+ Hash.new
312
+ elsif value.is_a?(String)
313
+ default_key = key == "busser" ? "version" : "name"
314
+ { default_key => value }
315
+ else
316
+ normalize(value)
317
+ end
318
+ else
319
+ hash[key] = normalize(value)
320
+ end
321
+ end
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
331
+ def parse_yaml_string(string, file_name)
332
+ return Hash.new if string.nil? || string.empty?
333
+
334
+ result = SafeYAML.load(string) || Hash.new
335
+ unless result.is_a?(Hash)
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 " \
339
+ "debug your issue."
340
+ end
341
+ result
342
+ rescue SyntaxError, Psych::SyntaxError
343
+ raise UserError, "Error parsing #{file_name} as YAML.\n" \
344
+ "Please run `kitchen diagnose --no-instances --loader' to help " \
345
+ "debug your issue."
346
+ end
347
+ end
348
+ end
349
+ end