# # Author:: John Keiser () # Copyright:: Copyright (c) 2012 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'chef_fs/path_utils' require 'chef_fs/file_system/operation_not_allowed_error' module ChefFS module FileSystem class BaseFSObject def initialize(name, parent) @parent = parent @name = name if parent @path = ChefFS::PathUtils::join(parent.path, name) else if name != '' raise ArgumentError, "Name of root object must be empty string: was '#{name}' instead" end @path = '/' end end attr_reader :name attr_reader :parent attr_reader :path # Override this if you have a special comparison algorithm that can tell # you whether this entry is the same as another--either a quicker or a # more reliable one. Callers will use this to decide whether to upload, # download or diff an object. # # You should not override this if you're going to do the standard # +self.read == other.read+. If you return +nil+, the caller will call # +other.compare_to(you)+ instead. Give them a chance :) # # ==== Parameters # # * +other+ - the entry to compare to # # ==== Returns # # * +[ are_same, value, other_value ]+ # +are_same+ may be +true+, +false+ or +nil+ (which means "don't know"). # +value+ and +other_value+ must either be the text of +self+ or +other+, # +:none+ (if the entry does not exist or has no value) or +nil+ if the # value was not retrieved. # * +nil+ if a definitive answer cannot be had and nothing was retrieved. # # ==== Example # # are_same, value, other_value = entry.compare_to(other) # if are_same.nil? # are_same, other_value, value = other.compare_to(entry) # end # if are_same.nil? # value = entry.read if value.nil? # other_value = entry.read if other_value.nil? # are_same = (value == other_value) # end def compare_to(other) nil end # Override can_have_child? to report whether a given file *could* be added # to this directory. (Some directories can't have subdirs, some can only have .json # files, etc.) def can_have_child?(name, is_dir) false end # Get a child of this entry with the given name. This MUST always # return a child, even if it is NonexistentFSObject. Overriders should # take caution not to do expensive network requests to get the list of # children to fulfill this request, unless absolutely necessary here; it # is intended as a quick way to traverse a hierarchy. # # For example, knife show /data_bags/x/y.json will call # root.child('data_bags').child('x').child('y.json'), which can then # directly perform a network request to retrieve the y.json data bag. No # network request was necessary to retrieve def child(name) NonexistentFSObject.new(name, self) end # Override children to report your *actual* list of children as an array. def children raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? [] end # Expand this entry into a chef object (Chef::Role, ::Node, etc.) def chef_object raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? nil end # Create a child of this entry with the given name and contents. If # contents is nil, create a directory. # # NOTE: create_child_from is an optional method that can also be added to # your entry class, and will be called without actually reading the # file_contents. This is used for knife upload /cookbooks/cookbookname. def create_child(name, file_contents) raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? raise OperationNotAllowedError.new(:create_child), "#{path_for_printing} cannot have a child created under it." end # Delete this item, possibly recursively. Entries MUST NOT delete a # directory unless recurse is true. def delete(recurse) raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? raise OperationNotAllowedError.new(:delete), "#{path_for_printing} cannot be deleted." end # Ask whether this entry is a directory. If not, it is a file. def dir? false end # Ask whether this entry exists. def exists? true end # Printable path, generally used to distinguish paths in one root from # paths in another. def path_for_printing if parent parent_path = parent.path_for_printing if parent_path == '.' name else ChefFS::PathUtils::join(parent.path_for_printing, name) end else name end end def root parent ? parent.root : self end # Read the contents of this file entry. def read raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? raise OperationNotAllowedError.new(:read), "#{path_for_printing} cannot be read." end # Write the contents of this file entry. def write(file_contents) raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists? raise OperationNotAllowedError.new(:write), "#{path_for_printing} cannot be updated." end # Important directory attributes: name, parent, path, root # Overridable attributes: dir?, child(name), path_for_printing # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to end end end require 'chef_fs/file_system/nonexistent_fs_object'