knife-essentials 0.5.4 → 0.6

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.
@@ -64,8 +64,7 @@ class Chef
64
64
  chef_rest.retriable_rest_request(method, url, json_body, headers) do |rest_request|
65
65
  response = rest_request.call {|r| r.read_body}
66
66
 
67
- # response_body = chef_rest.decompress_body(response)
68
- response_body = response.body
67
+ response_body = chef_rest.decompress_body(response)
69
68
 
70
69
  if response.kind_of?(Net::HTTPSuccess)
71
70
  if config[:pretty] && response['content-type'] =~ /json/
@@ -78,7 +77,7 @@ class Chef
78
77
  follow_redirect {api_request(:GET, create_url(redirect_location))}
79
78
  else
80
79
  # have to decompress the body before making an exception for it. But the body could be nil.
81
- # response.body.replace(decompress_body(response)) if response.body.respond_to?(:replace)
80
+ response.body.replace(chef_rest.decompress_body(response)) if response.body.respond_to?(:replace)
82
81
 
83
82
  if response['content-type'] =~ /json/
84
83
  exception = response_body
@@ -98,7 +97,7 @@ class Chef
98
97
  headers['Accept'] = "application/json" unless raw
99
98
  headers["Content-Type"] = 'application/json' if json_body
100
99
  headers['Content-Length'] = json_body.bytesize.to_s if json_body
101
- # headers[Chef::REST::RESTRequest::ACCEPT_ENCODING] = Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE
100
+ headers[Chef::REST::RESTRequest::ACCEPT_ENCODING] = Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE
102
101
  headers.merge!(chef_rest.authentication_headers(method, url, json_body)) if chef_rest.sign_requests?
103
102
  headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers]
104
103
  headers
@@ -0,0 +1,208 @@
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2009 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+
20
+ class Chef
21
+ #
22
+ # Represents a sandbox uploading session. You pass in a set of files with
23
+ #
24
+ # Much of this is shamelessly stolen from Chef::CookbookUploader, placed at
25
+ # the sandbox level to allow any set of files to be uploaded and to allow
26
+ # uploading to plug in to the host's threading model (if any).
27
+ #
28
+ # A single-threaded caller will want to:
29
+ #
30
+ # uploader = SandboxUploader.new(checksum_files, rest)
31
+ # while uploader.upload_next_file do
32
+ # end
33
+ # uploader.commit
34
+ #
35
+ # A multithreaded caller will want to keep doing this:
36
+ #
37
+ # if !uploader.upload_next_file
38
+ # uploader.commit
39
+ class SandboxUploader
40
+ # Create a new SandboxUploader.
41
+ #
42
+ # ==== Attributes
43
+ #
44
+ # * +checksums+ - a list of checksums of files you plan to upload
45
+ # * +rest+ - the rest client to use
46
+ #
47
+ # ==== Throws
48
+ #
49
+ # This method will throw an HTTP exception if the sandbox creation fails.
50
+ def initialize(checksum, rest)
51
+ # Grab the list of URLs we need to upload to
52
+ checksums_hash = checksums.inject({}) { |hash,checksum| hash[checksum] = nil; hash }
53
+ response = rest.post_rest("sandboxes", { :checksums => checksums_hash })
54
+ @sandbox_url = response['uri']
55
+ @to_upload = []
56
+ response['checksums'].each do |checksum, info|
57
+ if info['needs_upload'] == true
58
+ @to_upload << {
59
+ :checksum => checksum,
60
+ :url => info['url']
61
+ }
62
+ else
63
+ Chef::Log.debug("#{checksum_files[checksum]} has not changed")
64
+ end
65
+ end
66
+ @uploading = 0
67
+ end
68
+
69
+ # Picks a file off the list and uploads it.
70
+ #
71
+ # ==== Parameters
72
+ #
73
+ # * +&block+ - if there is a file to upload, +block.call(checksum)+ will be
74
+ # invoked, and this should return the file contents for the given
75
+ # checksum.
76
+ #
77
+ # ==== Throws
78
+ #
79
+ # An HTTP error is thrown if the upload fails.
80
+ #
81
+ # ==== Returns
82
+ #
83
+ # +{ :checksum => checksum, :url => url }+ if a file was uploaded. +nil+ if
84
+ # there was no file left to upload.
85
+ def upload_next_file(&block)
86
+ @uploading += 1
87
+ begin
88
+ file = @to_upload.pop
89
+ return nil if !file
90
+
91
+ begin
92
+ return upload_file(block.call(file[:checksum]), file[:checksum], file[:url])
93
+ rescue
94
+ # Stick the work on top so we work on other things first
95
+ @to_upload.unshift(file)
96
+ raise
97
+ end
98
+ ensure
99
+ @uploading -= 1
100
+ end
101
+ end
102
+
103
+ # Commits the sandbox to Chef.
104
+ #
105
+ # ==== Throws
106
+ #
107
+ # * An HTTP error if the commit fails.
108
+ # * "Attempt to commit "
109
+ #
110
+ # ==== Returns
111
+ #
112
+ # +true+ if a commit occurred. +false+ if commit
113
+ # has already been called.
114
+ def commit
115
+ return false if !@sandbox_url
116
+ raise "Attempt to commit before uploads are complete!" if !uploads_complete?
117
+ # nil out sandbox url to avoid race conditions where multiple
118
+ # callers try to commit
119
+ sandbox_url = @sandbox_url
120
+ @sandbox_url = nil
121
+ @to_upload = nil
122
+
123
+ Chef::Log.debug("Committing sandbox")
124
+ # Retry if S3 is claims a checksum doesn't exist (the eventual
125
+ # in eventual consistency)
126
+ retries = 0
127
+ begin
128
+ rest.put_rest(sandbox_url, {:is_completed => true})
129
+ rescue Net::HTTPServerException => e
130
+ if e.message =~ /^400/ && (retries += 1) <= 5
131
+ sleep 2
132
+ retry
133
+ else
134
+ raise
135
+ end
136
+ end
137
+ true
138
+ end
139
+
140
+ def cancel
141
+ # Yeah, so uh, delete doesn't work. But we'd like to imagine it will.
142
+ sandbox_url = @sandbox_url
143
+ @sandbox_url = nil
144
+ @to_upload = nil
145
+ rest.delete_rest(sandbox_url)
146
+ end
147
+
148
+ # Tells whether uploads are all complete.
149
+ def uploads_complete?
150
+ @to_upload.length == 0 && @uploading == 0
151
+ end
152
+
153
+ private
154
+
155
+ def upload_file(file_contents, checksum, url)
156
+ # Checksum is the hexadecimal representation of the md5,
157
+ # but we need the base64 encoding for the content-md5
158
+ # header
159
+ checksum64 = Base64.encode64([checksum].pack("H*")).strip
160
+ timestamp = Time.now.utc.iso8601
161
+ # TODO - 5/28/2010, cw: make signing and sending the request streaming
162
+ sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
163
+ :http_method => :put,
164
+ :path => URI.parse(url).path,
165
+ :body => file_contents,
166
+ :timestamp => timestamp,
167
+ :user_id => rest.client_name
168
+ )
169
+ headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' }
170
+ headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
171
+
172
+ begin
173
+ RestClient::Resource.new(url, :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents)
174
+ rescue RestClient::Exception => e
175
+ Chef::Knife.ui.error("Failed to upload #@cookbook : #{e.message}\n#{e.response.body}")
176
+ raise
177
+ end
178
+ end
179
+
180
+ # For CookbookDir
181
+
182
+ private
183
+
184
+ def upload(entries, force, freeze)
185
+ # knife cookbook upload: Validate environment
186
+ # knife cookbook upload: Warn about cookbook shadowing
187
+ # knife cookbook upload: Check for broken links
188
+ # knife cookbook upload: Check for dependencies
189
+ # CookbookUploader: Validate cookbook syntax, ruby files and templates
190
+ # SandboxUploader!
191
+ entries_by_checksum = entries.inject({}) do |hash,entry|
192
+ if entry.respond_to?(:checksum)
193
+ checksum = entry.checksum
194
+ else
195
+ checksum = ChefFS::Diff.calc_checksum(entry.read)
196
+ end
197
+ hash[checksum] = entry
198
+ end
199
+
200
+ uploader = SandboxUploader.new(entries_by_checksum.keys, rest)
201
+ while uploader.upload_next_file { |checksum| entries_by_checksum[checksum].read }
202
+ end
203
+ uploader.commit
204
+ # CookbookUploader: PUT the manifest to either the force_save_url or save_url for the cookbook
205
+ # knife cookbook upload: update version constraints for environment
206
+ end
207
+ end
208
+ end
@@ -1,115 +1,155 @@
1
- require 'chef_fs/diff'
1
+ require 'chef_fs/file_system'
2
2
 
3
3
  module ChefFS
4
4
  module CommandLine
5
5
  def self.diff(pattern, a_root, b_root, recurse_depth, output_mode)
6
6
  found_result = false
7
- ChefFS::Diff::diffable_leaves_from_pattern(pattern, a_root, b_root, recurse_depth) do |a_leaf, b_leaf|
7
+ ChefFS::FileSystem.list_pairs(pattern, a_root, b_root) do |a, b|
8
8
  found_result = true
9
- diff = diff_leaves(a_leaf, b_leaf, output_mode)
10
- yield diff if diff != ''
9
+ diff_entries(a, b, recurse_depth, output_mode) do |diff|
10
+ yield diff
11
+ end
11
12
  end
12
13
  if !found_result && pattern.exact_path
13
14
  yield "#{pattern}: No such file or directory on remote or local"
14
15
  end
15
16
  end
16
17
 
17
- private
18
-
19
- # Diff two known leaves (could be files or dirs)
20
- def self.diff_leaves(old_file, new_file, output_mode)
21
- result = ''
18
+ # Diff two known entries (could be files or dirs)
19
+ def self.diff_entries(old_entry, new_entry, recurse_depth, output_mode)
22
20
  # If both are directories
23
- # If old is a directory and new is a file
24
- # If old is a directory and new does not exist
25
- if old_file.dir?
26
- if new_file.dir?
27
- if output_mode != :name_only && output_mode != :name_status
28
- result << "Common subdirectories: #{old_file.path}\n"
21
+ if old_entry.dir?
22
+ if new_entry.dir?
23
+ if recurse_depth == 0
24
+ if output_mode != :name_only && output_mode != :name_status
25
+ yield "Common subdirectories: #{old_entry.path}\n"
26
+ end
27
+ else
28
+ ChefFS::FileSystem.child_pairs(old_entry, new_entry).each do |old_child,new_child|
29
+ diff_entries(old_child, new_child,
30
+ recurse_depth ? recurse_depth - 1 : nil, output_mode) do |diff|
31
+ yield diff
32
+ end
33
+ end
29
34
  end
30
- elsif new_file.exists?
35
+
36
+ # If old is a directory and new is a file
37
+ elsif new_entry.exists?
31
38
  if output_mode == :name_only
32
- result << "#{new_file.path_for_printing}\n"
39
+ yield "#{new_entry.path_for_printing}\n"
33
40
  elsif output_mode == :name_status
34
- result << "T\t#{new_file.path_for_printing}\n"
41
+ yield "T\t#{new_entry.path_for_printing}\n"
35
42
  else
36
- result << "File #{new_file.path_for_printing} is a directory while file #{new_file.path_for_printing} is a regular file\n"
43
+ yield "File #{new_entry.path_for_printing} is a directory while file #{new_entry.path_for_printing} is a regular file\n"
37
44
  end
38
- elsif new_file.parent.can_have_child?(old_file.name, old_file.dir?)
45
+
46
+ # If old is a directory and new does not exist
47
+ elsif new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
39
48
  if output_mode == :name_only
40
- result << "#{new_file.path_for_printing}\n"
49
+ yield "#{new_entry.path_for_printing}\n"
41
50
  elsif output_mode == :name_status
42
- result << "D\t#{new_file.path_for_printing}\n"
51
+ yield "D\t#{new_entry.path_for_printing}\n"
43
52
  else
44
- result << "Only in #{old_file.parent.path_for_printing}: #{old_file.name}\n"
53
+ yield "Only in #{old_entry.parent.path_for_printing}: #{old_entry.name}\n"
45
54
  end
46
55
  end
47
56
 
48
- # If new is a directory and old does not exist
49
57
  # If new is a directory and old is a file
50
- elsif new_file.dir?
51
- if old_file.exists?
58
+ elsif new_entry.dir?
59
+ if old_entry.exists?
52
60
  if output_mode == :name_only
53
- result << "#{new_file.path_for_printing}\n"
61
+ yield "#{new_entry.path_for_printing}\n"
54
62
  elsif output_mode == :name_status
55
- result << "T\t#{new_file.path_for_printing}\n"
63
+ yield "T\t#{new_entry.path_for_printing}\n"
56
64
  else
57
- result << "File #{old_file.path_for_printing} is a regular file while file #{old_file.path_for_printing} is a directory\n"
65
+ yield "File #{old_entry.path_for_printing} is a regular file while file #{old_entry.path_for_printing} is a directory\n"
58
66
  end
59
- elsif old_file.parent.can_have_child?(new_file.name, new_file.dir?)
67
+
68
+ # If new is a directory and old does not exist
69
+ elsif old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
60
70
  if output_mode == :name_only
61
- result << "#{new_file.path_for_printing}\n"
71
+ yield "#{new_entry.path_for_printing}\n"
62
72
  elsif output_mode == :name_status
63
- result << "A\t#{new_file.path_for_printing}\n"
73
+ yield "A\t#{new_entry.path_for_printing}\n"
64
74
  else
65
- result << "Only in #{new_file.parent.path_for_printing}: #{new_file.name}\n"
75
+ yield "Only in #{new_entry.parent.path_for_printing}: #{new_entry.name}\n"
66
76
  end
67
77
  end
68
78
 
69
79
  else
70
80
  # Neither is a directory, so they are diffable with file diff
71
- different, old_value, new_value = ChefFS::Diff::diff_files(old_file, new_file)
72
- if different
81
+ are_same, old_value, new_value = ChefFS::FileSystem.compare(old_entry, new_entry)
82
+ if !are_same
83
+ if old_value == :none
84
+ old_exists = false
85
+ elsif old_value.nil?
86
+ old_exists = old_entry.exists?
87
+ else
88
+ old_exists = true
89
+ end
90
+ if new_value == :none
91
+ new_exists = false
92
+ elsif new_value.nil?
93
+ new_exists = new_entry.exists?
94
+ else
95
+ new_exists = true
96
+ end
97
+
73
98
  # If one of the files doesn't exist, we only want to print the diff if the
74
99
  # other file *could be uploaded/downloaded*.
75
- if !old_value && !old_file.parent.can_have_child?(new_file.name, new_file.dir?)
76
- return result
100
+ if !old_exists && !old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
101
+ return
77
102
  end
78
- if !new_value && !new_file.parent.can_have_child?(old_file.name, old_file.dir?)
79
- return result
103
+ if !new_exists && !new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
104
+ return
80
105
  end
81
106
 
82
107
  if output_mode == :name_only
83
- result << "#{new_file.path_for_printing}\n"
108
+ yield "#{new_entry.path_for_printing}\n"
84
109
  elsif output_mode == :name_status
85
- if !old_value
86
- result << "A\t#{new_file.path_for_printing}\n"
87
- elsif !new_value
88
- result << "D\t#{new_file.path_for_printing}\n"
110
+ if old_value == :none || (old_value == nil && !old_entry.exists?)
111
+ yield "A\t#{new_entry.path_for_printing}\n"
112
+ elsif new_value == :none
113
+ yield "D\t#{new_entry.path_for_printing}\n"
89
114
  else
90
- result << "M\t#{new_file.path_for_printing}\n"
115
+ yield "M\t#{new_entry.path_for_printing}\n"
91
116
  end
92
117
  else
93
- old_path = old_file.path_for_printing
94
- new_path = new_file.path_for_printing
118
+ # If we haven't read the values yet, get them now.
119
+ begin
120
+ old_value = old_entry.read if old_value.nil?
121
+ rescue ChefFS::FileSystem::NotFoundError
122
+ old_value = :none
123
+ end
124
+ begin
125
+ new_value = new_entry.read if new_value.nil?
126
+ rescue ChefFS::FileSystem::NotFoundError
127
+ new_value = :none
128
+ end
129
+
130
+ old_path = old_entry.path_for_printing
131
+ new_path = new_entry.path_for_printing
132
+ result = ''
95
133
  result << "diff --knife #{old_path} #{new_path}\n"
96
- if !old_value
134
+ if old_value == :none
97
135
  result << "new file\n"
98
136
  old_path = "/dev/null"
99
137
  old_value = ''
100
138
  end
101
- if !new_value
139
+ if new_value == :none
102
140
  result << "deleted file\n"
103
141
  new_path = "/dev/null"
104
142
  new_value = ''
105
143
  end
106
144
  result << diff_text(old_path, new_path, old_value, new_value)
145
+ yield result
107
146
  end
108
147
  end
109
148
  end
110
- return result
111
149
  end
112
150
 
151
+ private
152
+
113
153
  def self.sort_keys(json_object)
114
154
  if json_object.is_a?(Array)
115
155
  json_object.map { |o| sort_keys(o) }
@@ -1,5 +1,4 @@
1
1
  require 'chef_fs/path_utils'
2
- require 'chef_fs/diff'
3
2
 
4
3
  module ChefFS
5
4
  module FileSystem
@@ -90,24 +89,114 @@ module ChefFS
90
89
  #
91
90
  # ==== Examples
92
91
  #
93
- # ChefFS::FileSystem.copy_to(FilePattern.new('/cookbooks', chef_fs, local_fs, nil, true)
92
+ # ChefFS::FileSystem.copy_to(FilePattern.new('/cookbooks'),
93
+ # chef_fs, local_fs, nil, true) do |message|
94
+ # puts message
95
+ # end
94
96
  #
95
97
  def self.copy_to(pattern, src_root, dest_root, recurse_depth, options)
96
98
  found_result = false
97
- # Find things we might want to copy
98
- ChefFS::Diff::diffable_leaves_from_pattern(pattern, src_root, dest_root, recurse_depth) do |src_leaf, dest_leaf, child_recurse_depth|
99
+ list_pairs(pattern, src_root, dest_root) do |a, b|
99
100
  found_result = true
100
- copy_leaves(src_leaf, dest_leaf, child_recurse_depth, options)
101
+ copy_entries(a, b, recurse_depth, options)
101
102
  end
102
103
  if !found_result && pattern.exact_path
103
- yield "#{pattern}: No such file or directory on remote or local"
104
+ puts "#{pattern}: No such file or directory on remote or local"
104
105
  end
105
106
  end
106
107
 
108
+ # Yield entries for children that are in either +a_root+ or +b_root+, with
109
+ # matching pairs matched up.
110
+ #
111
+ # ==== Yields
112
+ #
113
+ # Yields matching entries in pairs:
114
+ #
115
+ # [ a_entry, b_entry ]
116
+ #
117
+ # ==== Example
118
+ #
119
+ # ChefFS::FileSystem.list_pairs(FilePattern.new('**x.txt', a_root, b_root)) do |a, b|
120
+ # ...
121
+ # end
122
+ #
123
+ def self.list_pairs(pattern, a_root, b_root)
124
+ # Make sure everything on the server is also on the filesystem, and diff
125
+ found_paths = Set.new
126
+ ChefFS::FileSystem.list(a_root, pattern) do |a|
127
+ found_paths << a.path
128
+ b = ChefFS::FileSystem.resolve_path(b_root, a.path)
129
+ yield [ a, b ]
130
+ end
131
+
132
+ # Check the outer regex pattern to see if it matches anything on the
133
+ # filesystem that isn't on the server
134
+ ChefFS::FileSystem.list(b_root, pattern) do |b|
135
+ if !found_paths.include?(b.path)
136
+ a = ChefFS::FileSystem.resolve_path(a_root, b.path)
137
+ yield [ a, b ]
138
+ end
139
+ end
140
+ end
141
+
142
+ # Get entries for children of either a or b, with matching pairs matched up.
143
+ #
144
+ # ==== Returns
145
+ #
146
+ # An array of child pairs.
147
+ #
148
+ # [ [ a_child, b_child ], ... ]
149
+ #
150
+ # If a child is only in a or only in b, the other child entry will be
151
+ # retrieved by name (and will most likely be a "nonexistent child").
152
+ #
153
+ # ==== Example
154
+ #
155
+ # ChefFS::FileSystem.child_pairs(a, b).length
156
+ #
157
+ def self.child_pairs(a, b)
158
+ # If both are directories, recurse into them and diff the children instead of returning ourselves.
159
+ result = []
160
+ a_children_names = Set.new
161
+ a.children.each do |a_child|
162
+ a_children_names << a_child.name
163
+ result << [ a_child, b.child(a_child.name) ]
164
+ end
165
+
166
+ # Check b for children that aren't in a
167
+ b.children.each do |b_child|
168
+ if !a_children_names.include?(b_child.name)
169
+ result << [ a.child(b_child.name), b_child ]
170
+ end
171
+ end
172
+ result
173
+ end
174
+
175
+ def self.compare(a, b)
176
+ are_same, a_value, b_value = a.compare_to(b)
177
+ if are_same.nil?
178
+ are_same, b_value, a_value = b.compare_to(a)
179
+ end
180
+ if are_same.nil?
181
+ begin
182
+ a_value = a.read if a_value.nil?
183
+ rescue ChefFS::FileSystem::NotFoundError
184
+ a_value = :none
185
+ end
186
+ begin
187
+ b_value = b.read if b_value.nil?
188
+ rescue ChefFS::FileSystem::NotFoundError
189
+ b_value = :none
190
+ end
191
+ are_same = (a_value == b_value)
192
+ end
193
+ [ are_same, a_value, b_value ]
194
+ end
195
+
107
196
  private
108
197
 
109
- # Copy two known leaves (could be files or dirs)
110
- def self.copy_leaves(src_entry, dest_entry, recurse_depth, options)
198
+ # Copy two entries (could be files or dirs)
199
+ def self.copy_entries(src_entry, dest_entry, recurse_depth, options)
111
200
  # A NOTE about this algorithm:
112
201
  # There are cases where this algorithm does too many network requests.
113
202
  # knife upload with a specific filename will first check if the file
@@ -148,7 +237,7 @@ module ChefFS
148
237
  if recurse_depth != 0
149
238
  src_entry.children.each do |src_child|
150
239
  new_dest_child = new_dest_dir.child(src_child.name)
151
- copy_leaves(src_child, new_dest_child, recurse_depth ? recurse_depth - 1 : recurse_depth, options)
240
+ copy_entries(src_child, new_dest_child, recurse_depth ? recurse_depth - 1 : recurse_depth, options)
152
241
  end
153
242
  end
154
243
  else
@@ -163,10 +252,30 @@ module ChefFS
163
252
 
164
253
  else
165
254
  # Both exist.
255
+
256
+ # If the entry can do a copy directly, do that.
257
+ if dest_entry.respond_to?(:copy_from)
258
+ if options[:force] || compare(src_entry, dest_entry)[0] == false
259
+ if options[:dry_run]
260
+ puts "Would update #{dest_entry.path_for_printing}"
261
+ else
262
+ puts "Updating #{dest_entry.path_for_printing} ..."
263
+ dest_entry.copy_from(src_entry)
264
+ puts "Updated #{dest_entry.path_for_printing}"
265
+ end
266
+ end
267
+ return
268
+ end
269
+
166
270
  # If they are different types, log an error.
167
271
  if src_entry.dir?
168
272
  if dest_entry.dir?
169
- # If they are both directories, we'll end up recursing later.
273
+ # If both are directories, recurse into their children
274
+ if recurse_depth != 0
275
+ child_pairs(src_entry, dest_entry).each do |src_child, dest_child|
276
+ copy_entries(src_child, dest_child, recurse_depth ? recurse_depth - 1 : recurse_depth, options)
277
+ end
278
+ end
170
279
  else
171
280
  # If they are different types.
172
281
  Chef::Log.error("File #{dest_entry.path_for_printing} is a directory while file #{dest_entry.path_for_printing} is a regular file\n")
@@ -180,19 +289,16 @@ module ChefFS
180
289
  # Both are files! Copy them unless we're sure they are the same.
181
290
  if options[:force]
182
291
  should_copy = true
183
- src_value = src_entry.read
292
+ src_value = nil
184
293
  else
185
- should_copy, src_value, dest_value = ChefFS::Diff.diff_files(src_entry, dest_entry)
186
- src_value = src_entry.read if src_value == :not_retrieved
187
- if should_copy == nil
188
- should_copy = true
189
- end
294
+ are_same, src_value, dest_value = compare(src_entry, dest_entry)
295
+ should_copy = !are_same
190
296
  end
191
297
  if should_copy
192
298
  if options[:dry_run]
193
299
  puts "Would update #{dest_entry.path_for_printing}"
194
300
  else
195
- src_value = src_entry.read if src_value == :not_retrieved
301
+ src_value = src_entry.read if src_value.nil?
196
302
  dest_entry.write(src_value)
197
303
  puts "Updated #{dest_entry.path_for_printing}"
198
304
  end