knife-essentials 0.5.4 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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