summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-03-04 09:38:13 -0800
committerJohn Keiser <john@johnkeiser.com>2015-03-04 09:38:58 -0800
commit583c3ba1913b4e1a25452e949ccd491b0b9649db (patch)
treeea7e7f9550783fb547938db87022cf41fbece14a
parent0bb2861228fd33fd6a4ef296a1dd223abb6dec9f (diff)
downloadchef-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.rb69
-rw-r--r--lib/chef/chef_fs/copy_algorithm.rb197
-rw-r--r--lib/chef/chef_fs/file_system.rb185
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