diff options
author | John Keiser <john@johnkeiser.com> | 2015-03-04 09:38:13 -0800 |
---|---|---|
committer | John Keiser <john@johnkeiser.com> | 2015-03-04 09:38:58 -0800 |
commit | 583c3ba1913b4e1a25452e949ccd491b0b9649db (patch) | |
tree | ea7e7f9550783fb547938db87022cf41fbece14a | |
parent | 0bb2861228fd33fd6a4ef296a1dd223abb6dec9f (diff) | |
download | chef-jk/easier_chef_fs.tar.gz |
Move copy algorithm to instantiable class to enhance readabilityjk/easier_chef_fs
-rw-r--r-- | lib/chef/chef_fs/chef_fs_algorithm.rb | 69 | ||||
-rw-r--r-- | lib/chef/chef_fs/copy_algorithm.rb | 197 | ||||
-rw-r--r-- | lib/chef/chef_fs/file_system.rb | 185 |
3 files changed, 269 insertions, 182 deletions
diff --git a/lib/chef/chef_fs/chef_fs_algorithm.rb b/lib/chef/chef_fs/chef_fs_algorithm.rb new file mode 100644 index 0000000000..62ed06974e --- /dev/null +++ b/lib/chef/chef_fs/chef_fs_algorithm.rb @@ -0,0 +1,69 @@ +require 'chef/chef_fs/parallelizer' + +class Chef + module ChefFS + class ChefFSAlgorithm + def initialize(ui: nil, dry_run: dry_run, format_path: format_path) + @ui = ui + @dry_run = dry_run + @format_path = format_path + end + + attr_reader :ui + + def dry_run? + @dry_run + end + + def options + { + dry_run: dry_run?, + format_path: @format_path + } + end + + def converge(description, dry_run_text: nil, dry_run_block: nil, action: nil, &block) + if dry_run? + dry_run_text ||= "Would have #{description[0..0].downcase}#{description[1..-1]}" + begin + result = dry_run_block.call if dry_run_block + output dry_run_text + return result + rescue + error "Error during attempt to #{description}" + raise + end + + else + action ||= block + begin + result = action.call if action + output description + return result + rescue + error "Error during attempt to #{description}" + raise + end + end + end + + def parallel_do(enum, options = {}, &block) + Chef::ChefFS::Parallelizer.parallel_do(enum, options, &block) + end + + def output(string) + ui.output string if ui + end + def warn(string) + ui.warn string if ui + end + def error(string) + ui.error string if ui + end + + def format_path(entry) + @format_path ? @format_path.call(entry) : entry.path + end + end + end +end diff --git a/lib/chef/chef_fs/copy_algorithm.rb b/lib/chef/chef_fs/copy_algorithm.rb new file mode 100644 index 0000000000..0880d17ee5 --- /dev/null +++ b/lib/chef/chef_fs/copy_algorithm.rb @@ -0,0 +1,197 @@ +require 'chef/chef_fs/chef_fs_algorithm' +require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error' +require 'chef/chef_fs/file_system/operation_failed_error' +require 'chef/chef_fs/file_system/operation_not_allowed_error' + +class Chef + module ChefFS + class CopyEntries < ChefFSAlgorithm + def initialize(purge: false, max_recurse_depth: nil, force: nil, diff: true, format_path: nil, **options) + @max_recurse_depth = max_recurse_depth + @purge = purge + @force = force + @diff = diff + super(**options) + end + + attr_reader :max_recurse_depth + def purge? + @purge + end + def diff? + @diff + end + + def options + @options ||= super.merge({ + max_recurse_depth: max_recurse_depth, + purge: purge?, + diff: diff? + }) + end + + def copy_to(pattern, src_root, dest_root, recurse_depth, options, ui = nil, format_path = nil) + found_result = false + error = false + parallel_do(FileSystem.list_pairs(pattern, src_root, dest_root)) do |src, dest| + found_result = true + new_dest_parent = get_or_create_parent(dest, options, ui, format_path) + child_error = copy_entries(src, dest, new_dest_parent, recurse_depth, options, ui, format_path) + error ||= child_error + end + if !found_result && pattern.exact_path + error "#{pattern}: No such file or directory on remote or local" + error = true + end + error + end + + def copy(src_entry, dest_entry, new_dest_parent, recurse_depth=max_recurse_depth) + # A NOTE about this algorithm: + # There are cases where this algorithm does too many network requests. + # knife upload with a specific filename will first check if the file + # exists (a "dir" in the parent) before deciding whether to POST or + # PUT it. If we just tried PUT (or POST) and then tried the other if + # the conflict failed, we wouldn't need to check existence. + # On the other hand, we may already have DONE the request, in which + # case we shouldn't waste time trying PUT if we know the file doesn't + # exist. + # Will need to decide how that works with checksums, though. + error = false + begin + dest_path = format_path(dest_entry) + src_path = format_path(src_entry) + if !src_entry.exists? + if purge? + # If we would not have uploaded it, we will not purge it. + if src_entry.parent.can_have_child?(dest_entry.name, dest_entry.dir?) + maybe_converge "Deleted extra entry #{dest_path} (purge is on)" do + begin + dest_entry.delete(true) + true + rescue Chef::ChefFS::FileSystem::NotFoundError + output "Entry #{dest_path} does not exist. Nothing to do. (purge is on)" + false + end + end + else + output "Not deleting extra entry #{dest_path} (purge is off)" + end + end + + # + # The destination doesn't exist; copy it over. + # + elsif !dest_entry.exists? + if new_dest_parent.can_have_child?(src_entry.name, src_entry.dir?) + # If the entry can do a create directly from filesystem, do that. + # This is used for cookbooks, for example. + if new_dest_parent.respond_to?(:create_child_from) + converge ui, options, "Created #{dest_path}" do + new_dest_parent.create_child_from(src_entry) + end + return + end + + if src_entry.dir? + new_dest_dir = nil + converge "Created #{dest_path}", + dry_run_block: proc { new_dest_dir = new_dest_parent.child(src_entry.name) }, + action: proc { new_dest_dir = new_dest_parent.create_child_from(src_entry) + + # Directory creation is recursive. + if recurse_depth != 0 + parallel_do(src_entry.children) do |src_child| + new_dest_child = new_dest_dir.child(src_child.name) + child_error = copy_entries(src_child, new_dest_child, new_dest_dir, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) + error ||= child_error + end + end + else + converge "Created #{dest_path}" do + new_dest_parent.create_child(src_entry.name, src_entry.read) + end + end + end + + # + # Both exist; compare and copy if different. + # + else + # If the entry can do a copy directly, do that. + if dest_entry.respond_to?(:copy_from) + if force? || compare(src_entry, dest_entry)[0] == false + converge "Updated #{dest_path}" do + dest_entry.copy_from(src_entry, options) + end + end + return + end + + # If they are different types, log an error. + if src_entry.dir? + if dest_entry.dir? + # If both are directories, recurse into their children + if recurse_depth != 0 + parallel_do(child_pairs(src_entry, dest_entry)) do |src_child, dest_child| + child_error = copy_entries(src_child, dest_child, dest_entry, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) + error ||= child_error + end + end + else + # If they are different types. + error "File #{src_path} is a directory while file #{dest_path} is a regular file\n" + return + end + else + if dest_entry.dir? + error "File #{src_path} is a regular file while file #{dest_path} is a directory\n" + return + else + + # Both are files! Copy them unless we're sure they are the same. + # TODO the line above is ick, make sure callers don't pass it unless they have to and get rid of == false ... + if diff? == false + should_copy = false + elsif force? + should_copy = true + src_value = nil + else + are_same, src_value, _dest_value = compare(src_entry, dest_entry) + should_copy = !are_same + end + if should_copy + converge "Updated #{dest_path}" do + src_value = src_entry.read if src_value.nil? + dest_entry.write(src_value) + end + end + end + end + end + rescue FileSystem::DefaultEnvironmentCannotBeModifiedError => e + warn "#{format_path.call(e.entry)} #{e.reason}." + rescue FileSystem::OperationFailedError => e + error "#{format_path.call(e.entry)} failed to #{e.operation}: #{e.message}" + error = true + rescue FileSystem::OperationNotAllowedError => e + error "#{format_path.call(e.entry)} #{e.reason}." + error = true + end + error + end + + def get_or_create_parent(entry) + parent = entry.parent + if parent && !parent.exists? + parent_parent = get_or_create_parent(parent) + converge "Created #{parent_path}" do + parent_parent.create_child(parent.name, nil) + end + else + parent + end + end + end + end +end diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb index 3ab59046cc..14107a60e5 100644 --- a/lib/chef/chef_fs/file_system.rb +++ b/lib/chef/chef_fs/file_system.rb @@ -17,10 +17,8 @@ # require 'chef/chef_fs/path_utils' -require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error' -require 'chef/chef_fs/file_system/operation_failed_error' -require 'chef/chef_fs/file_system/operation_not_allowed_error' require 'chef/chef_fs/parallelizer' +require 'chef/chef_fs/copy_algorithm' class Chef module ChefFS @@ -137,19 +135,8 @@ class Chef # end # def self.copy_to(pattern, src_root, dest_root, recurse_depth, options, ui = nil, format_path = nil) - found_result = false - error = false - parallel_do(list_pairs(pattern, src_root, dest_root)) do |src, dest| - found_result = true - new_dest_parent = get_or_create_parent(dest, options, ui, format_path) - child_error = copy_entries(src, dest, new_dest_parent, recurse_depth, options, ui, format_path) - error ||= child_error - end - if !found_result && pattern.exact_path - ui.error "#{pattern}: No such file or directory on remote or local" if ui - error = true - end - error + copier = CopyEntries.new(recurse_depth: recurse_depth, ui: ui, format_path: format_path, **options) + copier.copy_to(pattern, src_root, dest_root) end # Yield entries for children that are in either +a_root+ or +b_root+, with @@ -258,172 +245,6 @@ class Chef end [ are_same, a_value, b_value ] end - - private - - # Copy two entries (could be files or dirs) - def self.copy_entries(src_entry, dest_entry, new_dest_parent, recurse_depth, options, ui, format_path) - # A NOTE about this algorithm: - # There are cases where this algorithm does too many network requests. - # knife upload with a specific filename will first check if the file - # exists (a "dir" in the parent) before deciding whether to POST or - # PUT it. If we just tried PUT (or POST) and then tried the other if - # the conflict failed, we wouldn't need to check existence. - # On the other hand, we may already have DONE the request, in which - # case we shouldn't waste time trying PUT if we know the file doesn't - # exist. - # Will need to decide how that works with checksums, though. - error = false - begin - dest_path = format_path.call(dest_entry) if ui - src_path = format_path.call(src_entry) if ui - if !src_entry.exists? - if options[:purge] - # If we would not have uploaded it, we will not purge it. - if src_entry.parent.can_have_child?(dest_entry.name, dest_entry.dir?) - if options[:dry_run] - ui.output "Would delete #{dest_path}" if ui - else - begin - dest_entry.delete(true) - ui.output "Deleted extra entry #{dest_path} (purge is on)" if ui - rescue Chef::ChefFS::FileSystem::NotFoundError - ui.output "Entry #{dest_path} does not exist. Nothing to do. (purge is on)" if ui - end - end - else - ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui - end - end - - elsif !dest_entry.exists? - if new_dest_parent.can_have_child?(src_entry.name, src_entry.dir?) - # If the entry can do a copy directly from filesystem, do that. - if new_dest_parent.respond_to?(:create_child_from) - if options[:dry_run] - ui.output "Would create #{dest_path}" if ui - else - new_dest_parent.create_child_from(src_entry) - ui.output "Created #{dest_path}" if ui - end - return - end - - if src_entry.dir? - if options[:dry_run] - ui.output "Would create #{dest_path}" if ui - new_dest_dir = new_dest_parent.child(src_entry.name) - else - new_dest_dir = new_dest_parent.create_child(src_entry.name, nil) - ui.output "Created #{dest_path}" if ui - end - # Directory creation is recursive. - if recurse_depth != 0 - parallel_do(src_entry.children) do |src_child| - new_dest_child = new_dest_dir.child(src_child.name) - child_error = copy_entries(src_child, new_dest_child, new_dest_dir, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) - error ||= child_error - end - end - else - if options[:dry_run] - ui.output "Would create #{dest_path}" if ui - else - new_dest_parent.create_child(src_entry.name, src_entry.read) - ui.output "Created #{dest_path}" if ui - end - end - end - - else - # Both exist. - - # If the entry can do a copy directly, do that. - if dest_entry.respond_to?(:copy_from) - if options[:force] || compare(src_entry, dest_entry)[0] == false - if options[:dry_run] - ui.output "Would update #{dest_path}" if ui - else - dest_entry.copy_from(src_entry, options) - ui.output "Updated #{dest_path}" if ui - end - end - return - end - - # If they are different types, log an error. - if src_entry.dir? - if dest_entry.dir? - # If both are directories, recurse into their children - if recurse_depth != 0 - parallel_do(child_pairs(src_entry, dest_entry)) do |src_child, dest_child| - child_error = copy_entries(src_child, dest_child, dest_entry, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) - error ||= child_error - end - end - else - # If they are different types. - ui.error("File #{src_path} is a directory while file #{dest_path} is a regular file\n") if ui - return - end - else - if dest_entry.dir? - ui.error("File #{src_path} is a regular file while file #{dest_path} is a directory\n") if ui - return - else - - # Both are files! Copy them unless we're sure they are the same.' - if options[:diff] == false - should_copy = false - elsif options[:force] - should_copy = true - src_value = nil - else - are_same, src_value, _dest_value = compare(src_entry, dest_entry) - should_copy = !are_same - end - if should_copy - if options[:dry_run] - ui.output "Would update #{dest_path}" if ui - else - src_value = src_entry.read if src_value.nil? - dest_entry.write(src_value) - ui.output "Updated #{dest_path}" if ui - end - end - end - end - end - rescue DefaultEnvironmentCannotBeModifiedError => e - ui.warn "#{format_path.call(e.entry)} #{e.reason}." if ui - rescue OperationFailedError => e - ui.error "#{format_path.call(e.entry)} failed to #{e.operation}: #{e.message}" if ui - error = true - rescue OperationNotAllowedError => e - ui.error "#{format_path.call(e.entry)} #{e.reason}." if ui - error = true - end - error - end - - def self.get_or_create_parent(entry, options, ui, format_path) - parent = entry.parent - if parent && !parent.exists? - parent_path = format_path.call(parent) if ui - parent_parent = get_or_create_parent(parent, options, ui, format_path) - if options[:dry_run] - ui.output "Would create #{parent_path}" if ui - else - parent = parent_parent.create_child(parent.name, nil) - ui.output "Created #{parent_path}" if ui - end - end - return parent - end - - def self.parallel_do(enum, options = {}, &block) - Chef::ChefFS::Parallelizer.parallel_do(enum, options, &block) - end end end end |