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.
- data/lib/chef/knife/raw.rb +3 -4
- data/lib/chef/sandbox_uploader.rb +208 -0
- data/lib/chef_fs/command_line.rb +90 -50
- data/lib/chef_fs/file_system.rb +123 -17
- data/lib/chef_fs/file_system/base_fs_object.rb +37 -4
- data/lib/chef_fs/file_system/chef_repository_file_system_entry.rb +25 -0
- data/lib/chef_fs/file_system/cookbook_dir.rb +45 -11
- data/lib/chef_fs/file_system/cookbook_file.rb +26 -0
- data/lib/chef_fs/file_system/file_system_entry.rb +2 -2
- data/lib/chef_fs/file_system/rest_list_entry.rb +17 -5
- data/lib/chef_fs/version.rb +1 -1
- data/spec/chef_fs/diff_spec.rb +3 -77
- data/spec/chef_fs/file_pattern_spec.rb +3 -2
- data/spec/chef_fs/file_system/chef_server_root_dir_spec.rb +1 -0
- data/spec/chef_fs/file_system/cookbooks_dir_spec.rb +2 -1
- data/spec/chef_fs/file_system/data_bags_dir_spec.rb +2 -1
- data/spec/chef_fs/file_system_spec.rb +1 -1
- data/spec/support/file_system_support.rb +0 -22
- data/spec/support/spec_helper.rb +8 -0
- metadata +4 -3
- data/lib/chef_fs/diff.rb +0 -167
data/lib/chef/knife/raw.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/chef_fs/command_line.rb
CHANGED
@@ -1,115 +1,155 @@
|
|
1
|
-
require 'chef_fs/
|
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::
|
7
|
+
ChefFS::FileSystem.list_pairs(pattern, a_root, b_root) do |a, b|
|
8
8
|
found_result = true
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
39
|
+
yield "#{new_entry.path_for_printing}\n"
|
33
40
|
elsif output_mode == :name_status
|
34
|
-
|
41
|
+
yield "T\t#{new_entry.path_for_printing}\n"
|
35
42
|
else
|
36
|
-
|
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
|
-
|
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
|
-
|
49
|
+
yield "#{new_entry.path_for_printing}\n"
|
41
50
|
elsif output_mode == :name_status
|
42
|
-
|
51
|
+
yield "D\t#{new_entry.path_for_printing}\n"
|
43
52
|
else
|
44
|
-
|
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
|
51
|
-
if
|
58
|
+
elsif new_entry.dir?
|
59
|
+
if old_entry.exists?
|
52
60
|
if output_mode == :name_only
|
53
|
-
|
61
|
+
yield "#{new_entry.path_for_printing}\n"
|
54
62
|
elsif output_mode == :name_status
|
55
|
-
|
63
|
+
yield "T\t#{new_entry.path_for_printing}\n"
|
56
64
|
else
|
57
|
-
|
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
|
-
|
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
|
-
|
71
|
+
yield "#{new_entry.path_for_printing}\n"
|
62
72
|
elsif output_mode == :name_status
|
63
|
-
|
73
|
+
yield "A\t#{new_entry.path_for_printing}\n"
|
64
74
|
else
|
65
|
-
|
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
|
-
|
72
|
-
if
|
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 !
|
76
|
-
return
|
100
|
+
if !old_exists && !old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
|
101
|
+
return
|
77
102
|
end
|
78
|
-
if !
|
79
|
-
return
|
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
|
-
|
108
|
+
yield "#{new_entry.path_for_printing}\n"
|
84
109
|
elsif output_mode == :name_status
|
85
|
-
if !
|
86
|
-
|
87
|
-
elsif
|
88
|
-
|
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
|
-
|
115
|
+
yield "M\t#{new_entry.path_for_printing}\n"
|
91
116
|
end
|
92
117
|
else
|
93
|
-
|
94
|
-
|
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
|
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
|
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) }
|
data/lib/chef_fs/file_system.rb
CHANGED
@@ -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',
|
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
|
-
|
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
|
-
|
101
|
+
copy_entries(a, b, recurse_depth, options)
|
101
102
|
end
|
102
103
|
if !found_result && pattern.exact_path
|
103
|
-
|
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
|
110
|
-
def self.
|
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
|
-
|
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
|
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 =
|
292
|
+
src_value = nil
|
184
293
|
else
|
185
|
-
|
186
|
-
|
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
|
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
|