chef 15.3.14-universal-mingw32 → 15.4.45-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/chef.gemspec +2 -2
  3. data/lib/chef/application/solo.rb +1 -1
  4. data/lib/chef/event_dispatch/dispatcher.rb +9 -2
  5. data/lib/chef/formatters/doc.rb +3 -3
  6. data/lib/chef/knife.rb +13 -3
  7. data/lib/chef/knife/bootstrap.rb +28 -4
  8. data/lib/chef/knife/bootstrap/templates/chef-full.erb +7 -8
  9. data/lib/chef/knife/data_bag_secret_options.rb +11 -4
  10. data/lib/chef/knife/download.rb +2 -2
  11. data/lib/chef/knife/exec.rb +9 -1
  12. data/lib/chef/knife/ssh.rb +1 -1
  13. data/lib/chef/knife/ssl_check.rb +1 -1
  14. data/lib/chef/knife/supermarket_list.rb +19 -7
  15. data/lib/chef/knife/supermarket_search.rb +3 -2
  16. data/lib/chef/node/attribute.rb +2 -0
  17. data/lib/chef/node/attribute_collections.rb +8 -0
  18. data/lib/chef/node/immutable_collections.rb +12 -0
  19. data/lib/chef/node/mixin/immutablize_array.rb +1 -0
  20. data/lib/chef/node/mixin/immutablize_hash.rb +1 -0
  21. data/lib/chef/provider.rb +14 -8
  22. data/lib/chef/provider/package/chocolatey.rb +11 -3
  23. data/lib/chef/provider/package/dnf/python_helper.rb +8 -3
  24. data/lib/chef/provider/package/windows/exe.rb +2 -2
  25. data/lib/chef/provider/package/windows/msi.rb +3 -3
  26. data/lib/chef/provider/package/yum/python_helper.rb +8 -3
  27. data/lib/chef/provider/service/windows.rb +1 -1
  28. data/lib/chef/resource/apt_repository.rb +19 -13
  29. data/lib/chef/resource/apt_update.rb +15 -1
  30. data/lib/chef/resource/archive_file.rb +10 -1
  31. data/lib/chef/resource/build_essential.rb +14 -1
  32. data/lib/chef/resource/chocolatey_config.rb +17 -1
  33. data/lib/chef/resource/chocolatey_feature.rb +15 -0
  34. data/lib/chef/resource/chocolatey_package.rb +31 -1
  35. data/lib/chef/resource/chocolatey_source.rb +17 -1
  36. data/lib/chef/resource/cookbook_file.rb +1 -1
  37. data/lib/chef/resource/cron_access.rb +22 -1
  38. data/lib/chef/resource/cron_d.rb +46 -1
  39. data/lib/chef/resource/dmg_package.rb +28 -0
  40. data/lib/chef/resource/kernel_module.rb +61 -0
  41. data/lib/chef/resource/sudo.rb +2 -2
  42. data/lib/chef/resource/windows_ad_join.rb +72 -3
  43. data/lib/chef/resource/windows_service.rb +1 -1
  44. data/lib/chef/resource/windows_share.rb +2 -1
  45. data/lib/chef/shell.rb +4 -4
  46. data/lib/chef/shell/ext.rb +2 -2
  47. data/lib/chef/train_transport.rb +1 -1
  48. data/lib/chef/version.rb +1 -1
  49. data/spec/functional/resource/ifconfig_spec.rb +0 -2
  50. data/spec/functional/resource/mount_spec.rb +0 -4
  51. data/spec/functional/util/powershell/cmdlet_spec.rb +2 -2
  52. data/spec/integration/knife/chef_repo_path_spec.rb +4 -2
  53. data/spec/integration/recipes/resource_converge_if_changed_spec.rb +19 -19
  54. data/spec/spec_helper.rb +2 -0
  55. data/spec/unit/formatters/doc_spec.rb +18 -0
  56. data/spec/unit/knife/bootstrap_spec.rb +46 -10
  57. data/spec/unit/knife/supermarket_list_spec.rb +70 -0
  58. data/spec/unit/knife/supermarket_search_spec.rb +85 -0
  59. data/spec/unit/node/attribute_spec.rb +22 -0
  60. data/spec/unit/node/immutable_collections_spec.rb +72 -144
  61. data/spec/unit/provider/package/chocolatey_spec.rb +50 -35
  62. data/spec/unit/provider/package/windows/exe_spec.rb +1 -1
  63. data/spec/unit/provider/service/windows_spec.rb +23 -3
  64. data/spec/unit/resource/chocolatey_package_spec.rb +17 -2
  65. data/spec/unit/resource/windows_ad_join_spec.rb +4 -0
  66. data/spec/unit/resource/windows_service_spec.rb +5 -0
  67. data/spec/unit/resource/windows_share_spec.rb +7 -0
  68. data/tasks/docs.rb +4 -1
  69. metadata +10 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d697d8e4f6cda468d0586b03e8c1eddda7a72ab33338c4012fc35ceca6b6f389
4
- data.tar.gz: 8c6f5f5797b7ca69023b0b29db9b76e76f60b8c6c5469603fbc82815037a991f
3
+ metadata.gz: 21b19428acc0503074938fb4e5dc93cb5cd70542f35625634b205d74b662bf48
4
+ data.tar.gz: 9e75d184a6494c67c334198d6cc55c60a9441d9bf18bece3598d8476d95adc8f
5
5
  SHA512:
6
- metadata.gz: 45977ec05519331f10644ed4ac549554d2330078dc4ea587c48c4dbb22575caaf4efc96e05f2df2108f4f578484b7e0e041d119cf15d6e1f6ee1111e6e0dcc4a
7
- data.tar.gz: e640ff48fd1aac419652fba2c8fbe3a613a15bb1e88d09b24ef2e2549a2815ebddc8838ef05f234c56b01f3081c83a8256d5883352f0267ee4a4cb53fd6e2ea6
6
+ metadata.gz: 6877efd745673cb51b0879c8e78abb0ea88049e320e07d69052bc3616f4c53077f039f47310b90fb469e13f90f9f5df3b8935aee3ea45ba5aa26984829d6a849
7
+ data.tar.gz: ffe577ee8b5b6f36e0a75930a9878bb659c60488c4ea9471e2596294a9590c6db0887f5be1d8266d26052b55702b89cf5ee15d432cee54aee63d9b3da65aab8d
data/chef.gemspec CHANGED
@@ -16,8 +16,8 @@ Gem::Specification.new do |s|
16
16
  s.required_ruby_version = ">= 2.5.0"
17
17
 
18
18
  s.add_dependency "chef-config", "= #{Chef::VERSION}"
19
- s.add_dependency "train-core", "~> 3.0"
20
- s.add_dependency "train-winrm"
19
+ s.add_dependency "train-core", "~> 3.1"
20
+ s.add_dependency "train-winrm", ">= 0.2.5"
21
21
 
22
22
  s.add_dependency "license-acceptance", "~> 1.0", ">= 1.0.5"
23
23
  s.add_dependency "mixlib-cli", ">= 2.1.1", "< 3.0"
@@ -52,13 +52,13 @@ class Chef::Application::Solo < Chef::Application::Base
52
52
  # Get this party started
53
53
  def run(enforce_license: false)
54
54
  setup_signal_handlers
55
- setup_application
56
55
  reconfigure
57
56
  check_license_acceptance if enforce_license
58
57
  for_ezra if Chef::Config[:ez]
59
58
  if !Chef::Config[:solo_legacy_mode]
60
59
  Chef::Application::Client.new.run
61
60
  else
61
+ setup_application
62
62
  run_application
63
63
  end
64
64
  end
@@ -10,11 +10,18 @@ class Chef
10
10
  class Dispatcher < Base
11
11
 
12
12
  attr_reader :subscribers
13
- attr_reader :event_list
14
13
 
15
14
  def initialize(*subscribers)
16
15
  @subscribers = subscribers
17
- @event_list = []
16
+ end
17
+
18
+ # Since the cookbook synchronizer will call this object from threads, we
19
+ # have to deal with concurrent access to this object. Since we don't want
20
+ # threads to handle events from other threads, we just use thread local
21
+ # storage.
22
+ #
23
+ def event_list
24
+ Thread.current[:chef_client_event_list] ||= []
18
25
  end
19
26
 
20
27
  # Add a new subscriber to the list of registered subscribers
@@ -236,11 +236,11 @@ class Chef
236
236
  end
237
237
 
238
238
  def resource_update_progress(resource, current, total, interval)
239
- @progress[resource] ||= 0
239
+ @progress[resource] ||= -1
240
240
 
241
- percent_complete = (current.to_f / total.to_f * 100).to_i
241
+ percent_complete = (current.to_f / total.to_f * 100).to_i unless total.to_f == 0.0
242
242
 
243
- if percent_complete > @progress[resource]
243
+ if percent_complete && percent_complete > @progress[resource]
244
244
 
245
245
  @progress[resource] = percent_complete
246
246
 
data/lib/chef/knife.rb CHANGED
@@ -64,6 +64,12 @@ class Chef
64
64
  attr_accessor :name_args
65
65
  attr_accessor :ui
66
66
 
67
+ # knife acl subcommands are grouped in this category using this constant to verify.
68
+ OPSCODE_HOSTED_CHEF_ACCESS_CONTROL = %w{acl group user}.freeze
69
+
70
+ # knife opc subcommands are grouped in this category using this constant to verify.
71
+ CHEF_ORGANIZATION_MANAGEMENT = %w{opc}.freeze
72
+
67
73
  # Configure mixlib-cli to always separate defaults from user-supplied CLI options
68
74
  def self.use_separate_defaults?
69
75
  true
@@ -234,7 +240,7 @@ class Chef
234
240
  dependency_loaders.each(&:call)
235
241
  end
236
242
 
237
- OFFICIAL_PLUGINS = %w{ec2 rackspace windows openstack azure google linode push vcenter lpar}.freeze
243
+ OFFICIAL_PLUGINS = %w{lpar openstack push rackspace vcenter}.freeze
238
244
 
239
245
  class << self
240
246
  def list_commands(preferred_category = nil)
@@ -270,10 +276,14 @@ class Chef
270
276
  ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
271
277
  end
272
278
 
273
- if category_commands = guess_category(args)
279
+ if CHEF_ORGANIZATION_MANAGEMENT.include?(args[0])
280
+ list_commands("CHEF ORGANIZATION MANAGEMENT")
281
+ elsif OPSCODE_HOSTED_CHEF_ACCESS_CONTROL.include?(args[0])
282
+ list_commands("OPSCODE HOSTED CHEF ACCESS CONTROL")
283
+ elsif category_commands = guess_category(args)
274
284
  list_commands(category_commands)
275
285
  elsif OFFICIAL_PLUGINS.include?(args[0]) # command was an uninstalled official chef knife plugin
276
- ui.info("Use `#{Chef::Dist::EXEC} gem install knife-#{args[0]}` to install the plugin into ChefDK")
286
+ ui.info("Use `#{Chef::Dist::EXEC} gem install knife-#{args[0]}` to install the plugin into ChefDK / Chef Workstation")
277
287
  else
278
288
  list_commands
279
289
  end
@@ -616,7 +616,7 @@ class Chef
616
616
 
617
617
  def connect!
618
618
  ui.info("Connecting to #{ui.color(server_name, :bold)}")
619
- opts = connection_opts.dup
619
+ opts ||= connection_opts.dup
620
620
  do_connect(opts)
621
621
  rescue Train::Error => e
622
622
  # We handle these by message text only because train only loads the
@@ -638,8 +638,10 @@ class Chef
638
638
  EOM
639
639
  # FIXME: this should save the key to known_hosts but doesn't appear to be
640
640
  config[:ssh_verify_host_key] = :accept_new
641
- do_connect(connection_opts(reset: true))
642
- elsif ssh? && e.cause && e.cause.class == Net::SSH::AuthenticationFailed
641
+ conn_opts = connection_opts(reset: true)
642
+ opts.merge! conn_opts
643
+ retry
644
+ elsif (ssh? && e.cause && e.cause.class == Net::SSH::AuthenticationFailed) || (ssh? && e.class == Train::ClientError && e.reason == :no_ssh_password_or_key_available)
643
645
  if connection.password_auth?
644
646
  raise
645
647
  else
@@ -650,7 +652,23 @@ class Chef
650
652
  end
651
653
 
652
654
  opts.merge! force_ssh_password_opts(password)
653
- do_connect(opts)
655
+ retry
656
+ else
657
+ raise
658
+ end
659
+ rescue RuntimeError => e
660
+ if winrm? && e.message == "password is a required option"
661
+ if connection.password_auth?
662
+ raise
663
+ else
664
+ ui.warn("Failed to authenticate #{opts[:user]} to #{server_name} - trying password auth")
665
+ password = ui.ask("Enter password for #{opts[:user]}@#{server_name}.") do |q|
666
+ q.echo = false
667
+ end
668
+ end
669
+
670
+ opts.merge! force_winrm_password_opts(password)
671
+ retry
654
672
  else
655
673
  raise
656
674
  end
@@ -1023,6 +1041,12 @@ class Chef
1023
1041
  }
1024
1042
  end
1025
1043
 
1044
+ def force_winrm_password_opts(password)
1045
+ {
1046
+ password: password,
1047
+ }
1048
+ end
1049
+
1026
1050
  # Looks up configuration entries, first in the class member
1027
1051
  # `config` which contains options populated from CLI flags.
1028
1052
  # If the entry is not found there, Chef::Config[:knife][KEY]
@@ -1,4 +1,3 @@
1
- sh -c '
2
1
  <%= "https_proxy=\"#{knife_config[:bootstrap_proxy]}\" export https_proxy" if knife_config[:bootstrap_proxy] %>
3
2
  <%= "no_proxy=\"#{knife_config[:bootstrap_no_proxy]}\" export no_proxy" if knife_config[:bootstrap_no_proxy] %>
4
3
 
@@ -189,24 +188,24 @@ fi
189
188
  mkdir -p <%= Chef::Dist::CONF_DIR %>
190
189
 
191
190
  <% if client_pem -%>
192
- cat > <%= Chef::Dist::CONF_DIR %>/client.pem <<'EOP'
191
+ (umask 077 && (cat > <%= Chef::Dist::CONF_DIR %>/client.pem <<'EOP'
193
192
  <%= ::File.read(::File.expand_path(client_pem)) %>
194
193
  EOP
195
- chmod 0600 <%= Chef::Dist::CONF_DIR %>/client.pem
194
+ )) || exit 1
196
195
  <% end -%>
197
196
 
198
197
  <% if validation_key -%>
199
- cat > <%= Chef::Dist::CONF_DIR %>/validation.pem <<'EOP'
198
+ (umask 077 && (cat > <%= Chef::Dist::CONF_DIR %>/validation.pem <<'EOP'
200
199
  <%= validation_key %>
201
200
  EOP
202
- chmod 0600 <%= Chef::Dist::CONF_DIR %>/validation.pem
201
+ )) || exit 1
203
202
  <% end -%>
204
203
 
205
204
  <% if encrypted_data_bag_secret -%>
206
- cat > <%= Chef::Dist::CONF_DIR %>/encrypted_data_bag_secret <<'EOP'
205
+ (umask 077 && (cat > <%= Chef::Dist::CONF_DIR %>/encrypted_data_bag_secret <<'EOP'
207
206
  <%= encrypted_data_bag_secret %>
208
207
  EOP
209
- chmod 0600 <%= Chef::Dist::CONF_DIR %>/encrypted_data_bag_secret
208
+ )) || exit 1
210
209
  <% end -%>
211
210
 
212
211
  <% unless trusted_certs.empty? -%>
@@ -240,4 +239,4 @@ mkdir -p <%= Chef::Dist::CONF_DIR %>/client.d
240
239
 
241
240
  echo "Starting the first <%= Chef::Dist::PRODUCT %> Client run..."
242
241
 
243
- <%= start_chef %>'
242
+ <%= start_chef %>
@@ -36,7 +36,7 @@ class Chef
36
36
  def self.included(base)
37
37
  base.option :secret,
38
38
  short: "-s SECRET",
39
- long: "--secret ",
39
+ long: "--secret SECRET",
40
40
  description: "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'.",
41
41
  # Need to store value from command line in separate variable - knife#merge_configs populates same keys
42
42
  # on config object from
@@ -80,9 +80,16 @@ class Chef
80
80
  end
81
81
 
82
82
  def validate_secrets
83
- if has_cl_secret? && has_cl_secret_file?
84
- ui.fatal("Please specify only one of --secret, --secret-file")
85
- exit(1)
83
+ if has_cl_secret?
84
+ if opt_parser.default_argv.include?("-s")
85
+ ui.warn("Secret short option -s is deprecated and will remove in the future. Please use --secret instead.
86
+ ")
87
+ end
88
+
89
+ if has_cl_secret_file?
90
+ ui.fatal("Please specify only one of --secret, --secret-file")
91
+ exit(1)
92
+ end
86
93
  end
87
94
 
88
95
  if knife_config[:secret] && knife_config[:secret_file]
@@ -43,7 +43,7 @@ class Chef
43
43
  long: "--[no-]force",
44
44
  boolean: true,
45
45
  default: false,
46
- description: "Force upload of files even if they match (quicker and harmless, but doesn't print out what it changed)."
46
+ description: "Force download of files even if they match (quicker and harmless, but doesn't print out what it changed)."
47
47
 
48
48
  option :dry_run,
49
49
  long: "--dry-run",
@@ -56,7 +56,7 @@ class Chef
56
56
  long: "--[no-]diff",
57
57
  boolean: true,
58
58
  default: true,
59
- description: "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff."
59
+ description: "Turn off to avoid downloading existing files; only new (and possibly deleted) files with --no-diff."
60
60
 
61
61
  option :cookbook_version,
62
62
  long: "--cookbook-version VERSION",
@@ -40,7 +40,7 @@ class Chef::Knife::Exec < Chef::Knife
40
40
  end
41
41
 
42
42
  def run
43
- config[:script_path] ||= Array(Chef::Config[:script_path])
43
+ config[:script_path] = Array(config[:script_path] || Chef::Config[:script_path])
44
44
 
45
45
  # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
46
46
  config[:script_path] << File.join(Chef::Knife.chef_config_dir, "scripts") if Chef::Knife.chef_config_dir
@@ -57,6 +57,14 @@ class Chef::Knife::Exec < Chef::Knife
57
57
  context.instance_eval(IO.read(file), file, 0)
58
58
  end
59
59
  else
60
+ puts "An interactive shell is opened"
61
+ puts
62
+ puts "Type your script and do:"
63
+ puts
64
+ puts "1. To run the script, use 'Ctrl D'"
65
+ puts "2. To exit, use 'Ctrl/Shift C'"
66
+ puts
67
+ puts "Type here a script..."
60
68
  script = STDIN.read
61
69
  context.instance_eval(script, "STDIN", 0)
62
70
  end
@@ -623,7 +623,7 @@ class Chef
623
623
  end
624
624
 
625
625
  session.close
626
- if exit_status != 0
626
+ if exit_status && exit_status != 0
627
627
  exit exit_status
628
628
  else
629
629
  exit_status
@@ -190,7 +190,7 @@ class Chef
190
190
  #{ui.color("TO FIX THIS ERROR:", :bold)}
191
191
 
192
192
  If the server you are connecting to uses a self-signed certificate, you must
193
- configure chef to trust that server's certificate.
193
+ configure #{Chef::Dist::PRODUCT} to trust that server's certificate.
194
194
 
195
195
  By default, the certificate is stored in the following location on the host
196
196
  where your chef-server runs:
@@ -37,23 +37,35 @@ class Chef
37
37
  default: "https://supermarket.chef.io",
38
38
  proc: Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
39
39
 
40
+ option :sort_by,
41
+ long: "--sort-by SORT",
42
+ description: "Use to sort the records",
43
+ in: %w{recently_updated recently_added most_downloaded most_followed}
44
+
45
+ option :owned_by,
46
+ short: "-o USER",
47
+ long: "--owned-by USER",
48
+ description: "Show cookbooks that are owned by the USER"
49
+
40
50
  def run
41
51
  if config[:with_uri]
42
- cookbooks = {}
43
- get_cookbook_list.each { |k, v| cookbooks[k] = v["cookbook"] }
44
- ui.output(format_for_display(cookbooks))
52
+ ui.output(format_for_display(get_cookbook_list))
45
53
  else
46
- ui.msg(ui.list(get_cookbook_list.keys.sort, :columns_down))
54
+ ui.msg(ui.list(get_cookbook_list.keys, :columns_down))
47
55
  end
48
56
  end
49
57
 
50
- def get_cookbook_list(items = 10, start = 0, cookbook_collection = {})
58
+ # In order to avoid pagination items limit set to 9999999
59
+ def get_cookbook_list(items = 9999999, start = 0, cookbook_collection = {})
51
60
  cookbooks_url = "#{config[:supermarket_site]}/api/v1/cookbooks?items=#{items}&start=#{start}"
61
+ cookbooks_url << "&order=#{config[:sort_by]}" if config[:sort_by]
62
+ cookbooks_url << "&user=#{config[:owned_by]}" if config[:owned_by]
52
63
  cr = noauth_rest.get(cookbooks_url)
64
+
53
65
  cr["items"].each do |cookbook|
54
- cookbook_collection[cookbook["cookbook_name"]] = cookbook
66
+ cookbook_collection[cookbook["cookbook_name"]] = cookbook["cookbook"]
55
67
  end
56
- new_start = start + cr["items"].length
68
+ new_start = start + items
57
69
  if new_start < cr["total"]
58
70
  get_cookbook_list(items, new_start, cookbook_collection)
59
71
  else
@@ -35,13 +35,14 @@ class Chef
35
35
  output(search_cookbook(name_args[0]))
36
36
  end
37
37
 
38
- def search_cookbook(query, items = 10, start = 0, cookbook_collection = {})
38
+ # In order to avoid pagination items limit set to 9999999
39
+ def search_cookbook(query, items = 9999999, start = 0, cookbook_collection = {})
39
40
  cookbooks_url = "#{config[:supermarket_site]}/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
40
41
  cr = noauth_rest.get(cookbooks_url)
41
42
  cr["items"].each do |cookbook|
42
43
  cookbook_collection[cookbook["cookbook_name"]] = cookbook
43
44
  end
44
- new_start = start + cr["items"].length
45
+ new_start = start + items
45
46
  if new_start < cr["total"]
46
47
  search_cookbook(query, items, new_start, cookbook_collection)
47
48
  else
@@ -148,7 +148,9 @@ class Chef
148
148
  to_a
149
149
  to_h
150
150
  to_hash
151
+ to_json
151
152
  to_set
153
+ to_yaml
152
154
  value?
153
155
  values
154
156
  values_at
@@ -65,6 +65,10 @@ class Chef
65
65
  Array.new(map { |e| safe_dup(e) })
66
66
  end
67
67
 
68
+ def to_yaml(*opts)
69
+ to_a.to_yaml(*opts)
70
+ end
71
+
68
72
  private
69
73
 
70
74
  def convert_value(value)
@@ -172,6 +176,10 @@ class Chef
172
176
  Mash.new(self)
173
177
  end
174
178
 
179
+ def to_yaml(*opts)
180
+ to_h.to_yaml(*opts)
181
+ end
182
+
175
183
  prepend Chef::Node::Mixin::StateTracking
176
184
  end
177
185
  end
@@ -96,6 +96,12 @@ class Chef
96
96
 
97
97
  alias_method :to_array, :to_a
98
98
 
99
+ # As Psych module, not respecting ImmutableArray object
100
+ # So first convert it to Hash/Array then parse it to `.to_yaml`
101
+ def to_yaml(*opts)
102
+ to_a.to_yaml(*opts)
103
+ end
104
+
99
105
  private
100
106
 
101
107
  # needed for __path__
@@ -168,6 +174,12 @@ class Chef
168
174
 
169
175
  alias_method :to_hash, :to_h
170
176
 
177
+ # As Psych module, not respecting ImmutableMash object
178
+ # So first convert it to Hash/Array then parse it to `.to_yaml`
179
+ def to_yaml(*opts)
180
+ to_h.to_yaml(*opts)
181
+ end
182
+
171
183
  # For elements like Fixnums, true, nil...
172
184
  def safe_dup(e)
173
185
  e.dup
@@ -119,6 +119,7 @@ class Chef
119
119
  to_h
120
120
  to_plist
121
121
  to_set
122
+ to_yaml
122
123
  transpose
123
124
  union
124
125
  uniq
@@ -116,6 +116,7 @@ class Chef
116
116
  to_proc
117
117
  to_set
118
118
  to_xml_attributes
119
+ to_yaml
119
120
  transform_keys
120
121
  transform_values
121
122
  uniq