chef 12.0.0.rc.0-x86-mingw32 → 12.0.0-x86-mingw32

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -1
  3. data/lib/chef/api_client/registration.rb +3 -1
  4. data/lib/chef/chef_fs/data_handler/group_data_handler.rb +4 -0
  5. data/lib/chef/config.rb +46 -38
  6. data/lib/chef/event_loggers/windows_eventlog.rb +5 -6
  7. data/lib/chef/exceptions.rb +13 -1
  8. data/lib/chef/file_content_management/tempfile.rb +33 -5
  9. data/lib/chef/knife.rb +11 -3
  10. data/lib/chef/knife/bootstrap.rb +8 -7
  11. data/lib/chef/mixin/deep_merge.rb +15 -54
  12. data/lib/chef/mixin/which.rb +37 -0
  13. data/lib/chef/node.rb +14 -25
  14. data/lib/chef/node/attribute.rb +227 -41
  15. data/lib/chef/node/attribute_collections.rb +117 -3
  16. data/lib/chef/node/immutable_collections.rb +6 -6
  17. data/lib/chef/platform/provider_priority_map.rb +3 -2
  18. data/lib/chef/platform/service_helpers.rb +37 -8
  19. data/lib/chef/provider/service/aixinit.rb +1 -1
  20. data/lib/chef/provider/service/arch.rb +1 -1
  21. data/lib/chef/provider/service/debian.rb +5 -1
  22. data/lib/chef/provider/service/init.rb +4 -0
  23. data/lib/chef/provider/service/insserv.rb +5 -1
  24. data/lib/chef/provider/service/invokercd.rb +5 -1
  25. data/lib/chef/provider/service/redhat.rb +5 -1
  26. data/lib/chef/provider/service/systemd.rb +50 -32
  27. data/lib/chef/provider/service/upstart.rb +5 -2
  28. data/lib/chef/provider_resolver.rb +30 -16
  29. data/lib/chef/resource.rb +2 -1
  30. data/lib/chef/resources.rb +7 -0
  31. data/lib/chef/run_context.rb +0 -5
  32. data/lib/chef/run_list/run_list_expansion.rb +2 -2
  33. data/lib/chef/shell.rb +2 -2
  34. data/lib/chef/util/selinux.rb +2 -10
  35. data/lib/chef/version.rb +1 -1
  36. data/lib/chef/workstation_config_loader.rb +1 -1
  37. data/spec/support/shared/unit/resource/static_provider_resolution.rb +1 -6
  38. data/spec/unit/api_client/registration_spec.rb +22 -0
  39. data/spec/unit/application/knife_spec.rb +6 -2
  40. data/spec/unit/chef_fs/data_handler/group_handler_spec.rb +63 -0
  41. data/spec/unit/config_spec.rb +5 -5
  42. data/spec/unit/knife/bootstrap_spec.rb +27 -1
  43. data/spec/unit/knife_spec.rb +5 -0
  44. data/spec/unit/mixin/deep_merge_spec.rb +0 -40
  45. data/spec/unit/node/attribute_spec.rb +37 -50
  46. data/spec/unit/node_spec.rb +321 -13
  47. data/spec/unit/provider/file/content_spec.rb +23 -2
  48. data/spec/unit/provider/service/systemd_service_spec.rb +173 -158
  49. data/spec/unit/provider_resolver_spec.rb +175 -10
  50. data/spec/unit/resource/timestamped_deploy_spec.rb +8 -29
  51. data/spec/unit/runner_spec.rb +3 -1
  52. metadata +147 -221
  53. data/spec/.DS_Store +0 -0
  54. data/spec/data/.DS_Store +0 -0
  55. data/spec/data/lwrp/.DS_Store +0 -0
  56. data/spec/data/lwrp/providers/.DS_Store +0 -0
  57. data/spec/data/lwrp/resources/.DS_Store +0 -0
  58. data/spec/data/lwrp_override/.DS_Store +0 -0
  59. data/spec/data/lwrp_override/providers/.DS_Store +0 -0
  60. data/spec/data/lwrp_override/resources/.DS_Store +0 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a76a266f7ae25ab6678f701843c2812db14e51e4
4
+ data.tar.gz: 72f478ea0f0cb3af196be6b2ce15206c179b6743
5
+ SHA512:
6
+ metadata.gz: 8069c9031d37ec51fbf04f13d4b169937ab2a2f3629f5739b10596485aabbac4f9384c18aa002d7abde1df552dea6512ebffe7019daff0328312e42286071ae0
7
+ data.tar.gz: 9c4f4957858271f4473ebc4fcd64b0abe97bf07a634a77ae83527947199737a05f27291455d42f6ca164c469dc564185f84b0ee5e26c33b68a9e4a611fe4f8d9
data/README.md CHANGED
@@ -33,7 +33,7 @@ emerge, etc.):
33
33
  * git
34
34
  * C compiler, header files, etc. On Ubuntu/debian, use the
35
35
  `build-essential` package.
36
- * ruby 1.9.3 or later
36
+ * ruby 2.0.0 or later
37
37
  * rubygems
38
38
  * bundler
39
39
 
@@ -153,7 +153,9 @@ class Chef
153
153
  def file_flags
154
154
  base_flags = File::CREAT|File::TRUNC|File::RDWR
155
155
  # Windows doesn't have symlinks, so it doesn't have NOFOLLOW
156
- base_flags |= File::NOFOLLOW if defined?(File::NOFOLLOW)
156
+ if defined?(File::NOFOLLOW) && !Chef::Config[:follow_client_key_symlink]
157
+ base_flags |= File::NOFOLLOW
158
+ end
157
159
  base_flags
158
160
  end
159
161
  end
@@ -36,6 +36,10 @@ class Chef
36
36
  result
37
37
  end
38
38
 
39
+ def normalize_for_post(group, entry)
40
+ normalize_for_put(group, entry)
41
+ end
42
+
39
43
  def preserve_key?(key)
40
44
  return key == 'name'
41
45
  end
@@ -396,6 +396,12 @@ class Chef
396
396
  # If chef-zero is enabled, this defaults to nil (no authentication).
397
397
  default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") }
398
398
 
399
+ # When registering the client, should we allow the client key location to
400
+ # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem
401
+ # If the path of the key goes through a directory like /tmp this should
402
+ # never be set to true or its possibly an easily exploitable security hole.
403
+ default :follow_client_key_symlink, false
404
+
399
405
  # This secret is used to decrypt encrypted data bag items.
400
406
  default(:encrypted_data_bag_secret) do
401
407
  if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
@@ -491,7 +497,7 @@ class Chef
491
497
  default :ssh_gateway, nil
492
498
  default :bootstrap_version, nil
493
499
  default :bootstrap_proxy, nil
494
- default :bootstrap_template, "chef-full"
500
+ default :bootstrap_template, nil
495
501
  default :secret, nil
496
502
  default :secret_file, nil
497
503
  default :identity_file, nil
@@ -554,10 +560,12 @@ class Chef
554
560
  # used to update files.
555
561
  default :file_atomic_update, true
556
562
 
557
- # If false file staging is will be done via tempfiles that are
558
- # created under ENV['TMP'] otherwise tempfiles will be created in
559
- # the directory that files are going to reside.
560
- default :file_staging_uses_destdir, true
563
+ # There are 3 possible values for this configuration setting.
564
+ # true => file staging is done in the destination directory
565
+ # false => file staging is done via tempfiles under ENV['TMP']
566
+ # :auto => file staging will try using destination directory if possible and
567
+ # will fall back to ENV['TMP'] if destination directory is not usable.
568
+ default :file_staging_uses_destdir, :auto
561
569
 
562
570
  # Exit if another run is in progress and the chef-client is unable to
563
571
  # get the lock before time expires. If nil, no timeout is enforced. (Exits
@@ -617,44 +625,44 @@ class Chef
617
625
  #
618
626
  # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
619
627
  # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
620
- default :internal_locale do
621
- begin
622
- # https://github.com/opscode/chef/issues/2181
623
- # Some systems have the `locale -a` command, but the result has
624
- # invalid characters for the default encoding.
625
- #
626
- # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
627
- # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
628
- locales = shell_out_with_systems_locale("locale -a").stdout.split
629
- case
630
- when locales.include?('C.UTF-8')
631
- 'C.UTF-8'
632
- when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
633
- 'en_US.UTF-8'
634
- when locales.include?('en.UTF-8')
635
- 'en.UTF-8'
636
- else
637
- # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
638
- guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
639
- unless guesses.empty?
640
- guessed_locale = guesses.first
641
- # Transform into the form en_ZZ.UTF-8
642
- guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
643
- else
644
- Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
645
- 'C'
646
- end
647
- end
648
- rescue
649
- if Chef::Platform.windows?
650
- Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
628
+ def self.guess_internal_locale
629
+ # https://github.com/opscode/chef/issues/2181
630
+ # Some systems have the `locale -a` command, but the result has
631
+ # invalid characters for the default encoding.
632
+ #
633
+ # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
634
+ # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
635
+ locales = shell_out_with_systems_locale!("locale -a").stdout.split
636
+ case
637
+ when locales.include?('C.UTF-8')
638
+ 'C.UTF-8'
639
+ when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
640
+ 'en_US.UTF-8'
641
+ when locales.include?('en.UTF-8')
642
+ 'en.UTF-8'
643
+ else
644
+ # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
645
+ guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
646
+ unless guesses.empty?
647
+ guessed_locale = guesses.first
648
+ # Transform into the form en_ZZ.UTF-8
649
+ guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
651
650
  else
652
- Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
651
+ Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
652
+ 'C'
653
653
  end
654
- 'en_US.UTF-8'
655
654
  end
655
+ rescue
656
+ if Chef::Platform.windows?
657
+ Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
658
+ else
659
+ Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
660
+ end
661
+ 'en_US.UTF-8'
656
662
  end
657
663
 
664
+ default :internal_locale, guess_internal_locale
665
+
658
666
  # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
659
667
  # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
660
668
  # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
@@ -26,7 +26,6 @@ if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
26
26
  end
27
27
 
28
28
  require 'win32/eventlog'
29
- include Win32
30
29
  end
31
30
 
32
31
  class Chef
@@ -51,12 +50,12 @@ class Chef
51
50
  end
52
51
 
53
52
  def initialize
54
- @eventlog = EventLog::open('Application')
53
+ @eventlog = ::Win32::EventLog::open('Application')
55
54
  end
56
55
 
57
56
  def run_start(version)
58
57
  @eventlog.report_event(
59
- :event_type => EventLog::INFO_TYPE,
58
+ :event_type => ::Win32::EventLog::INFO_TYPE,
60
59
  :source => SOURCE,
61
60
  :event_id => RUN_START_EVENT_ID,
62
61
  :data => [version]
@@ -66,7 +65,7 @@ class Chef
66
65
  def run_started(run_status)
67
66
  @run_status = run_status
68
67
  @eventlog.report_event(
69
- :event_type => EventLog::INFO_TYPE,
68
+ :event_type => ::Win32::EventLog::INFO_TYPE,
70
69
  :source => SOURCE,
71
70
  :event_id => RUN_STARTED_EVENT_ID,
72
71
  :data => [run_status.run_id]
@@ -75,7 +74,7 @@ class Chef
75
74
 
76
75
  def run_completed(node)
77
76
  @eventlog.report_event(
78
- :event_type => EventLog::INFO_TYPE,
77
+ :event_type => ::Win32::EventLog::INFO_TYPE,
79
78
  :source => SOURCE,
80
79
  :event_id => RUN_COMPLETED_EVENT_ID,
81
80
  :data => [@run_status.run_id, @run_status.elapsed_time.to_s]
@@ -88,7 +87,7 @@ class Chef
88
87
  #Exception backtrace: %5
89
88
  def run_failed(e)
90
89
  @eventlog.report_event(
91
- :event_type => EventLog::ERROR_TYPE,
90
+ :event_type => ::Win32::EventLog::ERROR_TYPE,
92
91
  :source => SOURCE,
93
92
  :event_id => RUN_FAILED_EVENT_ID,
94
93
  :data => [@run_status.run_id,
@@ -126,6 +126,13 @@ class Chef
126
126
 
127
127
  class CannotDetermineHomebrewOwner < Package; end
128
128
 
129
+ # Can not create staging file during file deployment
130
+ class FileContentStagingError < RuntimeError
131
+ def initialize(errors)
132
+ super "Staging tempfile can not be created during file deployment.\n Errors: #{errors.join('\n')}!"
133
+ end
134
+ end
135
+
129
136
  # A different version of a cookbook was added to a
130
137
  # VersionedRecipeList than the one already there.
131
138
  class CookbookVersionConflict < ArgumentError ; end
@@ -155,7 +162,12 @@ class Chef
155
162
  # Node::Attribute computes the merged version of of attributes
156
163
  # and makes it read-only. Attempting to modify a read-only
157
164
  # attribute will cause this error.
158
- class ImmutableAttributeModification < NoMethodError; end
165
+ class ImmutableAttributeModification < NoMethodError
166
+ def initialize
167
+ super "Node attributes are read-only when you do not specify which precedence level to set. " +
168
+ %Q(To set an attribute use code like `node.default["key"] = "value"')
169
+ end
170
+ end
159
171
 
160
172
  # Merged node attributes are invalidated when the component
161
173
  # attributes are updated. Attempting to read from a stale copy
@@ -35,7 +35,22 @@ class Chef
35
35
  private
36
36
 
37
37
  def tempfile_open
38
- tf = ::Tempfile.open(tempfile_basename, tempfile_dirname)
38
+ tf = nil
39
+ errors = [ ]
40
+
41
+ tempfile_dirnames.each do |tempfile_dirname|
42
+ begin
43
+ tf = ::Tempfile.open(tempfile_basename, tempfile_dirname)
44
+ break
45
+ rescue SystemCallError => e
46
+ message = "Creating temp file under '#{tempfile_dirname}' failed with: '#{e.message}'"
47
+ Chef::Log.debug(message)
48
+ errors << message
49
+ end
50
+ end
51
+
52
+ raise Chef::Exceptions::FileContentStagingError(errors) if tf.nil?
53
+
39
54
  # We always process the tempfile in binmode so that we
40
55
  # preserve the line endings of the content.
41
56
  tf.binmode
@@ -53,16 +68,29 @@ class Chef
53
68
  basename
54
69
  end
55
70
 
56
- def tempfile_dirname
71
+ # Returns the possible directories for the tempfile to be created in.
72
+ def tempfile_dirnames
57
73
  # in why-run mode we need to create a Tempfile to compare against, which we will never
58
74
  # wind up deploying, but our enclosing directory for the destdir may not exist yet, so
59
75
  # instead we can reliably always create a Tempfile to compare against in Dir::tmpdir
60
- if Chef::Config[:file_staging_uses_destdir] && !Chef::Config[:why_run]
61
- ::File.dirname(@new_resource.path)
76
+ if Chef::Config[:why_run]
77
+ [ Dir.tmpdir ]
62
78
  else
63
- Dir::tmpdir
79
+ case Chef::Config[:file_staging_uses_destdir]
80
+ when :auto
81
+ # In auto mode we try the destination directory first and fallback to ENV['TMP'] if
82
+ # that doesn't work.
83
+ [ ::File.dirname(@new_resource.path), Dir.tmpdir ]
84
+ when true
85
+ [ ::File.dirname(@new_resource.path) ]
86
+ when false
87
+ [ Dir.tmpdir ]
88
+ else
89
+ raise Chef::Exceptions::ConfigurationError, "Unknown setting '#{Chef::Config[:file_staging_uses_destdir]}' for Chef::Config[:file_staging_uses_destdir]. Possible values are :auto, true or false."
90
+ end
64
91
  end
65
92
  end
93
+
66
94
  end
67
95
  end
68
96
  end
@@ -72,6 +72,11 @@ class Chef
72
72
  ui.msg(msg)
73
73
  end
74
74
 
75
+ def self.reset_config_loader!
76
+ @@chef_config_dir = nil
77
+ @config_loader = nil
78
+ end
79
+
75
80
  def self.reset_subcommands!
76
81
  @@subcommands = {}
77
82
  @subcommands_by_category = nil
@@ -162,12 +167,15 @@ class Chef
162
167
  # Shared with subclasses
163
168
  @@chef_config_dir = nil
164
169
 
170
+ def self.config_loader
171
+ @config_loader ||= WorkstationConfigLoader.new(nil, Chef::Log)
172
+ end
173
+
165
174
  def self.load_config(explicit_config_file)
166
- config_loader = WorkstationConfigLoader.new(explicit_config_file, Chef::Log)
175
+ config_loader.explicit_config_file = explicit_config_file
167
176
  config_loader.load
168
177
 
169
178
  ui.warn("No knife configuration file found") if config_loader.no_config_found?
170
- @@chef_config_dir = config_loader.chef_config_dir
171
179
 
172
180
  config_loader
173
181
  rescue Exceptions::ConfigurationError => e
@@ -176,7 +184,7 @@ class Chef
176
184
  end
177
185
 
178
186
  def self.chef_config_dir
179
- @@chef_config_dir
187
+ @@chef_config_dir ||= config_loader.chef_config_dir
180
188
  end
181
189
 
182
190
  # Run knife for the given +args+ (ARGV), adding +options+ to the list of
@@ -194,13 +194,15 @@ class Chef
194
194
  :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
195
195
  :boolean => true
196
196
 
197
+ def default_bootstrap_template
198
+ "chef-full"
199
+ end
200
+
197
201
  def bootstrap_template
198
- # For some reason knife.merge_configs doesn't pick up the default values from
199
- # Chef::Config[:knife][:bootstrap_template] unless Chef::Config[:knife][:bootstrap_template]
200
- # is forced to pick up the values before calling merge_configs.
201
- # We therefore have Chef::Config[:knife][:bootstrap_template] to pick up the defaults
202
- # if no option is specified.
203
- config[:bootstrap_template] || config[:distro] || config[:template_file] || Chef::Config[:knife][:bootstrap_template]
202
+ # The order here is important. We want to check if we have the new Chef 12 option is set first.
203
+ # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
204
+ # the end.
205
+ config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
204
206
  end
205
207
 
206
208
  def find_template
@@ -210,7 +212,6 @@ class Chef
210
212
  if File.exists?(template)
211
213
  Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
212
214
  return template
213
-
214
215
  end
215
216
 
216
217
  # Otherwise search the template directories until we find the right one
@@ -27,16 +27,6 @@ class Chef
27
27
  # http://trac.misuse.org/science/wiki/DeepMerge
28
28
  module DeepMerge
29
29
 
30
- class InvalidSubtractiveMerge < ArgumentError; end
31
-
32
- OLD_KNOCKOUT_PREFIX = "!merge:".freeze
33
-
34
- # Regex to match the "knockout prefix" that was used to indicate
35
- # subtractive merging in Chef 10.x and previous. Subtractive merging is
36
- # removed as of Chef 11, but we detect attempted use of it and raise an
37
- # error (see: raise_if_knockout_used!)
38
- OLD_KNOCKOUT_MATCH = %r[!merge].freeze
39
-
40
30
  extend self
41
31
 
42
32
  def merge(first, second)
@@ -46,15 +36,6 @@ class Chef
46
36
  DeepMerge.deep_merge(second, first)
47
37
  end
48
38
 
49
- # Inherited roles use the knockout_prefix array subtraction functionality
50
- # This is likely to go away in Chef >= 0.11
51
- def role_merge(first, second)
52
- first = Mash.new(first) unless first.kind_of?(Mash)
53
- second = Mash.new(second) unless second.kind_of?(Mash)
54
-
55
- DeepMerge.deep_merge(second, first)
56
- end
57
-
58
39
  class InvalidParameter < StandardError; end
59
40
 
60
41
  # Deep Merge core documentation.
@@ -77,22 +58,15 @@ class Chef
77
58
  dest = source; return dest
78
59
  end
79
60
 
80
- raise_if_knockout_used!(source)
81
- raise_if_knockout_used!(dest)
82
61
  case source
83
62
  when nil
84
63
  dest
85
64
  when Hash
86
65
  if dest.kind_of?(Hash)
87
66
  source.each do |src_key, src_value|
88
- if dest.has_key? src_key
89
- if dest[src_key].nil?
90
- dest[src_key] = nil
91
- else
92
- dest[src_key] = deep_merge!(src_value, dest[src_key])
93
- end
67
+ if dest[src_key]
68
+ dest[src_key] = deep_merge!(src_value, dest[src_key])
94
69
  else # dest[src_key] doesn't exist so we take whatever source has
95
- raise_if_knockout_used!(src_value)
96
70
  dest[src_key] = src_value
97
71
  end
98
72
  end
@@ -131,11 +105,19 @@ class Chef
131
105
  # If there are two Hashes, recursively merge.
132
106
  if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
133
107
  merge_with.each do |key, merge_with_value|
134
- merge_onto[key] = if merge_onto.has_key?(key)
135
- hash_only_merge(merge_onto[key], merge_with_value)
136
- else
137
- merge_with_value
138
- end
108
+ value =
109
+ if merge_onto.has_key?(key)
110
+ hash_only_merge(merge_onto[key], merge_with_value)
111
+ else
112
+ merge_with_value
113
+ end
114
+
115
+ if merge_onto.respond_to?(:public_method_that_only_deep_merge_should_use)
116
+ # we can't call ImmutableMash#[]= because its immutable, but we need to mutate it to build it in-place
117
+ merge_onto.public_method_that_only_deep_merge_should_use(key, value)
118
+ else
119
+ merge_onto[key] = value
120
+ end
139
121
  end
140
122
  merge_onto
141
123
 
@@ -149,27 +131,6 @@ class Chef
149
131
  end
150
132
  end
151
133
 
152
- # Checks for attempted use of subtractive merge, which was removed for
153
- # Chef 11.0. If subtractive merge use is detected, will raise an
154
- # InvalidSubtractiveMerge exception.
155
- def raise_if_knockout_used!(obj)
156
- if uses_knockout?(obj)
157
- raise InvalidSubtractiveMerge, "subtractive merge with !merge is no longer supported"
158
- end
159
- end
160
-
161
- # Checks for attempted use of subtractive merge in +obj+.
162
- def uses_knockout?(obj)
163
- case obj
164
- when String
165
- obj =~ OLD_KNOCKOUT_MATCH
166
- when Array
167
- obj.any? {|element| element.respond_to?(:gsub) && element =~ OLD_KNOCKOUT_MATCH }
168
- else
169
- false
170
- end
171
- end
172
-
173
134
  def deep_merge(source, dest)
174
135
  deep_merge!(safe_dup(source), safe_dup(dest))
175
136
  end