summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorjkeiser <jkeiser@opscode.com>2012-12-17 13:24:23 -0800
committerJohn Keiser <jkeiser@opscode.com>2013-06-07 13:12:12 -0700
commit02daf8327204f5229e432a8e2cf7ab94f1c434a9 (patch)
tree0440af7a0f73eac4d16b0bc1358236144bc95b2b /lib
parent777ad0c423a6f15804d328e407c014e627de5419 (diff)
downloadchef-02daf8327204f5229e432a8e2cf7ab94f1c434a9.tar.gz
Support chef_repo_path, roles_path, multiple cookbook_paths
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb2
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb23
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb47
-rw-r--r--lib/chef/chef_fs/file_system/multiplexed_dir.rb48
-rw-r--r--lib/chef/chef_fs/knife.rb128
-rw-r--r--lib/chef/chef_fs/path_utils.rb2
6 files changed, 220 insertions, 30 deletions
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
index 855892fc89..9425b3546e 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -114,7 +114,7 @@ class Chef
# Important directory attributes: name, parent, path, root
# Overridable attributes: dir?, child(name), path_for_printing
- # Abstract: read, write, delete, children
+ # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
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
index 87d904e830..277ebef168 100644
--- 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
@@ -29,17 +29,16 @@ class Chef
module ChefFS
module FileSystem
# ChefRepositoryFileSystemEntry works just like FileSystemEntry,
- # except it pretends files in /cookbooks/chefignore don't exist
- # and it can inflate Chef objects
+ # except can inflate Chef objects
class ChefRepositoryFileSystemEntry < FileSystemEntry
def initialize(name, parent, file_path = nil)
super(name, parent, file_path)
# Load /cookbooks/chefignore
- if name == "cookbooks" && path == "/cookbooks" # We check name first because it's a faster fail than path
+ if path == '/cookbooks'
@chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
# If we are a cookbook or a cookbook subdirectory, empty directories
# underneath us are ignored (since they cannot be uploaded)
- elsif parent && parent.name === "cookbooks" && parent.path == "/cookbooks"
+ elsif parent.path == '/cookbooks'
@ignore_empty_directories = true
elsif parent && parent.ignore_empty_directories?
@ignore_empty_directories = true
@@ -54,7 +53,7 @@ class Chef
def chef_object
begin
- if parent.path == "/cookbooks"
+ if parent.path == '/cookbooks'
loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
loader.load_cookbooks
return loader.cookbook_version
@@ -69,14 +68,24 @@ class Chef
end
def children
- @children ||= Dir.entries(file_path).select { |entry| entry != '.' && entry != '..' && !ignored?(entry) }.
- map { |entry| ChefRepositoryFileSystemEntry.new(entry, self) }
+ @children ||=
+ Dir.entries(file_path).
+ select { |entry| entry != '.' && entry != '..' && !ignored?(entry) }.
+ map { |entry| ChefRepositoryFileSystemEntry.new(entry, self) }
end
attr_reader :chefignore
private
+ def is_cookbooks_dir?
+ # We check name first because it's a faster fail than path
+ path == "/cookbooks"
+ end
+
+ def is_under_cookbooks?
+ end
+
def ignored?(child_name)
# empty directories inside a cookbook are ignored
if ignore_empty_directories?
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
index fdad68003c..4ca674c627 100644
--- 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
@@ -16,14 +16,55 @@
# 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/multiplexed_dir'
class Chef
module ChefFS
module FileSystem
- class ChefRepositoryFileSystemRootDir < ChefRepositoryFileSystemEntry
- def initialize(file_path)
- super("", nil, file_path)
+ class ChefRepositoryFileSystemRootDir < BaseFSDir
+ def initialize(child_paths)
+ super("", nil)
+ @child_paths = child_paths
+ end
+
+ attr_reader :child_paths
+
+ def children
+ @children ||= child_paths.keys.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+ end
+
+ def can_have_child?(name, is_dir)
+ child_paths.has_key?(name) && is_dir
+ end
+
+ def create_child(name, file_contents = nil)
+ child_paths[name].each do |path|
+ Dir.mkdir(path)
+ end
+ make_child_entry(name)
+ end
+
+ def ignore_empty_directories?
+ false
+ end
+
+ def chefignore
+ nil
+ end
+
+ private
+
+ def make_child_entry(name)
+ paths = child_paths[name].select do |path|
+ File.exists?(path)
+ end
+ if paths.size == 0
+ return nil
+ end
+ dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path) }
+ MultiplexedDir.new(dirs)
end
end
end
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
new file mode 100644
index 0000000000..4e138ddf2a
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -0,0 +1,48 @@
+require 'chef/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
+
+class Chef
+ module ChefFS
+ module FileSystem
+ class MultiplexedDir < BaseFSDir
+ def initialize(*multiplexed_dirs)
+ @multiplexed_dirs = multiplexed_dirs.flatten
+ super(@multiplexed_dirs[0].name, @multiplexed_dirs[0].parent)
+ end
+
+ attr_reader :multiplexed_dirs
+
+ def write_dir
+ multiplexed_dirs[0]
+ end
+
+ def children
+ @children ||= begin
+ result = []
+ seen = {}
+ # If multiple things have the same name, the first one wins.
+ multiplexed_dirs.each do |dir|
+ dir.children.each do |child|
+ if seen[child.name]
+ Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{child} and #{seen[child.name]}")
+ else
+ result << child
+ seen[child.name] = child
+ end
+ end
+ end
+ result
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ write_dir.can_have_child?(name, is_dir)
+ end
+
+ def create_child(name, file_contents = nil)
+ write_dir.create_child(name, file_contents)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 8a116d980e..dc4b67e5ae 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -32,36 +32,124 @@ class Chef
:description => "Specifies the local repository layout. Values: default or full"
end
- def base_path
- @base_path ||= begin
- relative_to_base = Chef::ChefFS::PathUtils::relative_to(File.expand_path(Dir.pwd), chef_repo)
- relative_to_base == '.' ? '/' : "/#{relative_to_base}"
+ def chef_fs
+ @chef_fs ||= Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", Chef::Config, config[:repo_mode])
+ end
+
+ def chef_repo_path
+ @chef_repo_path ||= begin
+ if Chef::Config.chef_repo_path
+ File.expand_path(Chef::Config.chef_repo_path)
+ elsif Chef::Config.cookbook_path
+ File.expand_path('..', Array(Chef::Config.cookbook_path).flatten.first)
+ else
+ nil
+ end
end
end
- def chef_fs
- @chef_fs ||= Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", Chef::Config, config[:repo_mode])
+ # Smooth out some inappropriate (for know) variable defaults in Chef.
+ def config_var(name)
+ case name
+ when :data_bag_path
+ Chef::Config[name] == Chef::Config.platform_specific_path('/var/chef/data_bags') ? nil : Chef::Config[name]
+ when :node_path
+ Chef::Config[name] == '/var/chef/node' ? nil : Chef::Config[name]
+ when :role_path
+ Chef::Config[name] == Chef::Config.platform_specific_path('/var/chef/roles') ? nil : Chef::Config[name]
+ when :chef_repo_path
+ chef_repo_path
+ else
+ Chef::Config[name]
+ end
end
- def chef_repo
- @chef_repo ||= File.expand_path(File.join(Chef::Config.cookbook_path, ".."))
+ def object_paths
+ @object_paths ||= begin
+ result = {}
+ %w(clients cookbooks data_bags environments nodes roles users).each do |object_name|
+ variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
+ paths = config_var(variable_name.to_sym)
+ if !paths
+ if !chef_repo_path
+ # TODO if chef_repo is not specified and repo_mode does not require
+ # clients/users/nodes, don't require them to be specified.
+ Chef::Log.error("Must specify either chef_repo_path or #{variable_name} in Chef config file")
+ exit(1)
+ end
+ paths = File.join(chef_repo_path, object_name)
+ end
+ paths = Array(paths).flatten.map { |path| File.expand_path(path) }
+ result[object_name] = paths
+ end
+ result
+ end
end
- def format_path(path)
- if path[0,base_path.length] == base_path
- if path == base_path
+ # Returns the given real path's location relative to the server root.
+ #
+ # If chef_repo is /home/jkeiser/chef_repo,
+ # and pwd is /home/jkeiser/chef_repo/cookbooks,
+ # server_path('blah') == '/cookbooks/blah'
+ # server_path('../roles/blah.json') == '/roles/blah'
+ # server_path('../../readme.txt') == nil
+ # server_path('*/*ab*') == '/cookbooks/*/*ab*'
+ # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
+ # server_path('/home/*/chef_repo/cookbooks/blah') == nil
+ #
+ # If there are multiple paths (cookbooks, roles, data bags, etc. can all
+ # have separate paths), and cwd+the path reaches into one of them, we will
+ # return a path relative to that. Otherwise we will return a path to
+ # chef_repo.
+ #
+ # Globs are allowed as well, but globs outside server paths are NOT
+ # (presently) supported. See above examples. TODO support that.
+ #
+ # If the path does not reach into ANY specified directory, nil is returned.
+ def server_path(file_path)
+ pwd = File.expand_path(Dir.pwd)
+ absolute_path = File.expand_path(file_path, pwd)
+
+ # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+ object_paths.each_pair do |name, paths|
+ paths.each do |path|
+ if absolute_path[0,path.length] == path
+ relative_path = Chef::ChefFS::PathUtils::relative_to(path, absolute_path)
+ return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+ end
+ end
+ end
+
+ # Check chef_repo_path
+ if chef_repo_path[0,absolute_path.length] == absolute_path
+ relative_path = Chef::ChefFS::PathUtils::relative_to(chef_repo_path, absolute_path)
+ return relative_path == '.' ? '/' : "/#{relative_path}"
+ end
+
+ nil
+ end
+
+ # The current directory, relative to server root
+ def base_path
+ @base_path ||= server_path(File.expand_path(Dir.pwd))
+ end
+
+ # Print the given server path, relative to the current directory
+ def format_path(server_path)
+ if server_path[0,base_path.length] == base_path
+ if server_path == base_path
return "."
- elsif path[base_path.length] == "/"
- return path[base_path.length + 1, path.length - base_path.length - 1]
- elsif base_path == "/" && path[0] == "/"
- return path[1, path.length - 1]
+ elsif server_path[base_path.length] == "/"
+ return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
+ elsif base_path == "/" && server_path[0] == "/"
+ return server_path[1, server_path.length - 1]
end
end
- path
+ server_path
end
def local_fs
- @local_fs ||= Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(chef_repo)
+ @local_fs ||= Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
end
def pattern_args
@@ -69,7 +157,11 @@ class Chef
end
def pattern_args_from(args)
- args.map { |arg| Chef::ChefFS::FilePattern::relative_to(base_path, arg) }.to_a
+ # TODO support absolute file paths and not just patterns? Too much?
+ # Could be super useful in a world with multiple repo paths
+ args.map do |arg|
+ Chef::ChefFS::FilePattern::relative_to(base_path, arg)
+ end
end
end
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 67c62a7545..2f2a9537cf 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -24,7 +24,7 @@ class Chef
# If you are in 'source', this is what you would have to type to reach 'dest'
# relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
- # relative_to('/a/b', '/a/b') == ''
+ # relative_to('/a/b', '/a/b') == '.'
def self.relative_to(dest, source)
# Skip past the common parts
source_parts = Chef::ChefFS::PathUtils.split(source)