test-kitchen 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +8 -7
  3. data/.github/ISSUE_TEMPLATE.md +56 -0
  4. data/.gitignore +28 -27
  5. data/.kitchen.ci.yml +23 -0
  6. data/.kitchen.proxy.yml +27 -0
  7. data/.rubocop.yml +3 -3
  8. data/.travis.yml +70 -53
  9. data/.yardopts +3 -3
  10. data/Berksfile +3 -0
  11. data/CHANGELOG.md +1083 -1051
  12. data/CONTRIBUTING.md +14 -14
  13. data/Gemfile +19 -14
  14. data/Gemfile.proxy_tests +4 -5
  15. data/Guardfile +42 -42
  16. data/LICENSE +15 -15
  17. data/MAINTAINERS.md +23 -24
  18. data/README.md +135 -135
  19. data/Rakefile +61 -76
  20. data/appveyor.yml +44 -34
  21. data/features/kitchen_action_commands.feature +164 -164
  22. data/features/kitchen_command.feature +16 -16
  23. data/features/kitchen_console_command.feature +34 -34
  24. data/features/kitchen_defaults.feature +38 -38
  25. data/features/kitchen_diagnose_command.feature +96 -96
  26. data/features/kitchen_driver_create_command.feature +64 -64
  27. data/features/kitchen_driver_discover_command.feature +25 -25
  28. data/features/kitchen_help_command.feature +16 -16
  29. data/features/kitchen_init_command.feature +274 -274
  30. data/features/kitchen_list_command.feature +104 -104
  31. data/features/kitchen_login_command.feature +62 -62
  32. data/features/kitchen_sink_command.feature +30 -30
  33. data/features/kitchen_test_command.feature +88 -88
  34. data/features/step_definitions/gem_steps.rb +36 -36
  35. data/features/step_definitions/git_steps.rb +5 -5
  36. data/features/step_definitions/output_steps.rb +5 -5
  37. data/features/support/env.rb +75 -75
  38. data/lib/kitchen.rb +150 -150
  39. data/lib/kitchen/base64_stream.rb +55 -55
  40. data/lib/kitchen/cli.rb +419 -419
  41. data/lib/kitchen/collection.rb +55 -55
  42. data/lib/kitchen/color.rb +65 -65
  43. data/lib/kitchen/command.rb +185 -185
  44. data/lib/kitchen/command/action.rb +45 -45
  45. data/lib/kitchen/command/console.rb +58 -58
  46. data/lib/kitchen/command/diagnose.rb +92 -92
  47. data/lib/kitchen/command/driver_discover.rb +105 -105
  48. data/lib/kitchen/command/exec.rb +41 -41
  49. data/lib/kitchen/command/list.rb +119 -119
  50. data/lib/kitchen/command/login.rb +43 -43
  51. data/lib/kitchen/command/sink.rb +54 -54
  52. data/lib/kitchen/command/test.rb +51 -51
  53. data/lib/kitchen/config.rb +322 -322
  54. data/lib/kitchen/configurable.rb +529 -529
  55. data/lib/kitchen/data_munger.rb +959 -960
  56. data/lib/kitchen/diagnostic.rb +141 -141
  57. data/lib/kitchen/driver.rb +56 -56
  58. data/lib/kitchen/driver/base.rb +134 -134
  59. data/lib/kitchen/driver/dummy.rb +108 -108
  60. data/lib/kitchen/driver/proxy.rb +72 -72
  61. data/lib/kitchen/driver/ssh_base.rb +357 -357
  62. data/lib/kitchen/errors.rb +229 -229
  63. data/lib/kitchen/generator/driver_create.rb +177 -177
  64. data/lib/kitchen/generator/init.rb +296 -296
  65. data/lib/kitchen/instance.rb +662 -662
  66. data/lib/kitchen/lazy_hash.rb +142 -142
  67. data/lib/kitchen/loader/yaml.rb +349 -349
  68. data/lib/kitchen/logger.rb +423 -423
  69. data/lib/kitchen/logging.rb +56 -56
  70. data/lib/kitchen/login_command.rb +52 -52
  71. data/lib/kitchen/metadata_chopper.rb +52 -52
  72. data/lib/kitchen/platform.rb +67 -67
  73. data/lib/kitchen/provisioner.rb +54 -54
  74. data/lib/kitchen/provisioner/base.rb +236 -236
  75. data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
  76. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
  77. data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
  78. data/lib/kitchen/provisioner/chef_apply.rb +124 -125
  79. data/lib/kitchen/provisioner/chef_base.rb +341 -294
  80. data/lib/kitchen/provisioner/chef_solo.rb +88 -89
  81. data/lib/kitchen/provisioner/chef_zero.rb +245 -245
  82. data/lib/kitchen/provisioner/dummy.rb +79 -79
  83. data/lib/kitchen/provisioner/shell.rb +138 -138
  84. data/lib/kitchen/rake_tasks.rb +63 -63
  85. data/lib/kitchen/shell_out.rb +93 -93
  86. data/lib/kitchen/ssh.rb +276 -276
  87. data/lib/kitchen/state_file.rb +120 -120
  88. data/lib/kitchen/suite.rb +51 -51
  89. data/lib/kitchen/thor_tasks.rb +66 -66
  90. data/lib/kitchen/transport.rb +54 -54
  91. data/lib/kitchen/transport/base.rb +176 -176
  92. data/lib/kitchen/transport/dummy.rb +79 -79
  93. data/lib/kitchen/transport/ssh.rb +364 -364
  94. data/lib/kitchen/transport/winrm.rb +486 -486
  95. data/lib/kitchen/util.rb +147 -147
  96. data/lib/kitchen/verifier.rb +55 -55
  97. data/lib/kitchen/verifier/base.rb +235 -235
  98. data/lib/kitchen/verifier/busser.rb +277 -277
  99. data/lib/kitchen/verifier/dummy.rb +79 -79
  100. data/lib/kitchen/verifier/shell.rb +101 -101
  101. data/lib/kitchen/version.rb +21 -21
  102. data/lib/vendor/hash_recursive_merge.rb +82 -82
  103. data/spec/kitchen/base64_stream_spec.rb +77 -77
  104. data/spec/kitchen/cli_spec.rb +56 -56
  105. data/spec/kitchen/collection_spec.rb +80 -80
  106. data/spec/kitchen/color_spec.rb +54 -54
  107. data/spec/kitchen/config_spec.rb +408 -408
  108. data/spec/kitchen/configurable_spec.rb +1095 -1062
  109. data/spec/kitchen/data_munger_spec.rb +2694 -2383
  110. data/spec/kitchen/diagnostic_spec.rb +129 -129
  111. data/spec/kitchen/driver/base_spec.rb +121 -121
  112. data/spec/kitchen/driver/dummy_spec.rb +199 -199
  113. data/spec/kitchen/driver/proxy_spec.rb +138 -138
  114. data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
  115. data/spec/kitchen/driver_spec.rb +112 -112
  116. data/spec/kitchen/errors_spec.rb +309 -309
  117. data/spec/kitchen/instance_spec.rb +1419 -1419
  118. data/spec/kitchen/lazy_hash_spec.rb +117 -117
  119. data/spec/kitchen/loader/yaml_spec.rb +774 -774
  120. data/spec/kitchen/logger_spec.rb +429 -429
  121. data/spec/kitchen/logging_spec.rb +59 -59
  122. data/spec/kitchen/login_command_spec.rb +68 -68
  123. data/spec/kitchen/metadata_chopper_spec.rb +82 -82
  124. data/spec/kitchen/platform_spec.rb +89 -89
  125. data/spec/kitchen/provisioner/base_spec.rb +386 -386
  126. data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
  127. data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1067
  128. data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
  129. data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
  130. data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
  131. data/spec/kitchen/provisioner/shell_spec.rb +566 -566
  132. data/spec/kitchen/provisioner_spec.rb +107 -107
  133. data/spec/kitchen/shell_out_spec.rb +150 -150
  134. data/spec/kitchen/ssh_spec.rb +693 -693
  135. data/spec/kitchen/state_file_spec.rb +129 -129
  136. data/spec/kitchen/suite_spec.rb +62 -62
  137. data/spec/kitchen/transport/base_spec.rb +89 -89
  138. data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
  139. data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
  140. data/spec/kitchen/transport_spec.rb +112 -112
  141. data/spec/kitchen/util_spec.rb +165 -165
  142. data/spec/kitchen/verifier/base_spec.rb +362 -362
  143. data/spec/kitchen/verifier/busser_spec.rb +610 -610
  144. data/spec/kitchen/verifier/dummy_spec.rb +99 -99
  145. data/spec/kitchen/verifier/shell_spec.rb +160 -158
  146. data/spec/kitchen/verifier_spec.rb +120 -120
  147. data/spec/kitchen_spec.rb +114 -114
  148. data/spec/spec_helper.rb +85 -85
  149. data/spec/support/powershell_max_size_spec.rb +40 -40
  150. data/support/busser_install_command.ps1 +14 -14
  151. data/support/busser_install_command.sh +14 -14
  152. data/support/chef-client-zero.rb +77 -77
  153. data/support/chef_base_init_command.ps1 +18 -18
  154. data/support/chef_base_init_command.sh +2 -2
  155. data/support/chef_base_install_command.ps1 +85 -85
  156. data/support/chef_base_install_command.sh +229 -229
  157. data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
  158. data/support/chef_zero_prepare_command_legacy.sh +10 -10
  159. data/support/download_helpers.sh +109 -109
  160. data/support/dummy-validation.pem +27 -27
  161. data/templates/driver/CHANGELOG.md.erb +3 -3
  162. data/templates/driver/Gemfile.erb +3 -3
  163. data/templates/driver/README.md.erb +64 -64
  164. data/templates/driver/Rakefile.erb +21 -21
  165. data/templates/driver/driver.rb.erb +23 -23
  166. data/templates/driver/gemspec.erb +29 -29
  167. data/templates/driver/gitignore.erb +17 -17
  168. data/templates/driver/license_apachev2.erb +15 -15
  169. data/templates/driver/license_lgplv3.erb +16 -16
  170. data/templates/driver/license_mit.erb +22 -22
  171. data/templates/driver/license_reserved.erb +5 -5
  172. data/templates/driver/tailor.erb +4 -4
  173. data/templates/driver/travis.yml.erb +11 -11
  174. data/templates/driver/version.rb.erb +12 -12
  175. data/templates/init/chefignore.erb +1 -1
  176. data/templates/init/kitchen.yml.erb +18 -18
  177. data/test-kitchen.gemspec +62 -62
  178. data/test/integration/default/default_spec.rb +3 -0
  179. data/testing_windows.md +37 -37
  180. metadata +23 -11
@@ -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