summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2016-01-13 09:23:28 -0800
committerJohn Keiser <john@johnkeiser.com>2016-01-13 09:23:28 -0800
commitd0a9d75c7c80826a830c812ee15d1249553e4b43 (patch)
tree7872fd8cb9c8cb6cf9659c47fe7982667238aa62
parent5e5cbe6df61ae69b9a5942ce51a7ad1f8bb6ea3a (diff)
parent58f40acd2399114fc1752a0169c552fcfe507fc5 (diff)
downloadchef-d0a9d75c7c80826a830c812ee15d1249553e4b43.tar.gz
Merge branch 'jk/chef-fs-split'
-rw-r--r--chef.gemspec4
-rw-r--r--lib-backcompat/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb5
-rw-r--r--lib-backcompat/chef/chef_fs/file_system/chef_server_root_dir.rb5
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb4
-rw-r--r--lib/chef/chef_fs/config.rb8
-rw-r--r--lib/chef/chef_fs/file_system/acl_dir.rb63
-rw-r--r--lib/chef/chef_fs/file_system/acls_dir.rb68
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb102
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb80
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb82
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb81
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb199
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_dir.rb65
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_entry.rb60
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acls_dir.rb70
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb192
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb226
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb84
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb61
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb43
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb154
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb71
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/environments_dir.rb (renamed from lib/chef/chef_fs/file_system/environments_dir.rb)42
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb (renamed from lib/chef/chef_fs/file_system/acl_entry.rb)43
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/org_entry.rb31
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb61
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb60
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policies_dir.rb160
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb137
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb (renamed from lib/chef/chef_fs/file_system/policy_groups_dir.rb)24
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb25
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb179
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb187
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb190
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb224
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_file.rb82
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_subdir.rb59
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb41
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb152
-rw-r--r--lib/chef/chef_fs/file_system/data_bag_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb67
-rw-r--r--lib/chef/chef_fs/file_system/file_system_entry.rb115
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_dir.rb53
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_file.rb19
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_root.rb23
-rw-r--r--lib/chef/chef_fs/file_system/memory_dir.rb51
-rw-r--r--lib/chef/chef_fs/file_system/memory_file.rb17
-rw-r--r--lib/chef/chef_fs/file_system/memory_root.rb21
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb51
-rw-r--r--lib/chef/chef_fs/file_system/org_entry.rb29
-rw-r--r--lib/chef/chef_fs/file_system/organization_invites_entry.rb59
-rw-r--r--lib/chef/chef_fs/file_system/organization_members_entry.rb58
-rw-r--r--lib/chef/chef_fs/file_system/policies_dir.rb158
-rw-r--r--lib/chef/chef_fs/file_system/policy_group_entry.rb135
-rw-r--r--lib/chef/chef_fs/file_system/policy_revision_entry.rb23
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb)20
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb104
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb82
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb84
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb)16
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb83
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb201
-rw-r--r--lib/chef/chef_fs/file_system/repository/file_system_entry.rb117
-rw-r--r--lib/chef/chef_fs/file_system/repository/file_system_root_dir.rb (renamed from lib/chef/chef_fs/file_system/file_system_root_dir.rb)10
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb177
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb185
-rw-r--r--spec/support/shared/unit/file_system_support.rb15
-rw-r--r--spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb6
-rw-r--r--tasks/external_tests.rb4
70 files changed, 2811 insertions, 2734 deletions
diff --git a/chef.gemspec b/chef.gemspec
index 5200f7cf3e..359e002189 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -54,6 +54,6 @@ Gem::Specification.new do |s|
s.bindir = "bin"
s.executables = %w( chef-client chef-solo knife chef-shell chef-apply )
- s.require_path = 'lib'
- s.files = %w(Gemfile Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + Dir.glob("*.gemspec")
+ s.require_path = %w( lib lib-backcompat )
+ s.files = %w(Gemfile Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,lib-backcompat,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + Dir.glob("*.gemspec")
end
diff --git a/lib-backcompat/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib-backcompat/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
new file mode 100644
index 0000000000..88ede9c692
--- /dev/null
+++ b/lib-backcompat/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
@@ -0,0 +1,5 @@
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir'
+
+module Chef::ChefFS::FileSystem
+ ChefRepositoryFileSystemRootDir = Repository::ChefRepositoryFileSystemRootDir
+end
diff --git a/lib-backcompat/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib-backcompat/chef/chef_fs/file_system/chef_server_root_dir.rb
new file mode 100644
index 0000000000..c475057e7a
--- /dev/null
+++ b/lib-backcompat/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -0,0 +1,5 @@
+require 'chef/chef_fs/file_system/chef_server/chef_server_root_dir'
+
+module Chef::ChefFS::FileSystem
+ ChefServerRootDir = ChefServer::ChefServerRootDir
+end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index f886cd8c9a..07cb3d2cd5 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -23,7 +23,7 @@ require 'chef_zero/data_store/data_not_found_error'
require 'chef/chef_fs/file_pattern'
require 'chef/chef_fs/file_system'
require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/memory_root'
+require 'chef/chef_fs/file_system/memory/memory_root'
require 'fileutils'
class Chef
@@ -503,7 +503,7 @@ class Chef
end
# Create a little Chef::ChefFS memory filesystem with the data
- cookbook_fs = Chef::ChefFS::FileSystem::MemoryRoot.new('uploading')
+ cookbook_fs = Chef::ChefFS::FileSystem::Memory::MemoryRoot.new('uploading')
cookbook = Chef::JSONCompat.parse(data)
cookbook.each_pair do |key, value|
if value.is_a?(Array)
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index a666731b80..260882ae70 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -143,8 +143,8 @@ class Chef
end
def create_chef_fs
- require 'chef/chef_fs/file_system/chef_server_root_dir'
- Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version)
+ require 'chef/chef_fs/file_system/chef_server/chef_server_root_dir'
+ Chef::ChefFS::FileSystem::ChefServer::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version)
end
def local_fs
@@ -152,8 +152,8 @@ class Chef
end
def create_local_fs
- require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
- Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten, @chef_config)
+ require 'chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir'
+ Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten, @chef_config)
end
# Returns the given real path's location relative to the server root.
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
deleted file mode 100644
index 66cf794319..0000000000
--- a/lib/chef/chef_fs/file_system/acl_dir.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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/acl_entry'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-
-class Chef
- module ChefFS
- module FileSystem
- class AclDir < BaseFSDir
- def api_path
- parent.parent.child(name).api_path
- end
-
- def make_child_entry(name, exists = nil)
- result = @children.select { |child| child.name == name }.first if @children
- result || AclEntry.new(name, self, exists)
- end
-
- def can_have_child?(name, is_dir)
- name =~ /\.json$/ && !is_dir
- end
-
- def children
- if @children.nil?
- # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
- names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
- @children = names.map { |name| make_child_entry(name, true) }
- end
- @children
- end
-
- def create_child(name, file_contents)
- raise OperationNotAllowedError.new(:create_child, self, nil, "ACLs can only be updated, and can only be created when the corresponding object is created.")
- end
-
- def data_handler
- parent.data_handler
- end
-
- def rest
- parent.rest
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
deleted file mode 100644
index d8c9bf809c..0000000000
--- a/lib/chef/chef_fs/file_system/acls_dir.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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/acl_dir'
-require 'chef/chef_fs/file_system/cookbooks_acl_dir'
-require 'chef/chef_fs/file_system/acl_entry'
-require 'chef/chef_fs/data_handler/acl_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class AclsDir < BaseFSDir
- ENTITY_TYPES = %w(clients containers cookbooks data_bags environments groups nodes roles) # we don't read sandboxes, so we don't read their acls
-
- def data_handler
- @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
- end
-
- def api_path
- parent.api_path
- end
-
- def make_child_entry(name)
- children.select { |child| child.name == name }.first
- end
-
- def can_have_child?(name, is_dir)
- is_dir ? ENTITY_TYPES.include?(name) : name == 'organization.json'
- end
-
- def children
- if @children.nil?
- @children = ENTITY_TYPES.map do |entity_type|
- case entity_type
- when 'cookbooks'
- CookbooksAclDir.new(entity_type, self)
- else
- AclDir.new(entity_type, self)
- end
- end
- @children << AclEntry.new('organization.json', self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl
- end
- @children
- end
-
- def rest
- parent.rest
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
deleted file mode 100644
index ded390fa8d..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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_cookbook_entry'
-require 'chef/chef_fs/file_system/cookbook_dir'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/cookbook/chefignore'
-require 'chef/cookbook/cookbook_version_loader'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
- def initialize(name, parent, file_path = nil)
- super(name, parent, file_path)
- end
-
- def chef_object
- begin
- loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
- # We need the canonical cookbook name if we are using versioned cookbooks, but we don't
- # want to spend a lot of time adding code to the main Chef libraries
- if root.versioned_cookbooks
- canonical_name = canonical_cookbook_name(File.basename(file_path))
- raise "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z" unless canonical_name
-
- # KLUDGE: We shouldn't have to use instance_variable_set
- loader.instance_variable_set(:@cookbook_name, canonical_name)
- end
-
- loader.load_cookbooks
- cb = loader.cookbook_version
- if !cb
- Chef::Log.error("Cookbook #{file_path} empty.")
- raise "Cookbook #{file_path} empty."
- end
- cb
- rescue => e
- Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}")
- Chef::Log.error(e.backtrace.join("\n"))
- raise
- end
- end
-
- def children
- super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- # Only the given directories will be uploaded.
- return CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files'
- elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE
- return false
- end
- super(name, is_dir)
- end
-
- # Exposed as a class method so that it can be used elsewhere
- def self.canonical_cookbook_name(entry_name)
- name_match = Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name)
- return nil if name_match.nil?
- return name_match[1]
- end
-
- def canonical_cookbook_name(entry_name)
- self.class.canonical_cookbook_name(entry_name)
- end
-
- def uploaded_cookbook_version_path
- File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
- end
-
- def can_upload?
- File.exists?(uploaded_cookbook_version_path) || children.size > 0
- end
-
- protected
-
- def make_child_entry(child_name)
- segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
- ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
deleted file mode 100644
index 914412f839..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
-require 'chef/chef_fs/file_system/not_found_error'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbookEntry < ChefRepositoryFileSystemEntry
- def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false)
- super(name, parent, file_path)
- @ruby_only = ruby_only
- @recursive = recursive
- end
-
- attr_reader :ruby_only
- attr_reader :recursive
-
- def children
- super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- return recursive && name != '.' && name != '..'
- elsif ruby_only
- return false if name[-3..-1] != '.rb'
- end
-
- # Check chefignore
- ignorer = parent
- loop do
- if ignorer.is_a?(ChefRepositoryFileSystemCookbooksDir)
- # Grab the path from entry to child
- path_to_child = name
- child = self
- while child.parent != 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 || !ignorer.chefignore.ignored?(path_to_child)
- end
- ignorer = ignorer.parent
- break unless ignorer
- end
-
- true
- end
-
- def write_pretty_json
- false
- end
-
- protected
-
- def make_child_entry(child_name)
- ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
deleted file mode 100644
index 5b495666c3..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
-require 'chef/cookbook/chefignore'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, file_path)
- super(name, parent, file_path)
- begin
- @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
- rescue Errno::EISDIR
- rescue Errno::EACCES
- # Work around a bug in Chefignore when chefignore is a directory
- end
- end
-
- attr_reader :chefignore
-
- def children
- super.select do |entry|
- # empty cookbooks and cookbook directories are ignored
- if !entry.can_upload?
- Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
- false
- else
- true
- end
- end
- end
-
- def can_have_child?(name, is_dir)
- is_dir && !name.start_with?('.')
- end
-
- def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
- cookbook_name = File.basename(cookbook_path)
- child = make_child_entry(cookbook_name)
-
- # Use the copy/diff algorithm to copy it down so we don't destroy
- # chefignored data. This is terribly un-thread-safe.
- Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, child, nil, {:purge => true})
-
- # Write out .uploaded-cookbook-version.json
- cookbook_file_path = File.join(file_path, cookbook_name)
- if !File.exists?(cookbook_file_path)
- FileUtils.mkdir_p(cookbook_file_path)
- end
- uploaded_cookbook_version_path = File.join(cookbook_file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
- File.open(uploaded_cookbook_version_path, 'w') do |file|
- file.write(cookbook_version_json)
- end
- end
-
- protected
-
- def make_child_entry(child_name)
- ChefRepositoryFileSystemCookbookDir.new(child_name, self)
- end
- 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
deleted file mode 100644
index 39172e7ab9..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Author:: Ho-Sheng Hsiao (<hosh@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/chef_fs/file_system/not_found_error'
-
-class Chef
- module ChefFS
- module FileSystem
- # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
- # except can inflate Chef objects
- class ChefRepositoryFileSystemEntry < FileSystemEntry
- def initialize(name, parent, file_path = nil, data_handler = nil)
- super(name, parent, file_path)
- @data_handler = data_handler
- end
-
- def write_pretty_json=(value)
- @write_pretty_json = value
- end
-
- def write_pretty_json
- @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
- end
-
- def data_handler
- @data_handler || parent.data_handler
- end
-
- def chef_object
- begin
- return data_handler.chef_object(Chef::JSONCompat.parse(read))
- rescue
- Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
- end
- nil
- end
-
- def can_have_child?(name, is_dir)
- !is_dir && name[-5..-1] == '.json'
- end
-
- def write(file_contents)
- if file_contents && write_pretty_json && name[-5..-1] == '.json'
- file_contents = minimize(file_contents, self)
- end
- super(file_contents)
- end
-
- def minimize(file_contents, entry)
- object = Chef::JSONCompat.parse(file_contents)
- object = data_handler.normalize(object, entry)
- object = data_handler.minimize(object, entry)
- Chef::JSONCompat.to_json_pretty(object)
- end
-
- protected
-
- def make_child_entry(child_name)
- ChefRepositoryFileSystemEntry.new(child_name, self)
- 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
deleted file mode 100644
index b0bd08fc39..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012-2016 Chef Software, 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/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/chef_repository_file_system_acls_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir'
-require 'chef/chef_fs/file_system/multiplexed_dir'
-require 'chef/chef_fs/data_handler/client_data_handler'
-require 'chef/chef_fs/data_handler/environment_data_handler'
-require 'chef/chef_fs/data_handler/node_data_handler'
-require 'chef/chef_fs/data_handler/policy_data_handler'
-require 'chef/chef_fs/data_handler/policy_group_data_handler'
-require 'chef/chef_fs/data_handler/role_data_handler'
-require 'chef/chef_fs/data_handler/user_data_handler'
-require 'chef/chef_fs/data_handler/group_data_handler'
-require 'chef/chef_fs/data_handler/container_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
-
- #
- # Represents the root of a local Chef repository, with directories for
- # nodes, cookbooks, roles, etc. under it.
- #
- class ChefRepositoryFileSystemRootDir < BaseFSDir
- #
- # Create a new Chef Repository File System root.
- #
- # == Parameters
- # [child_paths]
- # A hash of child paths, e.g.:
- # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ],
- # "roles" => [ '/var/roles' ],
- # ...
- # [root_paths]
- # An array of paths representing the top level, where
- # +org.json+, +members.json+, and +invites.json+ will be stored.
- # [chef_config] - a hash of options that looks suspiciously like the ones
- # stored in Chef::Config, containing at least these keys:
- # :versioned_cookbooks:: whether to include versions in cookbook names
- def initialize(child_paths, root_paths=[], chef_config=Chef::Config)
- super("", nil)
- @child_paths = child_paths
- @root_paths = root_paths
- @versioned_cookbooks = chef_config[:versioned_cookbooks]
- end
-
- attr_accessor :write_pretty_json
-
- attr_reader :root_paths
- attr_reader :child_paths
- attr_reader :versioned_cookbooks
-
- CHILDREN = %w(org.json invitations.json members.json)
-
- def children
- @children ||= begin
- result = child_paths.keys.sort.map { |name| make_child_entry(name) }
- result += CHILDREN.map { |name| make_child_entry(name) }
- result.select { |c| c && c.exists? }.sort_by { |c| c.name }
- end
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- child_paths.has_key?(name)
- elsif root_dir
- CHILDREN.include?(name)
- else
- false
- end
- end
-
- def create_child(name, file_contents = nil)
- if file_contents
- child = root_dir.create_child(name, file_contents)
- else
- child_paths[name].each do |path|
- begin
- Dir.mkdir(path)
- rescue Errno::EEXIST
- end
- end
- child = make_child_entry(name)
- end
- @children = nil
- child
- end
-
- def json_class
- nil
- end
-
- # Used to print out a human-readable file system description
- def fs_description
- repo_paths = root_paths || [ File.dirname(child_paths['cookbooks'][0]) ]
- result = "repository at #{repo_paths.join(', ')}\n"
- if versioned_cookbooks
- result << " Multiple versions per cookbook\n"
- else
- result << " One version per cookbook\n"
- end
- child_paths.each_pair do |name, paths|
- if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
- result << " #{name} at #{paths.join(', ')}\n"
- end
- end
- result
- end
-
- private
-
- #
- # A FileSystemEntry representing the root path where invites.json,
- # members.json and org.json may be found.
- #
- def root_dir
- existing_paths = root_paths.select { |path| File.exists?(path) }
- if existing_paths.size > 0
- MultiplexedDir.new(existing_paths.map do |path|
- dir = ChefRepositoryFileSystemEntry.new(name, parent, path)
- dir.write_pretty_json = !!write_pretty_json
- dir
- end)
- end
- end
-
- #
- # Create a child entry of the appropriate type:
- # cookbooks, data_bags, acls, etc. All will be multiplexed (i.e. if
- # you have multiple paths for cookbooks, the multiplexed dir will grab
- # cookbooks from all of them when you list or grab them).
- #
- def make_child_entry(name)
- if CHILDREN.include?(name)
- return nil if !root_dir
- return root_dir.child(name)
- end
-
- paths = (child_paths[name] || []).select { |path| File.exists?(path) }
- if paths.size == 0
- return NonexistentFSObject.new(name, self)
- end
- case name
- when 'cookbooks'
- dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
- when 'data_bags'
- dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
- when 'acls'
- dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
- else
- data_handler = case name
- when 'clients'
- Chef::ChefFS::DataHandler::ClientDataHandler.new
- when 'environments'
- Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
- when 'nodes'
- Chef::ChefFS::DataHandler::NodeDataHandler.new
- when 'policies'
- Chef::ChefFS::DataHandler::PolicyDataHandler.new
- when 'policy_groups'
- Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new
- when 'roles'
- Chef::ChefFS::DataHandler::RoleDataHandler.new
- when 'users'
- Chef::ChefFS::DataHandler::UserDataHandler.new
- when 'groups'
- Chef::ChefFS::DataHandler::GroupDataHandler.new
- when 'containers'
- Chef::ChefFS::DataHandler::ContainerDataHandler.new
- else
- raise "Unknown top level path #{name}"
- end
- dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
- end
- MultiplexedDir.new(dirs)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb b/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
new file mode 100644
index 0000000000..fff47c986d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
@@ -0,0 +1,65 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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/chef_server/acl_entry'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclDir < BaseFSDir
+ def api_path
+ parent.parent.child(name).api_path
+ end
+
+ def make_child_entry(name, exists = nil)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || AclEntry.new(name, self, exists)
+ end
+
+ def can_have_child?(name, is_dir)
+ name =~ /\.json$/ && !is_dir
+ end
+
+ def children
+ if @children.nil?
+ # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
+ names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
+ @children = names.map { |name| make_child_entry(name, true) }
+ end
+ @children
+ end
+
+ def create_child(name, file_contents)
+ raise OperationNotAllowedError.new(:create_child, self, nil, "ACLs can only be updated, and can only be created when the corresponding object is created.")
+ end
+
+ def data_handler
+ parent.data_handler
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb b/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
new file mode 100644
index 0000000000..f87a7eaca9
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
@@ -0,0 +1,60 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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_server/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require 'chef/chef_fs/file_system/operation_failed_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclEntry < RestListEntry
+ PERMISSIONS = %w(create read update delete grant)
+
+ def api_path
+ "#{super}/_acl"
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, nil, "ACLs cannot be deleted")
+ end
+
+ def write(file_contents)
+ # ACL writes are fun.
+ acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents), self)
+ PERMISSIONS.each do |permission|
+ begin
+ rest.put("#{api_path}/#{permission}", { permission => acls[permission] })
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb b/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
new file mode 100644
index 0000000000..bc7168e31d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
@@ -0,0 +1,70 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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/chef_server/acl_dir'
+require 'chef/chef_fs/file_system/chef_server/cookbooks_acl_dir'
+require 'chef/chef_fs/file_system/chef_server/acl_entry'
+require 'chef/chef_fs/data_handler/acl_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclsDir < BaseFSDir
+ ENTITY_TYPES = %w(clients containers cookbooks data_bags environments groups nodes roles) # we don't read sandboxes, so we don't read their acls
+
+ def data_handler
+ @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
+ end
+
+ def api_path
+ parent.api_path
+ end
+
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir ? ENTITY_TYPES.include?(name) : name == 'organization.json'
+ end
+
+ def children
+ if @children.nil?
+ @children = ENTITY_TYPES.map do |entity_type|
+ case entity_type
+ when 'cookbooks'
+ CookbooksAclDir.new(entity_type, self)
+ else
+ AclDir.new(entity_type, self)
+ end
+ end
+ @children << AclEntry.new('organization.json', self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl
+ end
+ @children
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb
new file mode 100644
index 0000000000..1ac03166c2
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb
@@ -0,0 +1,192 @@
+#
+# 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/server_api'
+require 'chef/chef_fs/file_system/chef_server/acls_dir'
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/chef_server/rest_list_dir'
+require 'chef/chef_fs/file_system/chef_server/cookbooks_dir'
+require 'chef/chef_fs/file_system/chef_server/data_bags_dir'
+require 'chef/chef_fs/file_system/chef_server/nodes_dir'
+require 'chef/chef_fs/file_system/chef_server/org_entry'
+require 'chef/chef_fs/file_system/chef_server/organization_invites_entry'
+require 'chef/chef_fs/file_system/chef_server/organization_members_entry'
+require 'chef/chef_fs/file_system/chef_server/policies_dir'
+require 'chef/chef_fs/file_system/chef_server/policy_groups_dir'
+require 'chef/chef_fs/file_system/chef_server/environments_dir'
+require 'chef/chef_fs/data_handler/acl_data_handler'
+require 'chef/chef_fs/data_handler/client_data_handler'
+require 'chef/chef_fs/data_handler/environment_data_handler'
+require 'chef/chef_fs/data_handler/node_data_handler'
+require 'chef/chef_fs/data_handler/role_data_handler'
+require 'chef/chef_fs/data_handler/user_data_handler'
+require 'chef/chef_fs/data_handler/group_data_handler'
+require 'chef/chef_fs/data_handler/container_data_handler'
+require 'chef/chef_fs/data_handler/policy_group_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # Represents the root of a Chef server (or organization), under which
+ # nodes, roles, cookbooks, etc. can be found.
+ #
+ class ChefServerRootDir < BaseFSDir
+ #
+ # Create a new Chef server root.
+ #
+ # == Parameters
+ #
+ # [root_name]
+ # A friendly name for the root, for printing--like "remote" or "chef_central".
+ # [chef_config]
+ # A hash with options that look suspiciously like Chef::Config, including the
+ # following keys:
+ # :chef_server_url:: The URL to the Chef server or top of the organization
+ # :node_name:: The username to authenticate to the Chef server with
+ # :client_key:: The private key for the user for authentication
+ # :environment:: The environment in which you are presently working
+ # :repo_mode::
+ # The repository mode, :hosted_everything, :everything or :static.
+ # This determines the set of subdirectories the Chef server will
+ # offer up.
+ # :versioned_cookbooks:: whether or not to include versions in cookbook names
+ # [options]
+ # Other options:
+ # :cookbook_version:: when cookbooks are retrieved, grab this version for them.
+ # :freeze:: freeze cookbooks on upload
+ #
+ def initialize(root_name, chef_config, options = {})
+ 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 = chef_config[:repo_mode]
+ @versioned_cookbooks = chef_config[:versioned_cookbooks]
+ @root_name = root_name
+ @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version
+ end
+
+ attr_reader :chef_server_url
+ attr_reader :chef_username
+ attr_reader :chef_private_key
+ attr_reader :environment
+ attr_reader :repo_mode
+ attr_reader :cookbook_version
+ attr_reader :versioned_cookbooks
+
+ def fs_description
+ "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
+ end
+
+ def rest
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
+ end
+
+ def get_json(path)
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path)
+ end
+
+ def chef_rest
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key)
+ end
+
+ def api_path
+ ""
+ end
+
+ def path_for_printing
+ "#{@root_name}/"
+ end
+
+ def can_have_child?(name, is_dir)
+ result = children.select { |child| child.name == name }.first
+ result && !!result.dir? == !!is_dir
+ end
+
+ def org
+ @org ||= begin
+ path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
+ if File.dirname(path) == '/organizations'
+ File.basename(path)
+ else
+ # In Chef 12, everything is in an org.
+ 'chef'
+ end
+ end
+ end
+
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
+ def children
+ @children ||= begin
+ result = [
+ # /cookbooks
+ CookbooksDir.new("cookbooks", self),
+ # /data_bags
+ DataBagsDir.new("data_bags", self, "data"),
+ # /environments
+ EnvironmentsDir.new("environments", self, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new),
+ # /roles
+ RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new)
+ ]
+ if repo_mode == 'hosted_everything'
+ result += [
+ # /acls
+ AclsDir.new("acls", self),
+ # /clients
+ RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+ # /containers
+ RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
+ # /groups
+ RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
+ # /nodes
+ NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
+ # /org.json
+ OrgEntry.new("org.json", self),
+ # /members.json
+ OrganizationMembersEntry.new("members.json", self),
+ # /invitations.json
+ OrganizationInvitesEntry.new("invitations.json", self),
+ # /policies
+ PoliciesDir.new("policies", self, nil, Chef::ChefFS::DataHandler::PolicyDataHandler.new),
+ # /policy_groups
+ PolicyGroupsDir.new("policy_groups", self, nil, Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new),
+ ]
+ elsif repo_mode != 'static'
+ result += [
+ # /clients
+ RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+ # /nodes
+ NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
+ # /users
+ RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new)
+ ]
+ end
+ result.sort_by { |child| child.name }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
new file mode 100644
index 0000000000..51de0bfd86
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
@@ -0,0 +1,226 @@
+#
+# 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/command_line'
+require 'chef/chef_fs/file_system/chef_server/rest_list_dir'
+require 'chef/chef_fs/file_system/chef_server/cookbook_subdir'
+require 'chef/chef_fs/file_system/chef_server/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
+ module ChefServer
+ class CookbookDir < BaseFSDir
+ def initialize(name, parent, options = {})
+ super(name, parent)
+ @exists = options[:exists]
+ # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know
+ # the actual cookbook_name and version.
+ if root.versioned_cookbooks
+ if name =~ VALID_VERSIONED_COOKBOOK_NAME
+ @cookbook_name = $1
+ @version = $2
+ else
+ @exists = false
+ end
+ else
+ @cookbook_name = name
+ @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff
+ end
+ end
+
+ attr_reader :cookbook_name, :version
+
+ 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 => { }
+ }
+
+ # See Erchef code
+ # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
+ VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/
+
+ def add_child(child)
+ @children << child
+ end
+
+ def api_path
+ "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
+ end
+
+ def make_child_entry(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
+ children.select { |child| child.name == name }.first
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ # A cookbook's root may not have directories unless they are segment directories
+ return name != 'root_files' && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir
+ return 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
+ @children = @children.sort_by { |c| c.name }
+ end
+ @children
+ end
+
+ def dir?
+ exists?
+ end
+
+ def delete(recurse)
+ if recurse
+ begin
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
+ end
+ end
+ else
+ raise NotFoundError.new(self) if !exists?
+ raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
+ end
+ end
+
+ # In versioned cookbook mode, actually check if the version exists
+ # Probably want to cache this.
+ def exists?
+ if @exists.nil?
+ @exists = parent.children.any? { |child| child.name == name }
+ end
+ @exists
+ 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).each do |type, old_entry, new_entry|
+ if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
+ are_same = false
+ end
+ end
+ [ are_same, nil, nil ]
+ end
+
+ def copy_from(other, options = {})
+ parent.upload_cookbook_from(other, options)
+ 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(self, @could_not_get_chef_object)
+ 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 ||= Chef::CookbookVersion.json_create(root.get_json(api_path))
+ ensure
+ Chef::Config[:http_retry_count] = old_retry_count
+ end
+
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
+
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ @could_not_get_chef_object = e
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ 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 => e
+ if e.response.code == "500"
+ @could_not_get_chef_object = e
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb
new file mode 100644
index 0000000000..721c5092cf
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_file.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_object'
+require 'chef/http/simple'
+require 'openssl'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbookFile < BaseFSObject
+ def initialize(name, parent, file)
+ super(name, parent)
+ @file = file
+ end
+
+ attr_reader :file
+
+ def checksum
+ file[:checksum]
+ end
+
+ def read
+ begin
+ tmpfile = rest.streaming_request(file[:url])
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}")
+ rescue Net::HTTPServerException => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}")
+ end
+
+ begin
+ tmpfile.open
+ tmpfile.read
+ ensure
+ tmpfile.close!
+ 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)
+ OpenSSL::Digest::MD5.hexdigest(value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
new file mode 100644
index 0000000000..55c0168077
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
@@ -0,0 +1,61 @@
+#
+# 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
+ module ChefServer
+ 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 make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || NonexistentFSObject.new(name, self)
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb
new file mode 100644
index 0000000000..999fb58f79
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb
@@ -0,0 +1,43 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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_server/acl_dir'
+require 'chef/chef_fs/file_system/chef_server/acl_entry'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbooksAclDir < AclDir
+ # If versioned_cookbooks is on, the list of cookbooks will have versions
+ # in them. But all versions of a cookbook have the same acl, so even if
+ # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will
+ # only have one acl: acls/cookbooks/apache2.json. Thus, the list of
+ # children of acls/cookbooks is a unique list of cookbook *names*.
+ def children
+ if @children.nil?
+ names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
+ @children = names.uniq.map { |name| make_child_entry(name, true) }
+ end
+ @children
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
new file mode 100644
index 0000000000..28d88ea330
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
@@ -0,0 +1,154 @@
+#
+# 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_server/rest_list_dir'
+require 'chef/chef_fs/file_system/chef_server/cookbook_dir'
+require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/chef_fs/file_system/cookbook_frozen_error'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir'
+require 'chef/mixin/file_class'
+
+require 'tmpdir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbooksDir < RestListDir
+
+ include Chef::Mixin::FileClass
+
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookDir.new(name, self)
+ end
+
+ def children
+ @children ||= begin
+ if root.versioned_cookbooks
+ result = []
+ root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
+ cookbooks['versions'].each do |cookbook_version|
+ result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true)
+ end
+ end
+ else
+ result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) }
+ end
+ result.sort_by(&:name)
+ end
+ end
+
+ def create_child_from(other, options = {})
+ @children = nil
+ upload_cookbook_from(other, options)
+ end
+
+ def upload_cookbook_from(other, options = {})
+ root.versioned_cookbooks ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ case e.response.code
+ when "409"
+ raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ rescue Chef::Exceptions::CookbookFrozen => e
+ raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
+ end
+
+ # Knife currently does not understand versioned cookbooks
+ # Cookbook Version uploader also requires a lot of refactoring
+ # to make this work. So instead, we make a temporary cookbook
+ # symlinking back to real cookbook, and upload the proxy.
+ def upload_versioned_cookbook(other, options)
+ cookbook_name = Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
+
+ Dir.mktmpdir do |temp_cookbooks_path|
+ proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
+
+ # Make a symlink
+ file_class.symlink other.file_path, proxy_cookbook_path
+
+ # Instantiate a proxy loader using the temporary symlink
+ proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
+ proxy_loader.load_cookbooks
+
+ cookbook_to_upload = proxy_loader.cookbook_version
+ cookbook_to_upload.freeze_version if options[:freeze]
+
+ # Instantiate a new uploader based on the proxy loader
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
+
+ with_actual_cookbooks_dir(temp_cookbooks_path) do
+ upload_cookbook!(uploader)
+ end
+
+ #
+ # When the temporary directory is being deleted on
+ # windows, the contents of the symlink under that
+ # directory is also deleted. So explicitly remove
+ # the symlink without removing the original contents if we
+ # are running on windows
+ #
+ if Chef::Platform.windows?
+ Dir.rmdir proxy_cookbook_path
+ end
+ end
+ end
+
+ def upload_unversioned_cookbook(other, options)
+ cookbook_to_upload = other.chef_object
+ cookbook_to_upload.freeze_version if options[:freeze]
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
+
+ with_actual_cookbooks_dir(other.parent.file_path) do
+ upload_cookbook!(uploader)
+ end
+ end
+
+ # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
+ def with_actual_cookbooks_dir(actual_cookbook_path)
+ old_cookbook_path = Chef::Config.cookbook_path
+ Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
+
+ yield
+ ensure
+ Chef::Config.cookbook_path = old_cookbook_path
+ end
+
+ def upload_cookbook!(uploader, options = {})
+ if uploader.respond_to?(:upload_cookbook)
+ uploader.upload_cookbook
+ else
+ uploader.upload_cookbooks
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ return false if !is_dir
+ return false if root.versioned_cookbooks && name !~ Chef::ChefFS::FileSystem::ChefServer::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
+ return true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb b/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb
new file mode 100644
index 0000000000..b657243d64
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb
@@ -0,0 +1,71 @@
+#
+# 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_server/rest_list_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/must_delete_recursively_error'
+require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class DataBagDir < RestListDir
+ def initialize(name, parent, exists = nil)
+ super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+ @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.new(self)
+ end
+
+ def exists?
+ if @exists.nil?
+ @exists = parent.children.any? { |child| child.name == name }
+ end
+ @exists
+ end
+
+ def delete(recurse)
+ if !recurse
+ raise NotFoundError.new(self) if !exists?
+ raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
+ end
+ begin
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb b/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb
new file mode 100644
index 0000000000..50952cfc1b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb
@@ -0,0 +1,69 @@
+#
+# 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_server/rest_list_dir'
+require 'chef/chef_fs/file_system/chef_server/data_bag_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class DataBagsDir < RestListDir
+ def make_child_entry(name, exists = false)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || DataBagDir.new(name, self, exists)
+ end
+
+ def children
+ begin
+ @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) }
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout getting children: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error getting children: #{e}")
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir
+ end
+
+ def create_child(name, file_contents)
+ begin
+ rest.post(api_path, { 'name' => name })
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating child '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists")
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "HTTP error creating child '#{name}': #{e}")
+ end
+ end
+ @children = nil
+ DataBagDir.new(name, self, true)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
index 984129bab3..721c8a38d4 100644
--- a/lib/chef/chef_fs/file_system/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
@@ -17,36 +17,38 @@
#
require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
require 'chef/chef_fs/file_system/not_found_error'
require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error'
class Chef
module ChefFS
module FileSystem
- class EnvironmentsDir < RestListDir
- def make_child_entry(name, exists = nil)
- if name == '_default.json'
- DefaultEnvironmentEntry.new(name, self, exists)
- else
- super
+ module ChefServer
+ class EnvironmentsDir < RestListDir
+ def make_child_entry(name, exists = nil)
+ if name == '_default.json'
+ DefaultEnvironmentEntry.new(name, self, exists)
+ else
+ super
+ end
end
- end
- class DefaultEnvironmentEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
+ class DefaultEnvironmentEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
- def delete(recurse)
- raise NotFoundError.new(self) if !exists?
- raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self)
- end
+ def delete(recurse)
+ raise NotFoundError.new(self) if !exists?
+ raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self)
+ end
- def write(file_contents)
- raise NotFoundError.new(self) if !exists?
- raise DefaultEnvironmentCannotBeModifiedError.new(:write, self)
+ def write(file_contents)
+ raise NotFoundError.new(self) if !exists?
+ raise DefaultEnvironmentCannotBeModifiedError.new(:write, self)
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
index e739bd8f10..c0728fbe1f 100644
--- a/lib/chef/chef_fs/file_system/acl_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,41 +16,36 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/chef_fs/data_handler/node_data_handler'
class Chef
module ChefFS
module FileSystem
- class AclEntry < RestListEntry
- PERMISSIONS = %w(create read update delete grant)
-
- def api_path
- "#{super}/_acl"
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, nil, "ACLs cannot be deleted")
- end
-
- def write(file_contents)
- # ACL writes are fun.
- acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents), self)
- PERMISSIONS.each do |permission|
+ module ChefServer
+ class NodesDir < RestListDir
+ # Identical to RestListDir.children, except supports environments
+ def children
begin
- rest.put("#{api_path}/#{permission}", { permission => acls[permission] })
+ @children ||= root.get_json(env_api_path).keys.sort.map do |key|
+ make_child_entry("#{key}.json", true)
+ end
rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
end
end
end
+
+ def env_api_path
+ environment ? "environments/#{environment}/#{api_path}" : api_path
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/org_entry.rb b/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
new file mode 100644
index 0000000000..82c5764b42
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
@@ -0,0 +1,31 @@
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/org.json
+ # Represents the actual data at /organizations/NAME (the full name, etc.)
+ class OrgEntry < RestListEntry
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationDataHandler.new
+ end
+
+ # /organizations/foo/org.json -> GET /organizations/foo
+ def api_path
+ parent.api_path
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb
new file mode 100644
index 0000000000..cf276c6466
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb
@@ -0,0 +1,61 @@
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_invites_data_handler'
+require 'chef/json_compat'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/invitations.json
+ # read data from:
+ # - GET /organizations/NAME/association_requests
+ # write data to:
+ # - remove from list: DELETE /organizations/NAME/association_requests/id
+ # - add to list: POST /organizations/NAME/association_requests
+ class OrganizationInvitesEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new
+ end
+
+ # /organizations/foo/invites.json -> /organizations/foo/association_requests
+ def api_path
+ File.join(parent.api_path, 'association_requests')
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h }
+ invites = actual_invites.keys
+ (desired_invites - invites).each do |invite|
+ begin
+ rest.post(api_path, { 'user' => invite })
+ rescue Net::HTTPServerException => e
+ if e.response.code == '409'
+ Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}")
+ else
+ raise
+ end
+ end
+ end
+ (invites - desired_invites).each do |invite|
+ rest.delete(File.join(api_path, actual_invites[invite]))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb b/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb
new file mode 100644
index 0000000000..1c1c231643
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb
@@ -0,0 +1,60 @@
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_members_data_handler'
+require 'chef/json_compat'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/members.json
+ # reads data from:
+ # - GET /organizations/NAME/users
+ # writes data to:
+ # - remove from list: DELETE /organizations/NAME/users/name
+ # - add to list: POST /organizations/NAME/users/name
+ class OrganizationMembersEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new
+ end
+
+ # /organizations/foo/members.json -> /organizations/foo/users
+ def api_path
+ File.join(parent.api_path, 'users')
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ members = minimize_value(_read_json)
+ (desired_members - members).each do |member|
+ begin
+ rest.post(api_path, 'username' => member)
+ rescue Net::HTTPServerException => e
+ if %w(404 405).include?(e.response.code)
+ raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
+ else
+ raise
+ end
+ end
+ end
+ (members - desired_members).each do |member|
+ rest.delete(File.join(api_path, member))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb b/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
new file mode 100644
index 0000000000..b6c34cfee7
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
@@ -0,0 +1,160 @@
+#
+# 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_server/rest_list_dir'
+require 'chef/chef_fs/file_system/chef_server/policy_revision_entry'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # Server API:
+ # /policies - list of policies by name
+ # - /policies/NAME - represents a policy with all revisions
+ # - /policies/NAME/revisions - list of revisions for that policy
+ # - /policies/NAME/revisions/REVISION - actual policy-revision document
+ #
+ # Local Repository and ChefFS:
+ # /policies - PoliciesDir - maps to server API /policies
+ # - /policies/NAME-REVISION.json - PolicyRevision - maps to /policies/NAME/revisions/REVISION
+ #
+ class PoliciesDir < RestListDir
+ # Children: NAME-REVISION.json for all revisions of all policies
+ #
+ # /nodes: {
+ # "node1": "https://api.opscode.com/organizations/myorg/nodes/node1",
+ # "node2": "https://api.opscode.com/organizations/myorg/nodes/node2",
+ # }
+ #
+ # /policies: {
+ # "foo": {}
+ # }
+
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
+ PolicyRevisionEntry.new(name, self, exists)
+ end
+
+ # Children come from /policies in this format:
+ # {
+ # "foo": {
+ # "uri": "https://api.opscode.com/organizations/essentials/policies/foo",
+ # "revisions": {
+ # "1.0.0": {
+ #
+ # },
+ # "1.0.1": {
+ #
+ # }
+ # }
+ # }
+ # }
+ def children
+ begin
+ # Grab the names of the children, append json, and make child entries
+ @children ||= begin
+ result = []
+ data = root.get_json(api_path)
+ data.keys.sort.each do |policy_name|
+ data[policy_name]["revisions"].keys.each do |policy_revision|
+ filename = "#{policy_name}-#{policy_revision}.json"
+ result << make_child_entry(filename, true)
+ end
+ end
+ result
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if $!.response.code == "404"
+ # GET /organizations/ORG/policies returned 404, but that just might be because
+ # we are talking to an older version of the server that doesn't support policies.
+ # Do GET /orgqanizations/ORG to find out if the org exists at all.
+ # TODO use server API version instead of a second network request.
+ begin
+ root.get_json(parent.api_path)
+ # Return empty list if the organization exists but /policies didn't work
+ []
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ end
+ end
+
+ #
+ # Does POST <api_path> with file_contents
+ #
+ def create_child(name, file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ # Create the child entry that will be returned
+ result = make_child_entry(name, true)
+
+ # Normalize the file_contents before post (add defaults, etc.)
+ if data_handler
+ object = data_handler.normalize_for_post(object, result)
+ data_handler.verify_integrity(object, result) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ # POST /api_path with the normalized file_contents
+ begin
+ policy_name, policy_revision = data_handler.name_and_revision(name)
+ rest.post("#{api_path}/#{policy_name}/revisions", object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ # Clear the cache of children so that if someone asks for children
+ # again, we will get it again
+ @children = nil
+
+ result
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb b/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb
new file mode 100644
index 0000000000..76ececbd5b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb
@@ -0,0 +1,137 @@
+#
+# 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/already_exists_error'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/operation_failed_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # Represents an entire policy group.
+ # Server path: /organizations/ORG/policy_groups/GROUP
+ # Repository path: policy_groups\GROUP.json
+ # Format:
+ # {
+ # "policies": {
+ # "a": { "revision_id": "1.0.0" }
+ # }
+ # }
+ class PolicyGroupEntry < RestListEntry
+ # delete is handled normally:
+ # DELETE /organizations/ORG/policy_groups/GROUP
+
+ # read is handled normally:
+ # GET /organizations/ORG/policy_groups/GROUP
+
+ # write is different.
+ # For each policy:
+ # - PUT /organizations/ORG/policy_groups/GROUP/policies/POLICY
+ # For each policy on the server but not the client:
+ # - DELETE /organizations/ORG/policy_groups/GROUP/policies/POLICY
+ # If the server has associations for a, b and c,
+ # And the client wants associations for a, x and y,
+ # We must PUT a, x and y
+ # And DELETE b and c
+ def write(file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ if data_handler
+ object = data_handler.normalize_for_put(object, self)
+ data_handler.verify_integrity(object, self) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ begin
+
+ # this should all get carted to PolicyGroupEntry#write.
+
+ # the server demands the full policy data, but we want users' local policy_group documents to just
+ # have the data you'd get from GET /policy_groups/POLICY_GROUP. so we try to fetch that.
+
+ # ordinarily this would be POST to the normal URL, but we do PUT to
+ # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} with the full
+ # policy data, for each individual policy.
+ policy_datas = {}
+
+ object["policies"].each do |policy_name, policy_data|
+ policy_path = "/policies/#{policy_name}/revisions/#{policy_data["revision_id"]}"
+
+ get_data = begin
+ rest.get(policy_path)
+ rescue Net::HTTPServerException => e
+ raise "Could not find policy '#{policy_name}'' with revision '#{policy_data["revision_id"]}'' on the server"
+ end
+
+ # GET policy data
+ server_policy_data = Chef::JSONCompat.parse(get_data)
+
+ # if it comes back 404, raise an Exception with "Policy file X does not exist with revision Y on the server"
+
+ # otherwise, add it to the list of policyfile datas.
+ policy_datas[policy_name] = server_policy_data
+ end
+
+ begin
+ existing_group = Chef::JSONCompat.parse(self.read)
+ rescue NotFoundError
+ # It's OK if the group doesn't already exist, just means no existing policies
+ end
+
+ # now we have the fullpolicy data for each policies, which is what the PUT endpoint demands.
+ policy_datas.each do |policy_name, policy_data|
+ # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME
+ rest.put("#{api_path}/policies/#{policy_name}", policy_data)
+ end
+
+ # Now we need to remove any policies that are *not* in our current group.
+ if existing_group && existing_group['policies']
+ (existing_group['policies'].keys - policy_datas.keys).each do |policy_name|
+ rest.delete("#{api_path}/policies/#{policy_name}")
+ end
+ end
+
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ self
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/policy_groups_dir.rb b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
index c98ae50b97..0c061ebdb2 100644
--- a/lib/chef/chef_fs/file_system/policy_groups_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
@@ -17,23 +17,25 @@
#
require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/policy_group_entry'
+require 'chef/chef_fs/file_system/chef_server/policy_group_entry'
class Chef
module ChefFS
module FileSystem
- class PolicyGroupsDir < RestListDir
- def make_child_entry(name, exists = nil)
- PolicyGroupEntry.new(name, self, exists)
- end
+ module ChefServer
+ class PolicyGroupsDir < RestListDir
+ def make_child_entry(name, exists = nil)
+ PolicyGroupEntry.new(name, self, exists)
+ end
- def create_child(name, file_contents)
- entry = make_child_entry(name, true)
- entry.write(file_contents)
- @children = nil
- entry
+ def create_child(name, file_contents)
+ entry = make_child_entry(name, true)
+ entry.write(file_contents)
+ @children = nil
+ entry
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb b/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb
new file mode 100644
index 0000000000..7ea93278d0
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb
@@ -0,0 +1,25 @@
+require 'chef/chef_fs/file_system/chef_server/rest_list_entry'
+require 'chef/chef_fs/data_handler/policy_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /policies/NAME-REVISION.json
+ # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION
+ class PolicyRevisionEntry < RestListEntry
+
+ # /policies/foo-1.0.0.json -> /policies/foo/revisions/1.0.0
+ def api_path(options={})
+ policy_name, revision_id = data_handler.name_and_revision(name)
+ "#{parent.api_path}/#{policy_name}/revisions/#{revision_id}"
+ end
+
+ def write(file_contents)
+ raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb b/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb
new file mode 100644
index 0000000000..84790190d1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb
@@ -0,0 +1,179 @@
+#
+# 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/chef_server/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class RestListDir < BaseFSDir
+ def initialize(name, parent, api_path = nil, data_handler = nil)
+ super(name, parent)
+ @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
+ @data_handler = data_handler
+ end
+
+ attr_reader :api_path
+ attr_reader :data_handler
+
+ def can_have_child?(name, is_dir)
+ name =~ /\.json$/ && !is_dir
+ end
+
+ #
+ # When talking to a modern (12.0+) Chef server
+ # knife list /
+ # -> /nodes
+ # -> /policies
+ # -> /policy_groups
+ # -> /roles
+ #
+ # 12.0 or 12.1 will fail when you do this:
+ # knife list / --recursive
+ # Because it thinks /policies exists, and when it tries to list its children
+ # it gets a 404 (indicating it actually doesn't exist).
+ #
+ # With this change, knife list / --recursive will list /policies as a real, empty directory.
+ #
+ # Alternately, we could have done some sort of detection when we listed the top level
+ # and determined which endpoints the server would support, and returned only those.
+ # So you wouldn't see /policies in that case at all.
+ # The issue with that is there's no efficient way to do it because we can't find out
+ # the server version directly, and can't ask the server for a list of the endpoints it supports.
+ #
+
+
+ #
+ # Does GET /<api_path>, assumes the result is of the format:
+ #
+ # {
+ # "foo": "<api_path>/foo",
+ # "bar": "<api_path>/bar",
+ # }
+ #
+ # Children are foo.json and bar.json in this case.
+ #
+ def children
+ begin
+ # Grab the names of the children, append json, and make child entries
+ @children ||= root.get_json(api_path).keys.sort.map do |key|
+ make_child_entry("#{key}.json", true)
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if $!.response.code == "404"
+
+ if parent.is_a?(ChefServerRootDir)
+ # GET /organizations/ORG/<container> returned 404, but that just might be because
+ # we are talking to an older version of the server that doesn't support policies.
+ # Do GET /orgqanizations/ORG to find out if the org exists at all.
+ # TODO use server API version instead of a second network request.
+ begin
+ root.get_json(parent.api_path)
+ # Return empty list if the organization exists but /policies didn't work
+ []
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ else
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ end
+ end
+
+ #
+ # Does POST <api_path> with file_contents
+ #
+ def create_child(name, file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ # Create the child entry that will be returned
+ result = make_child_entry(name, true)
+
+ # Normalize the file_contents before post (add defaults, etc.)
+ if data_handler
+ object = data_handler.normalize_for_post(object, result)
+ data_handler.verify_integrity(object, result) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ # POST /api_path with the normalized file_contents
+ begin
+ rest.post(api_path, object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ # Clear the cache of children so that if someone asks for children
+ # again, we will get it again
+ @children = nil
+
+ result
+ end
+
+ def org
+ parent.org
+ end
+
+ def environment
+ parent.environment
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
+ RestListEntry.new(name, self, exists)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb b/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb
new file mode 100644
index 0000000000..4ceab42c1b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb
@@ -0,0 +1,187 @@
+#
+# 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/chef_fs/file_system/operation_failed_error'
+require 'chef/role'
+require 'chef/node'
+require 'chef/json_compat'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class RestListEntry < BaseFSObject
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ parent.data_handler
+ end
+
+ def api_child_name
+ if name.length < 5 || name[-5,5] != ".json"
+ raise "Invalid name #{path}: must end in .json"
+ end
+ name[0,name.length-5]
+ end
+
+ def api_path
+ "#{parent.api_path}/#{api_child_name}"
+ end
+
+ def org
+ parent.org
+ 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(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ end
+ end
+ end
+
+ def read
+ Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
+ end
+
+ def _read_json
+ begin
+ # Minimize the value (get rid of defaults) so the results don't look terrible
+ root.get_json(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
+ rescue Net::HTTPServerException => e
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ end
+ end
+ end
+
+ def chef_object
+ # REST will inflate the Chef object using json_class
+ data_handler.json_class.json_create(read)
+ end
+
+ def minimize_value(value)
+ data_handler.minimize(data_handler.normalize(value, self), self)
+ end
+
+ def compare_to(other)
+ # TODO this pair of reads can be parallelized
+
+ # Grab the other value
+ begin
+ other_value_json = other.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ nil, nil, :none ]
+ end
+
+ # Grab this value
+ begin
+ value = _read_json
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ false, :none, other_value_json ]
+ end
+
+ # Minimize (and normalize) both values for easy and beautiful diffs
+ value = minimize_value(value)
+ value_json = Chef::JSONCompat.to_json_pretty(value)
+ begin
+ other_value = Chef::JSONCompat.parse(other_value_json)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
+ return [ nil, value_json, other_value_json ]
+ end
+ other_value = minimize_value(other_value)
+ other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
+
+ [ value == other_value, value_json, other_value_json ]
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def write(file_contents)
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Parse error reading JSON: #{e}")
+ end
+
+ if data_handler
+ object = data_handler.normalize_for_put(object, self)
+ data_handler.verify_integrity(object, self) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, "#{error}")
+ end
+ end
+
+ begin
+ rest.put(api_path, object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ end
+ end
+
+ def api_error_text(response)
+ begin
+ Chef::JSONCompat.parse(response.body)['error'].join("\n")
+ rescue
+ response.body
+ end
+ end
+ 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
deleted file mode 100644
index badb70ce50..0000000000
--- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-#
-# 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/server_api'
-require 'chef/chef_fs/file_system/acls_dir'
-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'
-require 'chef/chef_fs/file_system/org_entry'
-require 'chef/chef_fs/file_system/organization_invites_entry'
-require 'chef/chef_fs/file_system/organization_members_entry'
-require 'chef/chef_fs/file_system/policies_dir'
-require 'chef/chef_fs/file_system/policy_groups_dir'
-require 'chef/chef_fs/file_system/environments_dir'
-require 'chef/chef_fs/data_handler/acl_data_handler'
-require 'chef/chef_fs/data_handler/client_data_handler'
-require 'chef/chef_fs/data_handler/environment_data_handler'
-require 'chef/chef_fs/data_handler/node_data_handler'
-require 'chef/chef_fs/data_handler/role_data_handler'
-require 'chef/chef_fs/data_handler/user_data_handler'
-require 'chef/chef_fs/data_handler/group_data_handler'
-require 'chef/chef_fs/data_handler/container_data_handler'
-require 'chef/chef_fs/data_handler/policy_group_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- #
- # Represents the root of a Chef server (or organization), under which
- # nodes, roles, cookbooks, etc. can be found.
- #
- class ChefServerRootDir < BaseFSDir
- #
- # Create a new Chef server root.
- #
- # == Parameters
- #
- # [root_name]
- # A friendly name for the root, for printing--like "remote" or "chef_central".
- # [chef_config]
- # A hash with options that look suspiciously like Chef::Config, including the
- # following keys:
- # :chef_server_url:: The URL to the Chef server or top of the organization
- # :node_name:: The username to authenticate to the Chef server with
- # :client_key:: The private key for the user for authentication
- # :environment:: The environment in which you are presently working
- # :repo_mode::
- # The repository mode, :hosted_everything, :everything or :static.
- # This determines the set of subdirectories the Chef server will
- # offer up.
- # :versioned_cookbooks:: whether or not to include versions in cookbook names
- # [options]
- # Other options:
- # :cookbook_version:: when cookbooks are retrieved, grab this version for them.
- # :freeze:: freeze cookbooks on upload
- #
- def initialize(root_name, chef_config, options = {})
- 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 = chef_config[:repo_mode]
- @versioned_cookbooks = chef_config[:versioned_cookbooks]
- @root_name = root_name
- @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version
- end
-
- attr_reader :chef_server_url
- attr_reader :chef_username
- attr_reader :chef_private_key
- attr_reader :environment
- attr_reader :repo_mode
- attr_reader :cookbook_version
- attr_reader :versioned_cookbooks
-
- def fs_description
- "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
- end
-
- def rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
- end
-
- def get_json(path)
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path)
- end
-
- def chef_rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key)
- end
-
- def api_path
- ""
- end
-
- def path_for_printing
- "#{@root_name}/"
- end
-
- def can_have_child?(name, is_dir)
- result = children.select { |child| child.name == name }.first
- result && !!result.dir? == !!is_dir
- end
-
- def org
- @org ||= begin
- path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
- if File.dirname(path) == '/organizations'
- File.basename(path)
- else
- # In Chef 12, everything is in an org.
- 'chef'
- end
- end
- end
-
- def make_child_entry(name)
- children.select { |child| child.name == name }.first
- end
-
- def children
- @children ||= begin
- result = [
- # /cookbooks
- CookbooksDir.new("cookbooks", self),
- # /data_bags
- DataBagsDir.new("data_bags", self, "data"),
- # /environments
- EnvironmentsDir.new("environments", self, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new),
- # /roles
- RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new)
- ]
- if repo_mode == 'hosted_everything'
- result += [
- # /acls
- AclsDir.new("acls", self),
- # /clients
- RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
- # /containers
- RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
- # /groups
- RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
- # /nodes
- NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
- # /org.json
- OrgEntry.new("org.json", self),
- # /members.json
- OrganizationMembersEntry.new("members.json", self),
- # /invitations.json
- OrganizationInvitesEntry.new("invitations.json", self),
- # /policies
- PoliciesDir.new("policies", self, nil, Chef::ChefFS::DataHandler::PolicyDataHandler.new),
- # /policy_groups
- PolicyGroupsDir.new("policy_groups", self, nil, Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new),
- ]
- elsif repo_mode != 'static'
- result += [
- # /clients
- RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
- # /nodes
- NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
- # /users
- RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new)
- ]
- end
- result.sort_by { |child| child.name }
- end
- end
- 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
deleted file mode 100644
index 42408c0869..0000000000
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ /dev/null
@@ -1,224 +0,0 @@
-#
-# 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/command_line'
-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, options = {})
- super(name, parent)
- @exists = options[:exists]
- # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know
- # the actual cookbook_name and version.
- if root.versioned_cookbooks
- if name =~ VALID_VERSIONED_COOKBOOK_NAME
- @cookbook_name = $1
- @version = $2
- else
- @exists = false
- end
- else
- @cookbook_name = name
- @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff
- end
- end
-
- attr_reader :cookbook_name, :version
-
- 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 => { }
- }
-
- # See Erchef code
- # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
- VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/
-
- def add_child(child)
- @children << child
- end
-
- def api_path
- "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
- end
-
- def make_child_entry(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
- children.select { |child| child.name == name }.first
- rescue Chef::ChefFS::FileSystem::NotFoundError
- nil
- end
- end
-
- def can_have_child?(name, is_dir)
- # A cookbook's root may not have directories unless they are segment directories
- return name != 'root_files' && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir
- return 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
- @children = @children.sort_by { |c| c.name }
- end
- @children
- end
-
- def dir?
- exists?
- end
-
- def delete(recurse)
- if recurse
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
- rescue Net::HTTPServerException
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
- end
- end
- else
- raise NotFoundError.new(self) if !exists?
- raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
- end
- end
-
- # In versioned cookbook mode, actually check if the version exists
- # Probably want to cache this.
- def exists?
- if @exists.nil?
- @exists = parent.children.any? { |child| child.name == name }
- end
- @exists
- 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).each do |type, old_entry, new_entry|
- if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
- are_same = false
- end
- end
- [ are_same, nil, nil ]
- end
-
- def copy_from(other, options = {})
- parent.upload_cookbook_from(other, options)
- 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(self, @could_not_get_chef_object)
- 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 ||= Chef::CookbookVersion.json_create(root.get_json(api_path))
- ensure
- Chef::Config[:http_retry_count] = old_retry_count
- end
-
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
-
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- @could_not_get_chef_object = e
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
- 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 => e
- if e.response.code == "500"
- @could_not_get_chef_object = e
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
- 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
deleted file mode 100644
index f755174183..0000000000
--- a/lib/chef/chef_fs/file_system/cookbook_file.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# 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/http/simple'
-require 'openssl'
-
-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
- begin
- tmpfile = rest.streaming_request(file[:url])
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}")
- rescue Net::HTTPServerException => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}")
- end
-
- begin
- tmpfile.open
- tmpfile.read
- ensure
- tmpfile.close!
- 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)
- OpenSSL::Digest::MD5.hexdigest(value)
- 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
deleted file mode 100644
index e7a6d3bccd..0000000000
--- a/lib/chef/chef_fs/file_system/cookbook_subdir.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# 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 make_child_entry(name)
- result = @children.select { |child| child.name == name }.first if @children
- result || NonexistentFSObject.new(name, self)
- end
-
- def rest
- parent.rest
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
deleted file mode 100644
index 560ceb4886..0000000000
--- a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 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/acl_dir'
-require 'chef/chef_fs/file_system/acl_entry'
-
-class Chef
- module ChefFS
- module FileSystem
- class CookbooksAclDir < AclDir
- # If versioned_cookbooks is on, the list of cookbooks will have versions
- # in them. But all versions of a cookbook have the same acl, so even if
- # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will
- # only have one acl: acls/cookbooks/apache2.json. Thus, the list of
- # children of acls/cookbooks is a unique list of cookbook *names*.
- def children
- if @children.nil?
- names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
- @children = names.uniq.map { |name| make_child_entry(name, true) }
- end
- @children
- 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
deleted file mode 100644
index 8cbc06931b..0000000000
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# 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'
-require 'chef/chef_fs/file_system/operation_failed_error'
-require 'chef/chef_fs/file_system/cookbook_frozen_error'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
-require 'chef/mixin/file_class'
-
-require 'tmpdir'
-
-class Chef
- module ChefFS
- module FileSystem
- class CookbooksDir < RestListDir
-
- include Chef::Mixin::FileClass
-
- def make_child_entry(name)
- result = @children.select { |child| child.name == name }.first if @children
- result || CookbookDir.new(name, self)
- end
-
- def children
- @children ||= begin
- if root.versioned_cookbooks
- result = []
- root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
- cookbooks['versions'].each do |cookbook_version|
- result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true)
- end
- end
- else
- result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) }
- end
- result.sort_by(&:name)
- end
- end
-
- def create_child_from(other, options = {})
- @children = nil
- upload_cookbook_from(other, options)
- end
-
- def upload_cookbook_from(other, options = {})
- root.versioned_cookbooks ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
- rescue Net::HTTPServerException => e
- case e.response.code
- when "409"
- raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
- end
- rescue Chef::Exceptions::CookbookFrozen => e
- raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
- end
-
- # Knife currently does not understand versioned cookbooks
- # Cookbook Version uploader also requires a lot of refactoring
- # to make this work. So instead, we make a temporary cookbook
- # symlinking back to real cookbook, and upload the proxy.
- def upload_versioned_cookbook(other, options)
- cookbook_name = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
-
- Dir.mktmpdir do |temp_cookbooks_path|
- proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
-
- # Make a symlink
- file_class.symlink other.file_path, proxy_cookbook_path
-
- # Instantiate a proxy loader using the temporary symlink
- proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
- proxy_loader.load_cookbooks
-
- cookbook_to_upload = proxy_loader.cookbook_version
- cookbook_to_upload.freeze_version if options[:freeze]
-
- # Instantiate a new uploader based on the proxy loader
- uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
-
- with_actual_cookbooks_dir(temp_cookbooks_path) do
- upload_cookbook!(uploader)
- end
-
- #
- # When the temporary directory is being deleted on
- # windows, the contents of the symlink under that
- # directory is also deleted. So explicitly remove
- # the symlink without removing the original contents if we
- # are running on windows
- #
- if Chef::Platform.windows?
- Dir.rmdir proxy_cookbook_path
- end
- end
- end
-
- def upload_unversioned_cookbook(other, options)
- cookbook_to_upload = other.chef_object
- cookbook_to_upload.freeze_version if options[:freeze]
- uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
-
- with_actual_cookbooks_dir(other.parent.file_path) do
- upload_cookbook!(uploader)
- end
- end
-
- # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
- def with_actual_cookbooks_dir(actual_cookbook_path)
- old_cookbook_path = Chef::Config.cookbook_path
- Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
-
- yield
- ensure
- Chef::Config.cookbook_path = old_cookbook_path
- end
-
- def upload_cookbook!(uploader, options = {})
- if uploader.respond_to?(:upload_cookbook)
- uploader.upload_cookbook
- else
- uploader.upload_cookbooks
- end
- end
-
- def can_have_child?(name, is_dir)
- return false if !is_dir
- return false if root.versioned_cookbooks && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
- return true
- 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
deleted file mode 100644
index 7a4463e69d..0000000000
--- a/lib/chef/chef_fs/file_system/data_bag_dir.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# 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/not_found_error'
-require 'chef/chef_fs/file_system/must_delete_recursively_error'
-require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class DataBagDir < RestListDir
- def initialize(name, parent, exists = nil)
- super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
- @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.new(self)
- end
-
- def exists?
- if @exists.nil?
- @exists = parent.children.any? { |child| child.name == name }
- end
- @exists
- end
-
- def delete(recurse)
- if !recurse
- raise NotFoundError.new(self) if !exists?
- raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
- end
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
- 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
deleted file mode 100644
index 0645a6c16f..0000000000
--- a/lib/chef/chef_fs/file_system/data_bags_dir.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# 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 make_child_entry(name, exists = false)
- result = @children.select { |child| child.name == name }.first if @children
- result || DataBagDir.new(name, self, exists)
- end
-
- def children
- begin
- @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) }
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout getting children: #{e}")
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error getting children: #{e}")
- end
- end
- end
-
- def can_have_child?(name, is_dir)
- is_dir
- end
-
- def create_child(name, file_contents)
- begin
- rest.post(api_path, { 'name' => name })
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating child '#{name}': #{e}")
- rescue Net::HTTPServerException => e
- if e.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists")
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "HTTP error creating child '#{name}': #{e}")
- end
- end
- @children = nil
- 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
deleted file mode 100644
index 5ce8b3320a..0000000000
--- a/lib/chef/chef_fs/file_system/file_system_entry.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# 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/already_exists_error'
-require 'chef/chef_fs/file_system/must_delete_recursively_error'
-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
- file_path
- end
-
- def children
- # Except cookbooks and data bag dirs, all things must be json files
- begin
- Dir.entries(file_path).sort.
- map { |child_name| make_child_entry(child_name) }.
- select { |child| child && can_have_child?(child.name, child.dir?) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def create_child(child_name, file_contents=nil)
- child = make_child_entry(child_name)
- if child.exists?
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
- end
- if file_contents
- child.write(file_contents)
- else
- begin
- Dir.mkdir(child.file_path)
- rescue Errno::EEXIST
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
- end
- end
- child
- end
-
- def dir?
- File.directory?(file_path)
- end
-
- def delete(recurse)
- begin
- if dir?
- if !recurse
- raise MustDeleteRecursivelyError.new(self, $!)
- end
- FileUtils.rm_r(file_path)
- else
- File.delete(file_path)
- end
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def exists?
- File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
- end
-
- def read
- begin
- File.open(file_path, "rb") {|f| f.read}
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def write(content)
- File.open(file_path, 'wb') do |file|
- file.write(content)
- end
- end
-
- protected
-
- def make_child_entry(child_name)
- FileSystemEntry.new(child_name, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_dir.rb b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
new file mode 100644
index 0000000000..b571a6f469
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
@@ -0,0 +1,53 @@
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/memory/memory_file'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
+ def initialize(name, parent)
+ super(name, parent)
+ @children = []
+ end
+
+ attr_reader :children
+
+ def make_child_entry(name)
+ @children.select { |child| child.name == name }.first
+ end
+
+ def add_child(child)
+ @children.push(child)
+ end
+
+ def can_have_child?(name, is_dir)
+ root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
+ end
+
+ def add_file(path, value)
+ path_parts = path.split('/')
+ dir = add_dir(path_parts[0..-2].join('/'))
+ file = MemoryFile.new(path_parts[-1], dir, value)
+ dir.add_child(file)
+ file
+ end
+
+ def add_dir(path)
+ path_parts = path.split('/')
+ dir = self
+ path_parts.each do |path_part|
+ subdir = dir.child(path_part)
+ if !subdir.exists?
+ subdir = MemoryDir.new(path_part, dir)
+ dir.add_child(subdir)
+ end
+ dir = subdir
+ end
+ dir
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_file.rb b/lib/chef/chef_fs/file_system/memory/memory_file.rb
new file mode 100644
index 0000000000..8b280eeb7d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_file.rb
@@ -0,0 +1,19 @@
+require 'chef/chef_fs/file_system/base_fs_object'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
+ def initialize(name, parent, value)
+ super(name, parent)
+ @value = value
+ end
+ def read
+ return @value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_root.rb b/lib/chef/chef_fs/file_system/memory/memory_root.rb
new file mode 100644
index 0000000000..f4a7b5208b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_root.rb
@@ -0,0 +1,23 @@
+require 'chef/chef_fs/file_system/memory/memory_dir'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryRoot < MemoryDir
+ def initialize(pretty_name, cannot_be_in_regex = nil)
+ super('', nil)
+ @pretty_name = pretty_name
+ @cannot_be_in_regex = cannot_be_in_regex
+ end
+
+ attr_reader :cannot_be_in_regex
+
+ def path_for_printing
+ @pretty_name
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
deleted file mode 100644
index 260a91693c..0000000000
--- a/lib/chef/chef_fs/file_system/memory_dir.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/memory_file'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
- def initialize(name, parent)
- super(name, parent)
- @children = []
- end
-
- attr_reader :children
-
- def make_child_entry(name)
- @children.select { |child| child.name == name }.first
- end
-
- def add_child(child)
- @children.push(child)
- end
-
- def can_have_child?(name, is_dir)
- root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
- end
-
- def add_file(path, value)
- path_parts = path.split('/')
- dir = add_dir(path_parts[0..-2].join('/'))
- file = MemoryFile.new(path_parts[-1], dir, value)
- dir.add_child(file)
- file
- end
-
- def add_dir(path)
- path_parts = path.split('/')
- dir = self
- path_parts.each do |path_part|
- subdir = dir.child(path_part)
- if !subdir.exists?
- subdir = MemoryDir.new(path_part, dir)
- dir.add_child(subdir)
- end
- dir = subdir
- end
- dir
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/memory_file.rb b/lib/chef/chef_fs/file_system/memory_file.rb
deleted file mode 100644
index 0c44e703f1..0000000000
--- a/lib/chef/chef_fs/file_system/memory_file.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'chef/chef_fs/file_system/base_fs_object'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
- def initialize(name, parent, value)
- super(name, parent)
- @value = value
- end
- def read
- return @value
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/memory_root.rb b/lib/chef/chef_fs/file_system/memory_root.rb
deleted file mode 100644
index 4a83830946..0000000000
--- a/lib/chef/chef_fs/file_system/memory_root.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'chef/chef_fs/file_system/memory_dir'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryRoot < MemoryDir
- def initialize(pretty_name, cannot_be_in_regex = nil)
- super('', nil)
- @pretty_name = pretty_name
- @cannot_be_in_regex = cannot_be_in_regex
- end
-
- attr_reader :cannot_be_in_regex
-
- def path_for_printing
- @pretty_name
- 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
deleted file mode 100644
index 12bc1c06c8..0000000000
--- a/lib/chef/chef_fs/file_system/nodes_dir.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# 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'
-require 'chef/chef_fs/data_handler/node_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class NodesDir < RestListDir
- # Identical to RestListDir.children, except supports environments
- def children
- begin
- @children ||= root.get_json(env_api_path).keys.sort.map do |key|
- make_child_entry("#{key}.json", true)
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
- rescue Net::HTTPServerException => e
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
- end
- end
- end
-
- def env_api_path
- environment ? "environments/#{environment}/#{api_path}" : api_path
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/org_entry.rb b/lib/chef/chef_fs/file_system/org_entry.rb
deleted file mode 100644
index df3acba528..0000000000
--- a/lib/chef/chef_fs/file_system/org_entry.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/org.json
- # Represents the actual data at /organizations/NAME (the full name, etc.)
- class OrgEntry < RestListEntry
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationDataHandler.new
- end
-
- # /organizations/foo/org.json -> GET /organizations/foo
- def api_path
- parent.api_path
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
deleted file mode 100644
index 5df37085cb..0000000000
--- a/lib/chef/chef_fs/file_system/organization_invites_entry.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_invites_data_handler'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/invitations.json
- # read data from:
- # - GET /organizations/NAME/association_requests
- # write data to:
- # - remove from list: DELETE /organizations/NAME/association_requests/id
- # - add to list: POST /organizations/NAME/association_requests
- class OrganizationInvitesEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new
- end
-
- # /organizations/foo/invites.json -> /organizations/foo/association_requests
- def api_path
- File.join(parent.api_path, 'association_requests')
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
-
- def write(contents)
- desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
- actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h }
- invites = actual_invites.keys
- (desired_invites - invites).each do |invite|
- begin
- rest.post(api_path, { 'user' => invite })
- rescue Net::HTTPServerException => e
- if e.response.code == '409'
- Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}")
- else
- raise
- end
- end
- end
- (invites - desired_invites).each do |invite|
- rest.delete(File.join(api_path, actual_invites[invite]))
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb
deleted file mode 100644
index 40042a9cbc..0000000000
--- a/lib/chef/chef_fs/file_system/organization_members_entry.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_members_data_handler'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/members.json
- # reads data from:
- # - GET /organizations/NAME/users
- # writes data to:
- # - remove from list: DELETE /organizations/NAME/users/name
- # - add to list: POST /organizations/NAME/users/name
- class OrganizationMembersEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new
- end
-
- # /organizations/foo/members.json -> /organizations/foo/users
- def api_path
- File.join(parent.api_path, 'users')
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
-
- def write(contents)
- desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
- members = minimize_value(_read_json)
- (desired_members - members).each do |member|
- begin
- rest.post(api_path, 'username' => member)
- rescue Net::HTTPServerException => e
- if %w(404 405).include?(e.response.code)
- raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
- else
- raise
- end
- end
- end
- (members - desired_members).each do |member|
- rest.delete(File.join(api_path, member))
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/policies_dir.rb b/lib/chef/chef_fs/file_system/policies_dir.rb
deleted file mode 100644
index a999ca0218..0000000000
--- a/lib/chef/chef_fs/file_system/policies_dir.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-#
-# 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/policy_revision_entry'
-
-class Chef
- module ChefFS
- module FileSystem
- #
- # Server API:
- # /policies - list of policies by name
- # - /policies/NAME - represents a policy with all revisions
- # - /policies/NAME/revisions - list of revisions for that policy
- # - /policies/NAME/revisions/REVISION - actual policy-revision document
- #
- # Local Repository and ChefFS:
- # /policies - PoliciesDir - maps to server API /policies
- # - /policies/NAME-REVISION.json - PolicyRevision - maps to /policies/NAME/revisions/REVISION
- #
- class PoliciesDir < RestListDir
- # Children: NAME-REVISION.json for all revisions of all policies
- #
- # /nodes: {
- # "node1": "https://api.opscode.com/organizations/myorg/nodes/node1",
- # "node2": "https://api.opscode.com/organizations/myorg/nodes/node2",
- # }
- #
- # /policies: {
- # "foo": {}
- # }
-
- def make_child_entry(name, exists = nil)
- @children.select { |child| child.name == name }.first if @children
- PolicyRevisionEntry.new(name, self, exists)
- end
-
- # Children come from /policies in this format:
- # {
- # "foo": {
- # "uri": "https://api.opscode.com/organizations/essentials/policies/foo",
- # "revisions": {
- # "1.0.0": {
- #
- # },
- # "1.0.1": {
- #
- # }
- # }
- # }
- # }
- def children
- begin
- # Grab the names of the children, append json, and make child entries
- @children ||= begin
- result = []
- data = root.get_json(api_path)
- data.keys.sort.each do |policy_name|
- data[policy_name]["revisions"].keys.each do |policy_revision|
- filename = "#{policy_name}-#{policy_revision}.json"
- result << make_child_entry(filename, true)
- end
- end
- result
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
- rescue Net::HTTPServerException => e
- # 404 = NotFoundError
- if $!.response.code == "404"
- # GET /organizations/ORG/policies returned 404, but that just might be because
- # we are talking to an older version of the server that doesn't support policies.
- # Do GET /orgqanizations/ORG to find out if the org exists at all.
- # TODO use server API version instead of a second network request.
- begin
- root.get_json(parent.api_path)
- # Return empty list if the organization exists but /policies didn't work
- []
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
- end
- # Anything else is unexpected (OperationFailedError)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
- end
- end
- end
-
- #
- # Does POST <api_path> with file_contents
- #
- def create_child(name, file_contents)
- # Parse the contents to ensure they are valid JSON
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
- end
-
- # Create the child entry that will be returned
- result = make_child_entry(name, true)
-
- # Normalize the file_contents before post (add defaults, etc.)
- if data_handler
- object = data_handler.normalize_for_post(object, result)
- data_handler.verify_integrity(object, result) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
- end
- end
-
- # POST /api_path with the normalized file_contents
- begin
- policy_name, policy_revision = data_handler.name_and_revision(name)
- rest.post("#{api_path}/#{policy_name}/revisions", object)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
- rescue Net::HTTPServerException => e
- # 404 = NotFoundError
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- # 409 = AlreadyExistsError
- elsif $!.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
- # Anything else is unexpected (OperationFailedError)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
- end
- end
-
- # Clear the cache of children so that if someone asks for children
- # again, we will get it again
- @children = nil
-
- result
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/policy_group_entry.rb b/lib/chef/chef_fs/file_system/policy_group_entry.rb
deleted file mode 100644
index 9885310cc7..0000000000
--- a/lib/chef/chef_fs/file_system/policy_group_entry.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-#
-# 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/already_exists_error'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/operation_failed_error'
-
-class Chef
- module ChefFS
- module FileSystem
- # Represents an entire policy group.
- # Server path: /organizations/ORG/policy_groups/GROUP
- # Repository path: policy_groups\GROUP.json
- # Format:
- # {
- # "policies": {
- # "a": { "revision_id": "1.0.0" }
- # }
- # }
- class PolicyGroupEntry < RestListEntry
- # delete is handled normally:
- # DELETE /organizations/ORG/policy_groups/GROUP
-
- # read is handled normally:
- # GET /organizations/ORG/policy_groups/GROUP
-
- # write is different.
- # For each policy:
- # - PUT /organizations/ORG/policy_groups/GROUP/policies/POLICY
- # For each policy on the server but not the client:
- # - DELETE /organizations/ORG/policy_groups/GROUP/policies/POLICY
- # If the server has associations for a, b and c,
- # And the client wants associations for a, x and y,
- # We must PUT a, x and y
- # And DELETE b and c
- def write(file_contents)
- # Parse the contents to ensure they are valid JSON
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
- end
-
- if data_handler
- object = data_handler.normalize_for_put(object, self)
- data_handler.verify_integrity(object, self) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
- end
- end
-
- begin
-
- # this should all get carted to PolicyGroupEntry#write.
-
- # the server demands the full policy data, but we want users' local policy_group documents to just
- # have the data you'd get from GET /policy_groups/POLICY_GROUP. so we try to fetch that.
-
- # ordinarily this would be POST to the normal URL, but we do PUT to
- # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} with the full
- # policy data, for each individual policy.
- policy_datas = {}
-
- object["policies"].each do |policy_name, policy_data|
- policy_path = "/policies/#{policy_name}/revisions/#{policy_data["revision_id"]}"
-
- get_data = begin
- rest.get(policy_path)
- rescue Net::HTTPServerException => e
- raise "Could not find policy '#{policy_name}'' with revision '#{policy_data["revision_id"]}'' on the server"
- end
-
- # GET policy data
- server_policy_data = Chef::JSONCompat.parse(get_data)
-
- # if it comes back 404, raise an Exception with "Policy file X does not exist with revision Y on the server"
-
- # otherwise, add it to the list of policyfile datas.
- policy_datas[policy_name] = server_policy_data
- end
-
- begin
- existing_group = Chef::JSONCompat.parse(self.read)
- rescue NotFoundError
- # It's OK if the group doesn't already exist, just means no existing policies
- end
-
- # now we have the fullpolicy data for each policies, which is what the PUT endpoint demands.
- policy_datas.each do |policy_name, policy_data|
- # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME
- rest.put("#{api_path}/policies/#{policy_name}", policy_data)
- end
-
- # Now we need to remove any policies that are *not* in our current group.
- if existing_group && existing_group['policies']
- (existing_group['policies'].keys - policy_datas.keys).each do |policy_name|
- rest.delete("#{api_path}/policies/#{policy_name}")
- end
- end
-
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
- rescue Net::HTTPServerException => e
- # 404 = NotFoundError
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- # 409 = AlreadyExistsError
- elsif $!.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
- # Anything else is unexpected (OperationFailedError)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
- end
- end
-
- self
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/policy_revision_entry.rb b/lib/chef/chef_fs/file_system/policy_revision_entry.rb
deleted file mode 100644
index 896586c54f..0000000000
--- a/lib/chef/chef_fs/file_system/policy_revision_entry.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/policy_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- # /policies/NAME-REVISION.json
- # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION
- class PolicyRevisionEntry < RestListEntry
-
- # /policies/foo-1.0.0.json -> /policies/foo/revisions/1.0.0
- def api_path(options={})
- policy_name, revision_id = data_handler.name_and_revision(name)
- "#{parent.api_path}/#{policy_name}/revisions/#{revision_id}"
- end
-
- def write(file_contents)
- raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes")
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb
index 7d2a930633..7d647c0fc3 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb
@@ -16,22 +16,24 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/acls_dir'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/chef_server/acls_dir'
require 'chef/chef_fs/data_handler/acl_data_handler'
class Chef
module ChefFS
module FileSystem
- class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, path = nil)
- super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new)
- end
+ module Repository
+ class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, path = nil)
+ super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new)
+ end
- def can_have_child?(name, is_dir)
- is_dir ? Chef::ChefFS::FileSystem::AclsDir::ENTITY_TYPES.include?(name) : name == 'organization.json'
+ def can_have_child?(name, is_dir)
+ is_dir ? Chef::ChefFS::FileSystem::ChefServer::AclsDir::ENTITY_TYPES.include?(name) : name == 'organization.json'
+ end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb
new file mode 100644
index 0000000000..91b8652de3
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb
@@ -0,0 +1,104 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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/repository/chef_repository_file_system_cookbook_entry'
+require 'chef/chef_fs/file_system/chef_server/cookbook_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/cookbook/chefignore'
+require 'chef/cookbook/cookbook_version_loader'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
+ def initialize(name, parent, file_path = nil)
+ super(name, parent, file_path)
+ end
+
+ def chef_object
+ begin
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ # We need the canonical cookbook name if we are using versioned cookbooks, but we don't
+ # want to spend a lot of time adding code to the main Chef libraries
+ if root.versioned_cookbooks
+ canonical_name = canonical_cookbook_name(File.basename(file_path))
+ raise "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z" unless canonical_name
+
+ # KLUDGE: We shouldn't have to use instance_variable_set
+ loader.instance_variable_set(:@cookbook_name, canonical_name)
+ end
+
+ loader.load_cookbooks
+ cb = loader.cookbook_version
+ if !cb
+ Chef::Log.error("Cookbook #{file_path} empty.")
+ raise "Cookbook #{file_path} empty."
+ end
+ cb
+ rescue => e
+ Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}")
+ Chef::Log.error(e.backtrace.join("\n"))
+ raise
+ end
+ end
+
+ def children
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ # Only the given directories will be uploaded.
+ return Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files'
+ elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE
+ return false
+ end
+ super(name, is_dir)
+ end
+
+ # Exposed as a class method so that it can be used elsewhere
+ def self.canonical_cookbook_name(entry_name)
+ name_match = Chef::ChefFS::FileSystem::ChefServer::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name)
+ return nil if name_match.nil?
+ return name_match[1]
+ end
+
+ def canonical_cookbook_name(entry_name)
+ self.class.canonical_cookbook_name(entry_name)
+ end
+
+ def uploaded_cookbook_version_path
+ File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
+ end
+
+ def can_upload?
+ File.exists?(uploaded_cookbook_version_path) || children.size > 0
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ segment_info = Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
+ ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb
new file mode 100644
index 0000000000..574ed44828
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb
@@ -0,0 +1,82 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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/repository/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbookEntry < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false)
+ super(name, parent, file_path)
+ @ruby_only = ruby_only
+ @recursive = recursive
+ end
+
+ attr_reader :ruby_only
+ attr_reader :recursive
+
+ def children
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ return recursive && name != '.' && name != '..'
+ elsif ruby_only
+ return false if name[-3..-1] != '.rb'
+ end
+
+ # Check chefignore
+ ignorer = parent
+ loop do
+ if ignorer.is_a?(ChefRepositoryFileSystemCookbooksDir)
+ # Grab the path from entry to child
+ path_to_child = name
+ child = self
+ while child.parent != 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 || !ignorer.chefignore.ignored?(path_to_child)
+ end
+ ignorer = ignorer.parent
+ break unless ignorer
+ end
+
+ true
+ end
+
+ def write_pretty_json
+ false
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb
new file mode 100644
index 0000000000..5c88aa2aa1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb
@@ -0,0 +1,84 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 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/repository/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir'
+require 'chef/cookbook/chefignore'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, file_path)
+ super(name, parent, file_path)
+ begin
+ @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
+ rescue Errno::EISDIR
+ rescue Errno::EACCES
+ # Work around a bug in Chefignore when chefignore is a directory
+ end
+ end
+
+ attr_reader :chefignore
+
+ def children
+ super.select do |entry|
+ # empty cookbooks and cookbook directories are ignored
+ if !entry.can_upload?
+ Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
+ false
+ else
+ true
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir && !name.start_with?('.')
+ end
+
+ def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
+ cookbook_name = File.basename(cookbook_path)
+ child = make_child_entry(cookbook_name)
+
+ # Use the copy/diff algorithm to copy it down so we don't destroy
+ # chefignored data. This is terribly un-thread-safe.
+ Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, child, nil, {:purge => true})
+
+ # Write out .uploaded-cookbook-version.json
+ cookbook_file_path = File.join(file_path, cookbook_name)
+ if !File.exists?(cookbook_file_path)
+ FileUtils.mkdir_p(cookbook_file_path)
+ end
+ uploaded_cookbook_version_path = File.join(cookbook_file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
+ File.open(uploaded_cookbook_version_path, 'w') do |file|
+ file.write(cookbook_version_json)
+ end
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemCookbookDir.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb
index 73556b2c0b..a0d564704a 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb
@@ -16,19 +16,21 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_entry'
require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
class Chef
module ChefFS
module FileSystem
- class ChefRepositoryFileSystemDataBagsDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, path = nil)
- super(name, parent, path, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
- end
+ module Repository
+ class ChefRepositoryFileSystemDataBagsDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, path = nil)
+ super(name, parent, path, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+ end
- def can_have_child?(name, is_dir)
- is_dir && !name.start_with?('.')
+ def can_have_child?(name, is_dir)
+ is_dir && !name.start_with?('.')
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb
new file mode 100644
index 0000000000..92658c07fa
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb
@@ -0,0 +1,83 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Author:: Ho-Sheng Hsiao (<hosh@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/repository/file_system_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
+ # except can inflate Chef objects
+ class ChefRepositoryFileSystemEntry < FileSystemEntry
+ def initialize(name, parent, file_path = nil, data_handler = nil)
+ super(name, parent, file_path)
+ @data_handler = data_handler
+ end
+
+ def write_pretty_json=(value)
+ @write_pretty_json = value
+ end
+
+ def write_pretty_json
+ @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
+ end
+
+ def data_handler
+ @data_handler || parent.data_handler
+ end
+
+ def chef_object
+ begin
+ return data_handler.chef_object(Chef::JSONCompat.parse(read))
+ rescue
+ Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
+ end
+ nil
+ end
+
+ def can_have_child?(name, is_dir)
+ !is_dir && name[-5..-1] == '.json'
+ end
+
+ def write(file_contents)
+ if file_contents && write_pretty_json && name[-5..-1] == '.json'
+ file_contents = minimize(file_contents, self)
+ end
+ super(file_contents)
+ end
+
+ def minimize(file_contents, entry)
+ object = Chef::JSONCompat.parse(file_contents)
+ object = data_handler.normalize(object, entry)
+ object = data_handler.minimize(object, entry)
+ Chef::JSONCompat.to_json_pretty(object)
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemEntry.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb
new file mode 100644
index 0000000000..c189323c9a
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb
@@ -0,0 +1,201 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012-2016 Chef Software, 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/repository/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir'
+require 'chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir'
+require 'chef/chef_fs/file_system/multiplexed_dir'
+require 'chef/chef_fs/data_handler/client_data_handler'
+require 'chef/chef_fs/data_handler/environment_data_handler'
+require 'chef/chef_fs/data_handler/node_data_handler'
+require 'chef/chef_fs/data_handler/policy_data_handler'
+require 'chef/chef_fs/data_handler/policy_group_data_handler'
+require 'chef/chef_fs/data_handler/role_data_handler'
+require 'chef/chef_fs/data_handler/user_data_handler'
+require 'chef/chef_fs/data_handler/group_data_handler'
+require 'chef/chef_fs/data_handler/container_data_handler'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+
+ #
+ # Represents the root of a local Chef repository, with directories for
+ # nodes, cookbooks, roles, etc. under it.
+ #
+ class ChefRepositoryFileSystemRootDir < BaseFSDir
+ #
+ # Create a new Chef Repository File System root.
+ #
+ # == Parameters
+ # [child_paths]
+ # A hash of child paths, e.g.:
+ # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ],
+ # "roles" => [ '/var/roles' ],
+ # ...
+ # [root_paths]
+ # An array of paths representing the top level, where
+ # +org.json+, +members.json+, and +invites.json+ will be stored.
+ # [chef_config] - a hash of options that looks suspiciously like the ones
+ # stored in Chef::Config, containing at least these keys:
+ # :versioned_cookbooks:: whether to include versions in cookbook names
+ def initialize(child_paths, root_paths=[], chef_config=Chef::Config)
+ super("", nil)
+ @child_paths = child_paths
+ @root_paths = root_paths
+ @versioned_cookbooks = chef_config[:versioned_cookbooks]
+ end
+
+ attr_accessor :write_pretty_json
+
+ attr_reader :root_paths
+ attr_reader :child_paths
+ attr_reader :versioned_cookbooks
+
+ CHILDREN = %w(org.json invitations.json members.json)
+
+ def children
+ @children ||= begin
+ result = child_paths.keys.sort.map { |name| make_child_entry(name) }
+ result += CHILDREN.map { |name| make_child_entry(name) }
+ result.select { |c| c && c.exists? }.sort_by { |c| c.name }
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ child_paths.has_key?(name)
+ elsif root_dir
+ CHILDREN.include?(name)
+ else
+ false
+ end
+ end
+
+ def create_child(name, file_contents = nil)
+ if file_contents
+ child = root_dir.create_child(name, file_contents)
+ else
+ child_paths[name].each do |path|
+ begin
+ Dir.mkdir(path)
+ rescue Errno::EEXIST
+ end
+ end
+ child = make_child_entry(name)
+ end
+ @children = nil
+ child
+ end
+
+ def json_class
+ nil
+ end
+
+ # Used to print out a human-readable file system description
+ def fs_description
+ repo_paths = root_paths || [ File.dirname(child_paths['cookbooks'][0]) ]
+ result = "repository at #{repo_paths.join(', ')}\n"
+ if versioned_cookbooks
+ result << " Multiple versions per cookbook\n"
+ else
+ result << " One version per cookbook\n"
+ end
+ child_paths.each_pair do |name, paths|
+ if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
+ result << " #{name} at #{paths.join(', ')}\n"
+ end
+ end
+ result
+ end
+
+ private
+
+ #
+ # A FileSystemEntry representing the root path where invites.json,
+ # members.json and org.json may be found.
+ #
+ def root_dir
+ existing_paths = root_paths.select { |path| File.exists?(path) }
+ if existing_paths.size > 0
+ MultiplexedDir.new(existing_paths.map do |path|
+ dir = ChefRepositoryFileSystemEntry.new(name, parent, path)
+ dir.write_pretty_json = !!write_pretty_json
+ dir
+ end)
+ end
+ end
+
+ #
+ # Create a child entry of the appropriate type:
+ # cookbooks, data_bags, acls, etc. All will be multiplexed (i.e. if
+ # you have multiple paths for cookbooks, the multiplexed dir will grab
+ # cookbooks from all of them when you list or grab them).
+ #
+ def make_child_entry(name)
+ if CHILDREN.include?(name)
+ return nil if !root_dir
+ return root_dir.child(name)
+ end
+
+ paths = (child_paths[name] || []).select { |path| File.exists?(path) }
+ if paths.size == 0
+ return NonexistentFSObject.new(name, self)
+ end
+ case name
+ when 'cookbooks'
+ dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
+ when 'data_bags'
+ dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
+ when 'acls'
+ dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
+ else
+ data_handler = case name
+ when 'clients'
+ Chef::ChefFS::DataHandler::ClientDataHandler.new
+ when 'environments'
+ Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
+ when 'nodes'
+ Chef::ChefFS::DataHandler::NodeDataHandler.new
+ when 'policies'
+ Chef::ChefFS::DataHandler::PolicyDataHandler.new
+ when 'policy_groups'
+ Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new
+ when 'roles'
+ Chef::ChefFS::DataHandler::RoleDataHandler.new
+ when 'users'
+ Chef::ChefFS::DataHandler::UserDataHandler.new
+ when 'groups'
+ Chef::ChefFS::DataHandler::GroupDataHandler.new
+ when 'containers'
+ Chef::ChefFS::DataHandler::ContainerDataHandler.new
+ else
+ raise "Unknown top level path #{name}"
+ end
+ dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
+ end
+ MultiplexedDir.new(dirs)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/file_system_entry.rb b/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
new file mode 100644
index 0000000000..f8c8d490d2
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
@@ -0,0 +1,117 @@
+#
+# 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/chef_server/rest_list_dir'
+require 'chef/chef_fs/file_system/already_exists_error'
+require 'chef/chef_fs/file_system/must_delete_recursively_error'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/path_utils'
+require 'fileutils'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ 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
+ file_path
+ end
+
+ def children
+ # Except cookbooks and data bag dirs, all things must be json files
+ begin
+ Dir.entries(file_path).sort.
+ map { |child_name| make_child_entry(child_name) }.
+ select { |child| child && can_have_child?(child.name, child.dir?) }
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ end
+
+ def create_child(child_name, file_contents=nil)
+ child = make_child_entry(child_name)
+ if child.exists?
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
+ end
+ if file_contents
+ child.write(file_contents)
+ else
+ begin
+ Dir.mkdir(child.file_path)
+ rescue Errno::EEXIST
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
+ end
+ end
+ child
+ end
+
+ def dir?
+ File.directory?(file_path)
+ end
+
+ def delete(recurse)
+ begin
+ if dir?
+ if !recurse
+ raise MustDeleteRecursivelyError.new(self, $!)
+ end
+ FileUtils.rm_r(file_path)
+ else
+ File.delete(file_path)
+ end
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ end
+
+ def exists?
+ File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
+ end
+
+ def read
+ begin
+ File.open(file_path, "rb") {|f| f.read}
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ end
+
+ def write(content)
+ File.open(file_path, 'wb') do |file|
+ file.write(content)
+ end
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ FileSystemEntry.new(child_name, self)
+ end
+ end
+ 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/repository/file_system_root_dir.rb
index afbf7b1901..e4c9a455e3 100644
--- a/lib/chef/chef_fs/file_system/file_system_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/file_system_root_dir.rb
@@ -16,14 +16,16 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_entry'
+require 'chef/chef_fs/file_system/repository/file_system_entry'
class Chef
module ChefFS
module FileSystem
- class FileSystemRootDir < FileSystemEntry
- def initialize(file_path)
- super("", nil, file_path)
+ module Repository
+ class FileSystemRootDir < FileSystemEntry
+ def initialize(file_path)
+ super("", nil, file_path)
+ 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
deleted file mode 100644
index 1190069035..0000000000
--- a/lib/chef/chef_fs/file_system/rest_list_dir.rb
+++ /dev/null
@@ -1,177 +0,0 @@
-#
-# 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, data_handler = nil)
- super(name, parent)
- @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
- @data_handler = data_handler
- end
-
- attr_reader :api_path
- attr_reader :data_handler
-
- def can_have_child?(name, is_dir)
- name =~ /\.json$/ && !is_dir
- end
-
- #
- # When talking to a modern (12.0+) Chef server
- # knife list /
- # -> /nodes
- # -> /policies
- # -> /policy_groups
- # -> /roles
- #
- # 12.0 or 12.1 will fail when you do this:
- # knife list / --recursive
- # Because it thinks /policies exists, and when it tries to list its children
- # it gets a 404 (indicating it actually doesn't exist).
- #
- # With this change, knife list / --recursive will list /policies as a real, empty directory.
- #
- # Alternately, we could have done some sort of detection when we listed the top level
- # and determined which endpoints the server would support, and returned only those.
- # So you wouldn't see /policies in that case at all.
- # The issue with that is there's no efficient way to do it because we can't find out
- # the server version directly, and can't ask the server for a list of the endpoints it supports.
- #
-
-
- #
- # Does GET /<api_path>, assumes the result is of the format:
- #
- # {
- # "foo": "<api_path>/foo",
- # "bar": "<api_path>/bar",
- # }
- #
- # Children are foo.json and bar.json in this case.
- #
- def children
- begin
- # Grab the names of the children, append json, and make child entries
- @children ||= root.get_json(api_path).keys.sort.map do |key|
- make_child_entry("#{key}.json", true)
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
- rescue Net::HTTPServerException => e
- # 404 = NotFoundError
- if $!.response.code == "404"
-
- if parent.is_a?(ChefServerRootDir)
- # GET /organizations/ORG/<container> returned 404, but that just might be because
- # we are talking to an older version of the server that doesn't support policies.
- # Do GET /orgqanizations/ORG to find out if the org exists at all.
- # TODO use server API version instead of a second network request.
- begin
- root.get_json(parent.api_path)
- # Return empty list if the organization exists but /policies didn't work
- []
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
- end
- else
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
-
- # Anything else is unexpected (OperationFailedError)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
- end
- end
- end
-
- #
- # Does POST <api_path> with file_contents
- #
- def create_child(name, file_contents)
- # Parse the contents to ensure they are valid JSON
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
- end
-
- # Create the child entry that will be returned
- result = make_child_entry(name, true)
-
- # Normalize the file_contents before post (add defaults, etc.)
- if data_handler
- object = data_handler.normalize_for_post(object, result)
- data_handler.verify_integrity(object, result) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
- end
- end
-
- # POST /api_path with the normalized file_contents
- begin
- rest.post(api_path, object)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
- rescue Net::HTTPServerException => e
- # 404 = NotFoundError
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- # 409 = AlreadyExistsError
- elsif $!.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
- # Anything else is unexpected (OperationFailedError)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
- end
- end
-
- # Clear the cache of children so that if someone asks for children
- # again, we will get it again
- @children = nil
-
- result
- end
-
- def org
- parent.org
- end
-
- def environment
- parent.environment
- end
-
- def rest
- parent.rest
- end
-
- def make_child_entry(name, exists = nil)
- @children.select { |child| child.name == name }.first if @children
- 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
deleted file mode 100644
index 49c6cc9e67..0000000000
--- a/lib/chef/chef_fs/file_system/rest_list_entry.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-#
-# 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/chef_fs/file_system/operation_failed_error'
-require 'chef/role'
-require 'chef/node'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- class RestListEntry < BaseFSObject
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- parent.data_handler
- end
-
- def api_child_name
- if name.length < 5 || name[-5,5] != ".json"
- raise "Invalid name #{path}: must end in .json"
- end
- name[0,name.length-5]
- end
-
- def api_path
- "#{parent.api_path}/#{api_child_name}"
- end
-
- def org
- parent.org
- 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(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
- end
- end
- end
-
- def read
- Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
- end
-
- def _read_json
- begin
- # Minimize the value (get rid of defaults) so the results don't look terrible
- root.get_json(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
- rescue Net::HTTPServerException => e
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
- end
- end
- end
-
- def chef_object
- # REST will inflate the Chef object using json_class
- data_handler.json_class.json_create(read)
- end
-
- def minimize_value(value)
- data_handler.minimize(data_handler.normalize(value, self), self)
- end
-
- def compare_to(other)
- # TODO this pair of reads can be parallelized
-
- # Grab the other value
- begin
- other_value_json = other.read
- rescue Chef::ChefFS::FileSystem::NotFoundError
- return [ nil, nil, :none ]
- end
-
- # Grab this value
- begin
- value = _read_json
- rescue Chef::ChefFS::FileSystem::NotFoundError
- return [ false, :none, other_value_json ]
- end
-
- # Minimize (and normalize) both values for easy and beautiful diffs
- value = minimize_value(value)
- value_json = Chef::JSONCompat.to_json_pretty(value)
- begin
- other_value = Chef::JSONCompat.parse(other_value_json)
- rescue Chef::Exceptions::JSON::ParseError => e
- Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
- return [ nil, value_json, other_value_json ]
- end
- other_value = minimize_value(other_value)
- other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
-
- [ value == other_value, value_json, other_value_json ]
- end
-
- def rest
- parent.rest
- end
-
- def write(file_contents)
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Parse error reading JSON: #{e}")
- end
-
- if data_handler
- object = data_handler.normalize_for_put(object, self)
- data_handler.verify_integrity(object, self) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, "#{error}")
- end
- end
-
- begin
- rest.put(api_path, object)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
- end
- end
- end
-
- def api_error_text(response)
- begin
- Chef::JSONCompat.parse(response.body)['error'].join("\n")
- rescue
- response.body
- end
- end
- end
-
- end
- end
-end
diff --git a/spec/support/shared/unit/file_system_support.rb b/spec/support/shared/unit/file_system_support.rb
index 3f9e6df3b9..3aee46fc7f 100644
--- a/spec/support/shared/unit/file_system_support.rb
+++ b/spec/support/shared/unit/file_system_support.rb
@@ -17,16 +17,16 @@
#
require 'chef/chef_fs/file_system'
-require 'chef/chef_fs/file_system/memory_root'
-require 'chef/chef_fs/file_system/memory_dir'
-require 'chef/chef_fs/file_system/memory_file'
+require 'chef/chef_fs/file_system/memory/memory_root'
+require 'chef/chef_fs/file_system/memory/memory_dir'
+require 'chef/chef_fs/file_system/memory/memory_file'
module FileSystemSupport
def memory_fs(pretty_name, value, cannot_be_in_regex = nil)
if !value.is_a?(Hash)
raise "memory_fs() must take a Hash"
end
- dir = Chef::ChefFS::FileSystem::MemoryRoot.new(pretty_name, cannot_be_in_regex)
+ dir = Chef::ChefFS::FileSystem::Memory::MemoryRoot.new(pretty_name, cannot_be_in_regex)
value.each do |key, child|
dir.add_child(memory_fs_value(child, key.to_s, dir))
end
@@ -35,13 +35,13 @@ module FileSystemSupport
def memory_fs_value(value, name = '', parent = nil)
if value.is_a?(Hash)
- dir = Chef::ChefFS::FileSystem::MemoryDir.new(name, parent)
+ dir = Chef::ChefFS::FileSystem::Memory::MemoryDir.new(name, parent)
value.each do |key, child|
dir.add_child(memory_fs_value(child, key.to_s, dir))
end
dir
else
- Chef::ChefFS::FileSystem::MemoryFile.new(name, parent, value || "#{name}\n")
+ Chef::ChefFS::FileSystem::Memory::MemoryFile.new(name, parent, value || "#{name}\n")
end
end
@@ -54,7 +54,7 @@ module FileSystemSupport
end
def no_blocking_calls_allowed
- [ Chef::ChefFS::FileSystem::MemoryFile, Chef::ChefFS::FileSystem::MemoryDir ].each do |c|
+ [ Chef::ChefFS::FileSystem::Memory::MemoryFile, Chef::ChefFS::FileSystem::Memory::MemoryDir ].each do |c|
[ :children, :exists?, :read ].each do |m|
allow_any_instance_of(c).to receive(m).and_raise("#{m} should not be called")
end
@@ -67,4 +67,3 @@ module FileSystemSupport
expect(result_paths).to match_array(expected_paths)
end
end
-
diff --git a/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb b/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
index 94a636fc0a..b98ec12161 100644
--- a/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
+++ b/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb
@@ -17,15 +17,15 @@
#
require 'spec_helper'
-require 'chef/chef_fs/file_system/cookbook_subdir'
+require 'chef/chef_fs/file_system/chef_server/cookbook_subdir'
-describe Chef::ChefFS::FileSystem::CookbookSubdir do
+describe Chef::ChefFS::FileSystem::ChefServer::CookbookSubdir do
let(:root) do
Chef::ChefFS::FileSystem::BaseFSDir.new('', nil)
end
let(:cookbook_subdir) do
- Chef::ChefFS::FileSystem::CookbookSubdir.new('test', root, false, true)
+ Chef::ChefFS::FileSystem::ChefServer::CookbookSubdir.new('test', root, false, true)
end
it 'can get child' do
diff --git a/tasks/external_tests.rb b/tasks/external_tests.rb
index 69c0712a12..6742d3f88d 100644
--- a/tasks/external_tests.rb
+++ b/tasks/external_tests.rb
@@ -26,8 +26,8 @@ def bundle_exec_with_chef(test_gem, commands)
gemfile.close
Dir.chdir(gem_path) do
Bundler.with_clean_env do
- unless system({ 'RUBYOPT' => nil, 'GEMFILE_MOD' => nil }, "bundle install --gemfile #{gemfile_path}")
- raise "Error running bundle install --gemfile #{gemfile_path} in #{gem_path}: #{$?.exitstatus}\nGemfile:\n#{IO.read(gemfile_path)}"
+ unless system({ 'BUNDLE_GEMFILE' => gemfile_path, 'RUBYOPT' => nil, 'GEMFILE_MOD' => nil }, "bundle update")
+ raise "Error running bundle update of #{gemfile_path} in #{gem_path}: #{$?.exitstatus}\nGemfile:\n#{IO.read(gemfile_path)}"
end
Array(commands).each do |command|
unless system({ 'BUNDLE_GEMFILE' => gemfile_path, 'RUBYOPT' => nil, 'GEMFILE_MOD' => nil }, "bundle exec #{command}")