test-kitchen 1.4.2 → 1.5.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +27 -2
  4. data/CHANGELOG.md +40 -1
  5. data/Gemfile.proxy_tests +6 -0
  6. data/features/kitchen_driver_discover_command.feature +6 -0
  7. data/features/kitchen_init_command.feature +5 -3
  8. data/features/step_definitions/gem_steps.rb +12 -4
  9. data/features/step_definitions/git_steps.rb +1 -1
  10. data/features/step_definitions/output_steps.rb +1 -1
  11. data/features/support/env.rb +13 -9
  12. data/lib/kitchen/cli.rb +3 -0
  13. data/lib/kitchen/command/driver_discover.rb +8 -0
  14. data/lib/kitchen/configurable.rb +32 -0
  15. data/lib/kitchen/driver/ssh_base.rb +18 -4
  16. data/lib/kitchen/generator/driver_create.rb +2 -2
  17. data/lib/kitchen/lazy_hash.rb +20 -0
  18. data/lib/kitchen/provisioner/base.rb +16 -1
  19. data/lib/kitchen/provisioner/chef_base.rb +42 -98
  20. data/lib/kitchen/provisioner/chef_solo.rb +6 -4
  21. data/lib/kitchen/provisioner/chef_zero.rb +7 -5
  22. data/lib/kitchen/transport/winrm.rb +36 -2
  23. data/lib/kitchen/verifier/base.rb +18 -1
  24. data/lib/kitchen/verifier/busser.rb +12 -5
  25. data/lib/kitchen/verifier/shell.rb +101 -0
  26. data/lib/kitchen/version.rb +1 -1
  27. data/spec/kitchen/configurable_spec.rb +211 -2
  28. data/spec/kitchen/driver/ssh_base_spec.rb +131 -8
  29. data/spec/kitchen/lazy_hash_spec.rb +27 -0
  30. data/spec/kitchen/provisioner/base_spec.rb +25 -0
  31. data/spec/kitchen/provisioner/chef_base_spec.rb +133 -252
  32. data/spec/kitchen/provisioner/chef_solo_spec.rb +17 -0
  33. data/spec/kitchen/provisioner/chef_zero_spec.rb +14 -2
  34. data/spec/kitchen/provisioner/shell_spec.rb +60 -12
  35. data/spec/kitchen/transport/winrm_spec.rb +50 -0
  36. data/spec/kitchen/verifier/base_spec.rb +26 -0
  37. data/spec/kitchen/verifier/busser_spec.rb +69 -3
  38. data/spec/kitchen/verifier/shell_spec.rb +157 -0
  39. data/support/busser_install_command.sh +1 -2
  40. data/test-kitchen.gemspec +10 -2
  41. metadata +86 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed6240c00a8d6ca35986ab16735134c1fe4368b1
4
- data.tar.gz: 3b4324d554794093cc15ebd2f82e9bca3c426074
3
+ metadata.gz: adede1e7727afe32740d4f4b60452b704f4326ee
4
+ data.tar.gz: cfd3c1e7ea3517a9d9bbdc8b3b1e135dd546f26b
5
5
  SHA512:
6
- metadata.gz: d1949f7688de24d288aa21d15f4d1bc80dc049937b60ab56e3ac445211e7c68c57fcb7c2b1f3fc93fa25e341f2dd9907494ecdcfcda4518dd2939cbed22fec3f
7
- data.tar.gz: a8713a783d3ff9b2dff382e790a1496ec75b1f305bab75b02d0a5a36f505995c4b998f2a0a057d6d7443371757b50d69aa732b1d16a467211378500b021836d3
6
+ metadata.gz: 3d3dfad8e69eb236fd38ca05e233245342140823b94cf9d7cea0b56f5ec6bbae1aba59494c728fe768dc29e51fd3ee817e764836751e1187f996f6566385b758
7
+ data.tar.gz: 4cb4852bb1977639bbb5068420ba26c170f5594d1e21b12be1dec4c6cd6a464ab7426640324066e7545b768c8865d401d1a5d6969f97fc2cdb200fd064ad6020
data/.gitignore CHANGED
@@ -3,7 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
6
+ Gemfile*.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
data/.travis.yml CHANGED
@@ -1,10 +1,9 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.2
4
+ - 2.2.3
5
5
  - 2.1
6
6
  - 2.0.0
7
- - 1.9.3
8
7
  - ruby-head
9
8
 
10
9
  env:
@@ -24,6 +23,32 @@ bundler_args: --without guard
24
23
  sudo: false
25
24
 
26
25
  matrix:
26
+ include:
27
+ - rvm: 2.2
28
+ sudo: required
29
+ dist: trusty
30
+ # To run the proxy tests we need additional gems than what Test Kitchen normally uses
31
+ # for testing
32
+ gemfile: Gemfile.proxy_tests
33
+ before_install:
34
+ - sudo apt-get update
35
+ - sudo apt-get -y install squid3 git curl
36
+ env:
37
+ # ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']
38
+ - secure: "E7h427Y8z6Nx5sdrA4Ye9gO9pDGpe4jxfhJdM4HPJIM/FSkEJM0ZDPdvhQ6QAQ1S1AHja/JhQUFaNP5NzKhRkVqGCzunBBC6RVb7pp1Qo16EMhJkg7LEsExiSsjQykAzt2w6K/71V+mIlMKxCPYdpu6PT/8w/jZFgBqLEHXuogU="
39
+ - global:
40
+ - PROXY_TESTS_DIR=proxy_tests/files/default/scripts
41
+ - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo
42
+
43
+ script:
44
+ - bundle exec kitchen --version
45
+ # TODO switch to master when https://github.com/chef/proxy_tests/pull/12 is merged
46
+ - git clone https://github.com/chef/proxy_tests.git
47
+ - rvmsudo -E bundle exec bash $PROXY_TESTS_DIR/run_tests.sh kitchen \* \* /tmp/out.txt
48
+ after_script:
49
+ - cat /tmp/out.txt
50
+ - sudo cat /var/log/squid3/cache.log
51
+ - sudo cat /var/log/squid3/access.log
27
52
  allow_failures:
28
53
  - rvm: ruby-head
29
54
  exclude:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,29 @@
1
- ## 1.4.2
1
+ ## 1.5.0
2
+
3
+ ### Potentially breaking changes
4
+
5
+ ### Bug fixes
6
+
7
+ * PR [#816][]: Fix SuSe OS Busser install ([@Peuserik][])
8
+ * PR [#833][]: Updates the gem path to install everything in /tmp/verifier ([@scotthain][])
9
+ * PR [#878][]: write install_command to file and invoke on the instance to avoid command too long on windows ([@mwrock][])
10
+
11
+ ### New features
12
+
13
+ * PR [#741][]: Add shell verifier ([@sawanoboly][])
14
+ * PR [#752][]: Make lazyhash enumerable ([@caboteria][])
15
+ * PR [#892][]: Adding proxy tests to the Travis.yml ([@tyler-ball][])
16
+ * PR [#895][]: Adding in ChefConfig support to enable loading proxy config from chef config files ([@tyler-ball][])
17
+
18
+ ### Improvements
19
+
20
+ * PR [#782][]: Use [`mixlib-install`](https://github.com/chef/mixlib-install) to generate chef install command. ([@thommay][])
21
+ * PR [#804][]: Drop Ruby 1.9 from TravisCI build matrix ([@thommay][])
22
+ * PR [#813][]: Honor proxy ENV variables ([@mcquin][])
23
+ * PR [#885][]: Running the chef_base provisioner install_command via sudo, and command_prefix support ([@adamleff][])
24
+ * PR [#896][]: Fixing garbled output for chef_zero provisioner ([@someara][])
25
+
26
+ ## 1.4.2 / 2015-08-03
2
27
 
3
28
  ### Potentially breaking changes
4
29
 
@@ -769,12 +794,26 @@ The initial release.
769
794
  [#734]: https://github.com/test-kitchen/test-kitchen/issues/734
770
795
  [#736]: https://github.com/test-kitchen/test-kitchen/issues/736
771
796
  [#737]: https://github.com/test-kitchen/test-kitchen/issues/737
797
+ [#741]: https://github.com/test-kitchen/test-kitchen/issues/741
798
+ [#752]: https://github.com/test-kitchen/test-kitchen/issues/752
799
+ [#782]: https://github.com/test-kitchen/test-kitchen/issues/782
772
800
  [#801]: https://github.com/test-kitchen/test-kitchen/issues/801
773
801
  [#802]: https://github.com/test-kitchen/test-kitchen/issues/802
802
+ [#804]: https://github.com/test-kitchen/test-kitchen/issues/804
803
+ [#813]: https://github.com/test-kitchen/test-kitchen/issues/813
804
+ [#816]: https://github.com/test-kitchen/test-kitchen/issues/816
805
+ [#833]: https://github.com/test-kitchen/test-kitchen/issues/833
806
+ [#878]: https://github.com/test-kitchen/test-kitchen/issues/878
807
+ [#885]: https://github.com/test-kitchen/test-kitchen/issues/885
808
+ [#892]: https://github.com/test-kitchen/test-kitchen/issues/892
809
+ [#895]: https://github.com/test-kitchen/test-kitchen/issues/895
810
+ [#896]: https://github.com/test-kitchen/test-kitchen/issues/896
774
811
  [@Annih]: https://github.com/Annih
775
812
  [@ChrisLundquist]: https://github.com/ChrisLundquist
776
813
  [@MarkGibbons]: https://github.com/MarkGibbons
814
+ [@Peuserik]: https://github.com/Peuserik
777
815
  [@adamhjk]: https://github.com/adamhjk
816
+ [@adamleff]: https://github.com/adamleff
778
817
  [@afiune]: https://github.com/afiune
779
818
  [@arangamani]: https://github.com/arangamani
780
819
  [@arunthampi]: https://github.com/arunthampi
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+ eval_gemfile File.join(File.dirname(__FILE__), "Gemfile")
3
+
4
+ gem "kitchen-ec2"
5
+ # TODO when this is released in Chef 12.6.0 we need to depend on that
6
+ gem "chef-config", github: "chef/chef", submodules: true
@@ -17,3 +17,9 @@ Feature: Search RubyGems to discover new Test Kitchen Driver gems
17
17
  When I run `kitchen driver discover`
18
18
  Then the exit status should be 0
19
19
  And the output should contain "kitchen-bluebox"
20
+
21
+ Scenario: Running driver discover with the --chef-config-path parameter loads the chef config
22
+ Given an empty file named "kitchen_client.rb"
23
+ When I run `kitchen driver discover --chef-config-path=kitchen_client.rb`
24
+ Then the exit status should be 0
25
+ And the output should contain "kitchen-bluebox"
@@ -3,6 +3,9 @@ Feature: Add Test Kitchen support to an existing project
3
3
  As an operator
4
4
  I want to run a command to initialize my project
5
5
 
6
+ Background:
7
+ Given a sandboxed GEM_HOME directory named "kitchen-init"
8
+
6
9
  @spawn
7
10
  Scenario: Displaying help
8
11
  When I run `kitchen help init`
@@ -15,8 +18,7 @@ Feature: Add Test Kitchen support to an existing project
15
18
 
16
19
  @spawn
17
20
  Scenario: Running init with default values
18
- Given a sandboxed GEM_HOME directory named "kitchen-init"
19
- And I have a git repository
21
+ Given I have a git repository
20
22
  When I run `kitchen init`
21
23
  Then the exit status should be 0
22
24
  And a directory named "test/integration/default" should exist
@@ -173,7 +175,7 @@ Feature: Add Test Kitchen support to an existing project
173
175
  """
174
176
 
175
177
  Scenario: Running without git doesn't make a .gitignore
176
- When I successfully run `kitchen init`
178
+ When I successfully run `kitchen init --no-driver`
177
179
  Then the exit status should be 0
178
180
  And a file named ".gitignore" should not exist
179
181
 
@@ -10,19 +10,27 @@ Given(/^a sandboxed GEM_HOME directory named "(.*?)"$/) do |name|
10
10
  @aruba_timeout_seconds = 30
11
11
 
12
12
  gem_home = Pathname.new(Dir.mktmpdir(name))
13
- ENV["GEM_HOME"] = gem_home.to_s
14
- ENV["GEM_PATH"] = [gem_home.to_s, ENV["GEM_PATH"]].join(":")
13
+ aruba.environment["GEM_HOME"] = gem_home.to_s
14
+ aruba.environment["GEM_PATH"] = [gem_home.to_s, ENV["GEM_PATH"]].join(":")
15
15
  @cleanup_dirs << gem_home
16
16
  end
17
17
 
18
18
  Then(/^a gem named "(.*?)" is installed with version "(.*?)"$/) do |name, version|
19
19
  unbundlerize do
20
- run_simple(unescape("gem list #{name} --version #{version} -i"), true, nil)
20
+ run_simple(
21
+ sanitize_text("gem list #{name} --version #{version} -i"),
22
+ :fail_on_error => true,
23
+ :exit_timeout => nil
24
+ )
21
25
  end
22
26
  end
23
27
 
24
28
  Then(/^a gem named "(.*?)" is installed$/) do |name|
25
29
  unbundlerize do
26
- run_simple(unescape("gem list #{name} -i"), true, nil)
30
+ run_simple(
31
+ sanitize_text("gem list #{name} -i"),
32
+ :fail_on_error => true,
33
+ :exit_timeout => nil
34
+ )
27
35
  end
28
36
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Given(/I have a git repository/) do
4
- create_dir(".git")
4
+ create_directory(".git")
5
5
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Then(/^the stdout should match \/([^\/]*)\/$/) do |expected|
4
- assert_matching_output(expected, all_stdout)
4
+ expect(last_command_started).to have_output_on_stdout(Regexp.new(expected))
5
5
  end
@@ -31,12 +31,12 @@ Before do
31
31
  @aruba_timeout_seconds = 15
32
32
  @cleanup_dirs = []
33
33
 
34
- Aruba::Processes::InProcess.main_class = ArubaHelper
35
- Aruba.process = Aruba::Processes::InProcess
34
+ aruba.config.command_launcher = :in_process
35
+ aruba.config.main_class = ArubaHelper
36
36
  end
37
37
 
38
38
  Before("@spawn") do
39
- Aruba.process = Aruba::Processes::SpawnProcess
39
+ aruba.config.command_launcher = :spawn
40
40
  end
41
41
 
42
42
  After do |s|
@@ -47,25 +47,29 @@ After do |s|
47
47
 
48
48
  # Restore environment variables to their original settings, if they have
49
49
  # been saved off
50
- ENV.keys.select { |key| key =~ /^_CUKE_/ }.each do |backup_key|
51
- ENV[backup_key.sub(/^_CUKE_/, "")] = ENV.delete(backup_key)
52
- end
50
+ env = aruba.environment
51
+ env.to_h.keys.select { |key| key =~ /^_CUKE_/ }.
52
+ each do |backup_key|
53
+ env[backup_key.sub(/^_CUKE_/, "")] = env[backup_key]
54
+ env.delete(backup_key)
55
+ end
53
56
 
54
57
  @cleanup_dirs.each { |dir| FileUtils.rm_rf(dir) }
55
58
  end
56
59
 
57
60
  def backup_envvar(key)
58
- ENV["_CUKE_#{key}"] = ENV[key]
61
+ aruba.environment["_CUKE_#{key}"] = aruba.environment[key]
59
62
  end
60
63
 
61
64
  def restore_envvar(key)
62
- ENV[key] = ENV.delete("_CUKE_#{key}")
65
+ aruba.environment[key] = aruba.environment["_CUKE_#{key}"]
66
+ aruba.environment.delete("_CUKE_#{key}")
63
67
  end
64
68
 
65
69
  def unbundlerize
66
70
  keys = %w[BUNDLER_EDITOR BUNDLE_BIN_PATH BUNDLE_GEMFILE RUBYOPT]
67
71
 
68
- keys.each { |key| backup_envvar(key); ENV.delete(key) }
72
+ keys.each { |key| backup_envvar(key); aruba.environment.delete(key) }
69
73
  yield
70
74
  keys.each { |key| restore_envvar(key) }
71
75
  end
data/lib/kitchen/cli.rb CHANGED
@@ -287,6 +287,9 @@ module Kitchen
287
287
  guarenteed that every result is a driver, but chances are good most
288
288
  relevant drivers will be returned.
289
289
  D
290
+ method_option :chef_config_path,
291
+ :default => nil,
292
+ :desc => "Path to chef config file containing proxy configuration to use"
290
293
  def discover
291
294
  perform("discover", "driver_discover", args)
292
295
  end
@@ -19,6 +19,8 @@
19
19
  require "kitchen/command"
20
20
 
21
21
  require "rubygems/spec_fetcher"
22
+ require "chef-config/config"
23
+ require "chef-config/workstation_config_loader"
22
24
 
23
25
  module Kitchen
24
26
 
@@ -31,6 +33,12 @@ module Kitchen
31
33
 
32
34
  # Invoke the command.
33
35
  def call
36
+ # We are introducing the idea of using the Chef configuration as a
37
+ # unified config for all the ChefDK tools. The first practical
38
+ # implementation of this is 1 location to setup proxy configurations.
39
+ ChefConfig::WorkstationConfigLoader.new(options[:chef_config_path]).load
40
+ ChefConfig::Config.export_proxies
41
+
34
42
  specs = fetch_gem_specs.sort { |x, y| x[0] <=> y[0] }
35
43
  specs = specs[0, 49].push(["...", "..."]) if specs.size > 49
36
44
  specs = specs.unshift(["Gem Name", "Latest Stable Release"])
@@ -295,15 +295,36 @@ module Kitchen
295
295
  # @param code [String] the shell code to be wrapped
296
296
  # @return [String] wrapped shell code
297
297
  # @api private
298
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
299
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
298
300
  def wrap_shell_code(code)
299
301
  env = []
300
302
  if config[:http_proxy]
301
303
  env << shell_env_var("http_proxy", config[:http_proxy])
302
304
  env << shell_env_var("HTTP_PROXY", config[:http_proxy])
305
+ else
306
+ export_proxy(env, "http")
303
307
  end
304
308
  if config[:https_proxy]
305
309
  env << shell_env_var("https_proxy", config[:https_proxy])
306
310
  env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
311
+ else
312
+ export_proxy(env, "https")
313
+ end
314
+ if config[:ftp_proxy]
315
+ env << shell_env_var("ftp_proxy", config[:ftp_proxy])
316
+ env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
317
+ else
318
+ export_proxy(env, "ftp")
319
+ end
320
+ # if http_proxy was set from environment variable or https_proxy was set
321
+ # from environment variable, or ftp_proxy was set from environment
322
+ # variable, include no_proxy environment variable, if set.
323
+ if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
324
+ (!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
325
+ (!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
326
+ env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
327
+ env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
307
328
  end
308
329
  if powershell_shell?
309
330
  env.join("\n").concat("\n").concat(code)
@@ -312,6 +333,17 @@ module Kitchen
312
333
  end
313
334
  end
314
335
 
336
+ # Helper method to export
337
+ #
338
+ # @param env [Array] the environment to modify
339
+ # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
340
+ # @api private
341
+ def export_proxy(env, type)
342
+ env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
343
+ env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
344
+ ENV["#{type.upcase}_PROXY"]
345
+ end
346
+
315
347
  # Class methods which will be mixed in on inclusion of Configurable module.
316
348
  module ClassMethods
317
349
 
@@ -233,17 +233,31 @@ module Kitchen
233
233
  [combined[:hostname], combined[:username], opts]
234
234
  end
235
235
 
236
- # Adds http and https proxy environment variables to a command, if set
237
- # in configuration data.
236
+ # Adds http, https and ftp proxy environment variables to a command, if
237
+ # set in configuration data or on local workstation.
238
238
  #
239
239
  # @param cmd [String] command string
240
240
  # @return [String] command string
241
241
  # @api private
242
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
242
243
  def env_cmd(cmd)
243
244
  return if cmd.nil?
244
245
  env = "env"
245
- env << " http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
246
- env << " https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
246
+ http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
247
+ ENV["HTTP_PROXY"]
248
+ https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
249
+ ENV["HTTPS_PROXY"]
250
+ ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
251
+ ENV["FTP_PROXY"]
252
+ no_proxy = if (!config[:http_proxy] && http_proxy) ||
253
+ (!config[:https_proxy] && https_proxy) ||
254
+ (!config[:ftp_proxy] && ftp_proxy)
255
+ ENV["no_proxy"] || ENV["NO_PROXY"]
256
+ end
257
+ env << " http_proxy=#{http_proxy}" if http_proxy
258
+ env << " https_proxy=#{https_proxy}" if https_proxy
259
+ env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy
260
+ env << " no_proxy=#{no_proxy}" if no_proxy
247
261
 
248
262
  env == "env" ? cmd : "#{env} #{cmd}"
249
263
  end
@@ -87,8 +87,8 @@ module Kitchen
87
87
  # @api private
88
88
  def initialize_git
89
89
  inside(target_dir) do
90
- run("git init")
91
- run("git add .")
90
+ run("git init", :capture => true)
91
+ run("git add .", :capture => true)
92
92
  end
93
93
  end
94
94
 
@@ -53,6 +53,7 @@ module Kitchen
53
53
  #
54
54
  # @author Fletcher Nichol <fnichol@nichol.ca>
55
55
  class LazyHash < SimpleDelegator
56
+ include Enumerable
56
57
 
57
58
  # Creates a new LazyHash using a Hash-like object to populate itself and
58
59
  # an object that can be used as context in value-callable blocks. The
@@ -105,6 +106,25 @@ module Kitchen
105
106
  hash
106
107
  end
107
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
+
108
128
  private
109
129
 
110
130
  # Returns an object or invokes call with context if object is callable.