summaryrefslogtreecommitdiff
path: root/lib/chef/chef_fs/file_system
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/chef_fs/file_system')
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_dir.rb47
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb121
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb109
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb31
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb84
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb188
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_file.rb78
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_subdir.rb54
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb68
-rw-r--r--lib/chef/chef_fs/file_system/data_bag_dir.rb78
-rw-r--r--lib/chef/chef_fs/file_system/data_bag_item.rb59
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb66
-rw-r--r--lib/chef/chef_fs/file_system/file_system_entry.rb90
-rw-r--r--lib/chef/chef_fs/file_system/file_system_error.rb31
-rw-r--r--lib/chef/chef_fs/file_system/file_system_root_dir.rb31
-rw-r--r--lib/chef/chef_fs/file_system/must_delete_recursively_error.rb31
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb47
-rw-r--r--lib/chef/chef_fs/file_system/nonexistent_fs_object.rb40
-rw-r--r--lib/chef/chef_fs/file_system/not_found_error.rb31
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb84
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb123
21 files changed, 1491 insertions, 0 deletions
diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb
new file mode 100644
index 0000000000..74038f481b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -0,0 +1,47 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class BaseFSDir < BaseFSObject
+ def initialize(name, parent)
+ super
+ end
+
+ def dir?
+ true
+ end
+
+ # Override child(name) to provide a child object by name without the network read
+ def child(name)
+ children.select { |child| child.name == name }.first || NonexistentFSObject.new(name, self)
+ end
+
+ def can_have_child?(name, is_dir)
+ true
+ end
+
+ # Abstract: children
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
new file mode 100644
index 0000000000..855892fc89
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -0,0 +1,121 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/path_utils'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class BaseFSObject
+ def initialize(name, parent)
+ @parent = parent
+ @name = name
+ if parent
+ @path = Chef::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
+
+ def root
+ parent ? parent.root : self
+ end
+
+ def path_for_printing
+ if parent
+ parent_path = parent.path_for_printing
+ if parent_path == '.'
+ name
+ else
+ Chef::ChefFS::PathUtils::join(parent.path_for_printing, name)
+ end
+ else
+ name
+ end
+ end
+
+ def dir?
+ false
+ end
+
+ def exists?
+ true
+ end
+
+ def child(name)
+ NonexistentFSObject.new(name, self)
+ 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
+
+ # 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)
+ return nil
+ end
+
+ # Important directory attributes: name, parent, path, root
+ # Overridable attributes: dir?, child(name), path_for_printing
+ # Abstract: read, write, delete, children
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
new file mode 100644
index 0000000000..87d904e830
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
@@ -0,0 +1,109 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/file_system_entry'
+require 'chef/cookbook/chefignore'
+require 'chef/cookbook/cookbook_version_loader'
+require 'chef/node'
+require 'chef/role'
+require 'chef/environment'
+require 'chef/data_bag_item'
+require 'chef/client'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
+ # except it pretends files in /cookbooks/chefignore don't exist
+ # and it can inflate Chef objects
+ class ChefRepositoryFileSystemEntry < FileSystemEntry
+ def initialize(name, parent, file_path = nil)
+ super(name, parent, file_path)
+ # Load /cookbooks/chefignore
+ if name == "cookbooks" && path == "/cookbooks" # We check name first because it's a faster fail than path
+ @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
+ # If we are a cookbook or a cookbook subdirectory, empty directories
+ # underneath us are ignored (since they cannot be uploaded)
+ elsif parent && parent.name === "cookbooks" && parent.path == "/cookbooks"
+ @ignore_empty_directories = true
+ elsif parent && parent.ignore_empty_directories?
+ @ignore_empty_directories = true
+ end
+ end
+
+ attr_reader :chefignore
+
+ def ignore_empty_directories?
+ @ignore_empty_directories
+ end
+
+ def chef_object
+ begin
+ if parent.path == "/cookbooks"
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ loader.load_cookbooks
+ return loader.cookbook_version
+ end
+
+ # Otherwise the information to inflate the object, is in the file (json_class).
+ return Chef::JSONCompat.from_json(read)
+ rescue
+ Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
+ end
+ nil
+ end
+
+ def children
+ @children ||= Dir.entries(file_path).select { |entry| entry != '.' && entry != '..' && !ignored?(entry) }.
+ map { |entry| ChefRepositoryFileSystemEntry.new(entry, self) }
+ end
+
+ attr_reader :chefignore
+
+ private
+
+ def ignored?(child_name)
+ # empty directories inside a cookbook are ignored
+ if ignore_empty_directories?
+ child_path = PathUtils.join(file_path, child_name)
+ if File.directory?(child_path) && Dir.entries(child_path) == [ '.', '..' ]
+ return true
+ end
+ end
+
+ ignorer = self
+ begin
+ if ignorer.chefignore
+ # Grab the path from entry to child
+ path_to_child = child_name
+ child = self
+ while child != ignorer
+ path_to_child = PathUtils.join(child.name, path_to_child)
+ child = child.parent
+ end
+ # Check whether that relative path is ignored
+ return ignorer.chefignore.ignored?(path_to_child)
+ end
+ ignorer = ignorer.parent
+ end while ignorer
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
new file mode 100644
index 0000000000..fdad68003c
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/chef_repository_file_system_entry'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class ChefRepositoryFileSystemRootDir < ChefRepositoryFileSystemEntry
+ def initialize(file_path)
+ super("", nil, file_path)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
new file mode 100644
index 0000000000..d3c217d11c
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -0,0 +1,84 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbooks_dir'
+require 'chef/chef_fs/file_system/data_bags_dir'
+require 'chef/chef_fs/file_system/nodes_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class ChefServerRootDir < BaseFSDir
+ def initialize(root_name, chef_config, repo_mode)
+ super("", nil)
+ @chef_server_url = chef_config[:chef_server_url]
+ @chef_username = chef_config[:node_name]
+ @chef_private_key = chef_config[:client_key]
+ @environment = chef_config[:environment]
+ @repo_mode = repo_mode
+ @root_name = root_name
+ end
+
+ attr_reader :chef_server_url
+ attr_reader :chef_username
+ attr_reader :chef_private_key
+ attr_reader :environment
+ attr_reader :repo_mode
+
+ def rest
+ Chef::REST.new(chef_server_url, chef_username, chef_private_key)
+ end
+
+ def api_path
+ ""
+ end
+
+ def path_for_printing
+ "#{@root_name}/"
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir && children.any? { |child| child.name == name }
+ end
+
+ def children
+ @children ||= begin
+ result = [
+ CookbooksDir.new(self),
+ DataBagsDir.new(self),
+ RestListDir.new("environments", self),
+ RestListDir.new("roles", self)
+ ]
+ if repo_mode == 'everything'
+ result += [
+ RestListDir.new("clients", self),
+ NodesDir.new(self),
+ RestListDir.new("users", self)
+ ]
+ end
+ result.sort_by { |child| child.name }
+ end
+ end
+
+ # Yeah, sorry, I'm not putting delete on this thing.
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
new file mode 100644
index 0000000000..e87d5dd49d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -0,0 +1,188 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbook_subdir'
+require 'chef/chef_fs/file_system/cookbook_file'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/cookbook_version'
+require 'chef/cookbook_uploader'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class CookbookDir < BaseFSDir
+ def initialize(name, parent, versions = nil)
+ super(name, parent)
+ @versions = versions
+ end
+
+ attr_reader :versions
+
+ COOKBOOK_SEGMENT_INFO = {
+ :attributes => { :ruby_only => true },
+ :definitions => { :ruby_only => true },
+ :recipes => { :ruby_only => true },
+ :libraries => { :ruby_only => true },
+ :templates => { :recursive => true },
+ :files => { :recursive => true },
+ :resources => { :ruby_only => true, :recursive => true },
+ :providers => { :ruby_only => true, :recursive => true },
+ :root_files => { }
+ }
+
+ def add_child(child)
+ @children << child
+ end
+
+ def api_path
+ "#{parent.api_path}/#{name}/_latest"
+ end
+
+ def child(name)
+ # Since we're ignoring the rules and doing a network request here,
+ # we need to make sure we don't rethrow the exception. (child(name)
+ # is not supposed to fail.)
+ begin
+ result = children.select { |child| child.name == name }.first
+ return result if result
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ end
+ return NonexistentFSObject.new(name, self)
+ end
+
+ def can_have_child?(name, is_dir)
+ # A cookbook's root may not have directories unless they are segment directories
+ if is_dir
+ return name != 'root_files' &&
+ COOKBOOK_SEGMENT_INFO.keys.any? { |segment| segment.to_s == name }
+ end
+ true
+ end
+
+ def children
+ if @children.nil?
+ @children = []
+ manifest = chef_object.manifest
+ COOKBOOK_SEGMENT_INFO.each do |segment, segment_info|
+ next unless manifest.has_key?(segment)
+
+ # Go through each file in the manifest for the segment, and
+ # add cookbook subdirs and files for it.
+ manifest[segment].each do |segment_file|
+ parts = segment_file[:path].split('/')
+ # Get or create the path to the file
+ container = self
+ parts[0,parts.length-1].each do |part|
+ old_container = container
+ container = old_container.children.select { |child| part == child.name }.first
+ if !container
+ container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive])
+ old_container.add_child(container)
+ end
+ end
+ # Create the file itself
+ container.add_child(CookbookFile.new(parts[parts.length-1], container, segment_file))
+ end
+ end
+ end
+ @children
+ end
+
+ def dir?
+ exists?
+ end
+
+ def read
+ # This will only be called if dir? is false, which means exists? is false.
+ raise Chef::ChefFS::FileSystem::NotFoundError, path_for_printing
+ end
+
+ def exists?
+ if !@versions
+ child = parent.children.select { |child| child.name == name }.first
+ @versions = child.versions if child
+ end
+ !!@versions
+ end
+
+ def compare_to(other)
+ if !other.dir?
+ return [ !exists?, nil, nil ]
+ end
+ are_same = true
+ Chef::ChefFS::CommandLine::diff_entries(self, other, nil, :name_only) do
+ are_same = false
+ end
+ [ are_same, nil, nil ]
+ end
+
+ def copy_from(other)
+ parent.upload_cookbook_from(other)
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def chef_object
+ # We cheat and cache here, because it seems like a good idea to keep
+ # the cookbook view consistent with the directory structure.
+ return @chef_object if @chef_object
+
+ # The negative (not found) response is cached
+ if @could_not_get_chef_object
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(@could_not_get_chef_object), "#{path_for_printing} not found"
+ end
+
+ begin
+ # We want to fail fast, for now, because of the 500 issue :/
+ # This will make things worse for parallelism, a little, because
+ # Chef::Config is global and this could affect other requests while
+ # this request is going on. (We're not parallel yet, but we will be.)
+ # Chef bug http://tickets.opscode.com/browse/CHEF-3066
+ old_retry_count = Chef::Config[:http_retry_count]
+ begin
+ Chef::Config[:http_retry_count] = 0
+ @chef_object ||= rest.get_rest(api_path)
+ ensure
+ Chef::Config[:http_retry_count] = old_retry_count
+ end
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ @could_not_get_chef_object = $!
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(@could_not_get_chef_object), "#{path_for_printing} not found"
+ else
+ raise
+ end
+
+ # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now.
+ # Remove this when that bug is fixed.
+ rescue Net::HTTPFatalError
+ if $!.response.code == "500"
+ @could_not_get_chef_object = $!
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(@could_not_get_chef_object), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb
new file mode 100644
index 0000000000..baa71f5d9e
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_file.rb
@@ -0,0 +1,78 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_object'
+require 'digest/md5'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class CookbookFile < BaseFSObject
+ def initialize(name, parent, file)
+ super(name, parent)
+ @file = file
+ end
+
+ attr_reader :file
+
+ def checksum
+ file[:checksum]
+ end
+
+ def read
+ old_sign_on_redirect = rest.sign_on_redirect
+ rest.sign_on_redirect = false
+ begin
+ rest.get_rest(file[:url])
+ ensure
+ rest.sign_on_redirect = old_sign_on_redirect
+ end
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def compare_to(other)
+ other_value = nil
+ if other.respond_to?(:checksum)
+ other_checksum = other.checksum
+ else
+ begin
+ other_value = other.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ false, nil, :none ]
+ end
+ other_checksum = calc_checksum(other_value)
+ end
+ [ checksum == other_checksum, nil, other_value ]
+ end
+
+ private
+
+ def calc_checksum(value)
+ begin
+ Digest::MD5.hexdigest(value)
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
new file mode 100644
index 0000000000..73c709e01e
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
@@ -0,0 +1,54 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class CookbookSubdir < BaseFSDir
+ def initialize(name, parent, ruby_only, recursive)
+ super(name, parent)
+ @children = []
+ @ruby_only = ruby_only
+ @recursive = recursive
+ end
+
+ attr_reader :versions
+ attr_reader :children
+
+ def add_child(child)
+ @children << child
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ return false if !@recursive
+ else
+ return false if @ruby_only && name !~ /\.rb$/
+ end
+ true
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
new file mode 100644
index 0000000000..9249b42aaa
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -0,0 +1,68 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbook_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class CookbooksDir < RestListDir
+ def initialize(parent)
+ super("cookbooks", parent)
+ end
+
+ def child(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookDir.new(name, self)
+ end
+
+ def children
+ @children ||= rest.get_rest(api_path).map { |key, value| CookbookDir.new(key, self, value) }
+ end
+
+ def create_child_from(other)
+ upload_cookbook_from(other)
+ end
+
+ def upload_cookbook_from(other)
+ other_cookbook_version = other.chef_object
+ # TODO this only works on the file system. And it can't be broken into
+ # pieces.
+ begin
+ uploader = Chef::CookbookUploader.new(other_cookbook_version, other.parent.file_path)
+ uploader.upload_cookbooks
+ rescue Net::HTTPServerException => e
+ case e.response.code
+ when "409"
+ ui.error "Version #{other_cookbook_version.version} of cookbook #{other_cookbook_version.name} is frozen. Use --force to override."
+ Chef::Log.debug(e)
+ raise Exceptions::CookbookFrozen
+ else
+ raise
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/data_bag_dir.rb b/lib/chef/chef_fs/file_system/data_bag_dir.rb
new file mode 100644
index 0000000000..41fb5dfc63
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/data_bag_dir.rb
@@ -0,0 +1,78 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/data_bag_item'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/must_delete_recursively_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class DataBagDir < RestListDir
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = nil
+ end
+
+ def dir?
+ exists?
+ end
+
+ def read
+ # This will only be called if dir? is false, which means exists? is false.
+ raise Chef::ChefFS::FileSystem::NotFoundError, "#{path_for_printing} not found"
+ end
+
+ def exists?
+ if @exists.nil?
+ @exists = parent.children.any? { |child| child.name == name }
+ end
+ @exists
+ end
+
+ def create_child(name, file_contents)
+ json = Chef::JSONCompat.from_json(file_contents).to_hash
+ id = name[0,name.length-5]
+ if json.include?('id') && json['id'] != id
+ raise "ID in #{path_for_printing}/#{name} must be '#{id}' (is '#{json['id']}')"
+ end
+ rest.post_rest(api_path, json)
+ _make_child_entry(name, true)
+ end
+
+ def _make_child_entry(name, exists = nil)
+ DataBagItem.new(name, self, exists)
+ end
+
+ def delete(recurse)
+ if !recurse
+ raise Chef::ChefFS::FileSystem::MustDeleteRecursivelyError.new, "#{path_for_printing} must be deleted recursively"
+ end
+ begin
+ rest.delete_rest(api_path)
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/data_bag_item.rb b/lib/chef/chef_fs/file_system/data_bag_item.rb
new file mode 100644
index 0000000000..2f6eb15232
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/data_bag_item.rb
@@ -0,0 +1,59 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/rest_list_entry'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class DataBagItem < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent, exists)
+ end
+
+ def write(file_contents)
+ # Write is just a little tiny bit different for data bags:
+ # you set raw_data in the JSON instead of putting the items
+ # in the top level.
+ json = Chef::JSONCompat.from_json(file_contents).to_hash
+ id = name[0,name.length-5] # Strip off the .json from the end
+ if json['id'] != id
+ raise "Id in #{path_for_printing}/#{name} must be '#{id}' (is '#{json['id']}')"
+ end
+ begin
+ data_bag = parent.name
+ json = {
+ "name" => "data_bag_item_#{data_bag}_#{id}",
+ "json_class" => "Chef::DataBagItem",
+ "chef_type" => "data_bag_item",
+ "data_bag" => data_bag,
+ "raw_data" => json
+ }
+ rest.put_rest(api_path, json)
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
new file mode 100644
index 0000000000..6eca990545
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb
@@ -0,0 +1,66 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/data_bag_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class DataBagsDir < RestListDir
+ def initialize(parent)
+ super("data_bags", parent, "data")
+ end
+
+ def child(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || DataBagDir.new(name, self)
+ end
+
+ def children
+ begin
+ @children ||= rest.get_rest(api_path).keys.map do |entry|
+ DataBagDir.new(entry, self, true)
+ end
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir
+ end
+
+ def create_child(name, file_contents)
+ begin
+ rest.post_rest(api_path, { 'name' => name })
+ rescue Net::HTTPServerException
+ if $!.response.code != "409"
+ raise
+ end
+ end
+ DataBagDir.new(name, self, true)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
new file mode 100644
index 0000000000..a86e0cb82a
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_entry.rb
@@ -0,0 +1,90 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/path_utils'
+require 'fileutils'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class FileSystemEntry < BaseFSDir
+ def initialize(name, parent, file_path = nil)
+ super(name, parent)
+ @file_path = file_path || "#{parent.file_path}/#{name}"
+ end
+
+ attr_reader :file_path
+
+ def path_for_printing
+ Chef::ChefFS::PathUtils::relative_to(file_path, File.expand_path(Dir.pwd))
+ end
+
+ def children
+ begin
+ @children ||= Dir.entries(file_path).select { |entry| entry != '.' && entry != '..' }.map { |entry| FileSystemEntry.new(entry, self) }
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{file_path} not found"
+ end
+ end
+
+ def create_child(child_name, file_contents=nil)
+ result = FileSystemEntry.new(child_name, self)
+ if file_contents
+ result.write(file_contents)
+ else
+ Dir.mkdir(result.file_path)
+ end
+ result
+ end
+
+ def dir?
+ File.directory?(file_path)
+ end
+
+ def delete(recurse)
+ if dir?
+ if recurse
+ FileUtils.rm_rf(file_path)
+ else
+ File.rmdir(file_path)
+ end
+ else
+ File.delete(file_path)
+ end
+ end
+
+ def read
+ begin
+ File.open(file_path, "rb") {|f| f.read}
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{file_path} not found"
+ end
+ end
+
+ def write(content)
+ File.open(file_path, 'wb') do |file|
+ file.write(content)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_error.rb b/lib/chef/chef_fs/file_system/file_system_error.rb
new file mode 100644
index 0000000000..a461221108
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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.
+#
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class FileSystemError < StandardError
+ def initialize(cause = nil)
+ @cause = cause
+ end
+
+ attr_reader :cause
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_root_dir.rb b/lib/chef/chef_fs/file_system/file_system_root_dir.rb
new file mode 100644
index 0000000000..afbf7b1901
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_root_dir.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/file_system_entry'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class FileSystemRootDir < FileSystemEntry
+ def initialize(file_path)
+ super("", nil, file_path)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
new file mode 100644
index 0000000000..d247a5b4ed
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class MustDeleteRecursivelyError < FileSystemError
+ def initialize(cause = nil)
+ super(cause)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
new file mode 100644
index 0000000000..4dfbf6d850
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/nodes_dir.rb
@@ -0,0 +1,47 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class NodesDir < RestListDir
+ def initialize(parent)
+ super("nodes", parent)
+ end
+
+ # Override children to respond to environment
+ def children
+ @children ||= begin
+ env_api_path = environment ? "environments/#{environment}/#{api_path}" : api_path
+ rest.get_rest(env_api_path).keys.map { |key| RestListEntry.new("#{key}.json", self, true) }
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
new file mode 100644
index 0000000000..dc82e83b0d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
@@ -0,0 +1,40 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class NonexistentFSObject < BaseFSObject
+ def initialize(name, parent)
+ super
+ end
+
+ def exists?
+ false
+ end
+
+ def read
+ raise Chef::ChefFS::FileSystem::NotFoundError, "Nonexistent #{path_for_printing}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/not_found_error.rb b/lib/chef/chef_fs/file_system/not_found_error.rb
new file mode 100644
index 0000000000..0b608f1abf
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/not_found_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class NotFoundError < FileSystemError
+ def initialize(cause = nil)
+ super(cause)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
new file mode 100644
index 0000000000..0e8db4d7b9
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb
@@ -0,0 +1,84 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class RestListDir < BaseFSDir
+ def initialize(name, parent, api_path = nil)
+ super(name, parent)
+ @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
+ end
+
+ attr_reader :api_path
+
+ def child(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result ||= can_have_child?(name, false) ?
+ _make_child_entry(name) : NonexistentFSObject.new(name, self)
+ end
+
+ def can_have_child?(name, is_dir)
+ name =~ /\.json$/ && !is_dir
+ end
+
+ def children
+ begin
+ @children ||= rest.get_rest(api_path).keys.map do |key|
+ _make_child_entry("#{key}.json", true)
+ end
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+
+ # NOTE if you change this significantly, you will likely need to change
+ # DataBagDir.create_child as well.
+ def create_child(name, file_contents)
+ json = Chef::JSONCompat.from_json(file_contents).to_hash
+ base_name = name[0,name.length-5]
+ if json.include?('name') && json['name'] != base_name
+ raise "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{json['name']}')"
+ end
+ rest.post_rest(api_path, json)
+ _make_child_entry(name, true)
+ end
+
+ def environment
+ parent.environment
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def _make_child_entry(name, exists = nil)
+ RestListEntry.new(name, self, exists)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
new file mode 100644
index 0000000000..dd504ef341
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb
@@ -0,0 +1,123 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/role'
+require 'chef/node'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class RestListEntry < BaseFSObject
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def api_path
+ if name.length < 5 || name[-5,5] != ".json"
+ raise "Invalid name #{path}: must end in .json"
+ end
+ api_child_name = name[0,name.length-5]
+ "#{parent.api_path}/#{api_child_name}"
+ end
+
+ def environment
+ parent.environment
+ end
+
+ def exists?
+ if @exists.nil?
+ begin
+ @exists = parent.children.any? { |child| child.name == name }
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ @exists = false
+ end
+ end
+ @exists
+ end
+
+ def delete(recurse)
+ begin
+ rest.delete_rest(api_path)
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+
+ def read
+ Chef::JSONCompat.to_json_pretty(chef_object.to_hash)
+ end
+
+ def chef_object
+ begin
+ # REST will inflate the Chef object using json_class
+ rest.get_rest(api_path)
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+
+ def compare_to(other)
+ begin
+ other_value = other.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ nil, nil, :none ]
+ end
+ begin
+ value = chef_object.to_hash
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ false, :none, other_value ]
+ end
+ are_same = (value == Chef::JSONCompat.from_json(other_value, :create_additions => false))
+ [ are_same, Chef::JSONCompat.to_json_pretty(value), other_value ]
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def write(file_contents)
+ json = Chef::JSONCompat.from_json(file_contents).to_hash
+ base_name = name[0,name.length-5]
+ if json['name'] != base_name
+ raise "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{json['name']}')"
+ end
+ begin
+ rest.put_rest(api_path, json)
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
+ else
+ raise
+ end
+ end
+ end
+ end
+ end
+ end
+end