chef 12.0.0.rc.0 → 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +141 -191
  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: 1bef47bedec10e78a63b563578b47919c68066b9
4
+ data.tar.gz: 2073eef92ea6cc252afa09d2c047de4798d6c997
5
+ SHA512:
6
+ metadata.gz: efd4822d4ef0e0b4c01666d8093faf3a238cab14b3e421d802be7e19b666d51b2ba39e1c710cbd1e0dee6e8210ba4539a77e9a71aa2a889d317eb191f3f15897
7
+ data.tar.gz: 4a7d25fc7c1592c5484e7c47b8bfa38c5db25fed17c6d08c76a8e2d226ab173db93d3f7063c74462e148e8f865c29b5b59fc9bfb4179a1fbe73e019e0e3ab957
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