File Manager
Viewing File: config.rb
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Copyright:: Copyright 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/log"
require "chef/chef_fs/path_utils"
class Chef
module ChefFS
#
# Helpers to take Chef::Config and create chef_fs and local_fs (ChefFS
# objects representing the server and local repository, respectively).
#
class Config
# Not all of our object types pluralize by adding an 's', so we map them
# out here:
INFLECTIONS = {
"acls" => "acl",
"client_keys" => "client_key",
"clients" => "client",
"cookbooks" => "cookbook",
"cookbook_artifacts" => "cookbook_artifact",
"containers" => "container",
"data_bags" => "data_bag",
"environments" => "environment",
"groups" => "group",
"nodes" => "node",
"roles" => "role",
"users" => "user",
"policies" => "policy",
"policy_groups" => "policy_group",
}
INFLECTIONS.each { |k, v| k.freeze; v.freeze }
INFLECTIONS.freeze
# ChefFS supports three modes of operation: "static", "everything", and
# "hosted_everything". These names are antiquated since Chef 12 moved
# multi-tenant and RBAC to the open source product. In practice, they
# mean:
#
# * static: just static objects that are included in a traditional
# chef-repo, with no support for anything introduced in Chef 12 or
# later.
# * everything: all of the objects supported by the open source Chef
# Server 11.x
# * hosted_everything: (the name comes from Hosted Chef) supports
# everything in Chef Server 12 and later, including RBAC objects and
# Policyfile objects.
#
# The "static" and "everything" modes are used for backup and
# upgrade/migration of older Chef Servers, so they should be considered
# frozen in time.
CHEF_11_OSS_STATIC_OBJECTS = %w{cookbooks cookbook_artifacts data_bags environments roles}.freeze
CHEF_11_OSS_DYNAMIC_OBJECTS = %w{clients nodes users}.freeze
RBAC_OBJECT_NAMES = %w{acls containers groups }.freeze
CHEF_12_OBJECTS = %w{ cookbook_artifacts policies policy_groups client_keys }.freeze
STATIC_MODE_OBJECT_NAMES = CHEF_11_OSS_STATIC_OBJECTS
EVERYTHING_MODE_OBJECT_NAMES = (CHEF_11_OSS_STATIC_OBJECTS + CHEF_11_OSS_DYNAMIC_OBJECTS).freeze
HOSTED_EVERYTHING_MODE_OBJECT_NAMES = (EVERYTHING_MODE_OBJECT_NAMES + RBAC_OBJECT_NAMES + CHEF_12_OBJECTS).freeze
#
# Create a new Config object which can produce a chef_fs and local_fs.
#
# ==== Arguments
#
# [chef_config]
# A hash that looks suspiciously like +Chef::Config+. These hash keys
# include:
#
# :chef_repo_path::
# The root where all local chef object data is stored. Mirrors
# +Chef::Config.chef_repo_path+
# :cookbook_path, node_path, ...::
# Paths to cookbooks/, nodes/, data_bags/, etc. Mirrors
# +Chef::Config.cookbook_path+, etc. Defaults to
# +<chef_repo_path>/cookbooks+, etc.
# :repo_mode::
# The directory format on disk. 'everything', 'hosted_everything' and
# 'static'. Default: autodetected based on whether the URL has
# "/organizations/NAME."
# :versioned_cookbooks::
# If true, the repository contains cookbooks with versions in their
# name (apache2-1.0.0). If false, the repository just has one version
# of each cookbook and the directory has the cookbook name (apache2).
# Default: +false+
# :chef_server_url::
# The URL to the Chef server, e.g. https://api.opscode.com/organizations/foo.
# Used as the server for the remote chef_fs, and to "guess" repo_mode
# if not specified.
# :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
#
# [cwd]
# The current working directory to base relative Chef paths from.
# Defaults to +Dir.pwd+.
#
# [options]
# A hash of other, not-suspiciously-like-chef-config options:
# :cookbook_version::
# When downloading cookbooks, download this cookbook version instead
# of the latest.
#
# [ui]
# The object to print output to, with "output", "warn" and "error"
# (looks a little like a Chef::Knife::UI object, obtainable from
# Chef::Knife.ui).
#
# ==== Example
#
# require 'chef/chef_fs/config'
# config = Chef::ChefFS::Config.new
# config.chef_fs.child('cookbooks').children.each do |cookbook|
# puts "Cookbook on server: #{cookbook.name}"
# end
# config.local_fs.child('cookbooks').children.each do |cookbook|
# puts "Local cookbook: #{cookbook.name}"
# end
#
def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
@chef_config = chef_config
@cwd = File.expand_path(cwd)
@cookbook_version = options[:cookbook_version]
if @chef_config[:repo_mode] == "everything" && is_hosted? && !ui.nil?
ui.warn %Q{You have repo_mode set to 'everything', but your chef_server_url
looks like it might be a hosted setup. If this is the case please use
hosted_everything or allow repo_mode to default}
end
# Default to getting *everything* from the server.
if !@chef_config[:repo_mode]
if is_hosted?
@chef_config[:repo_mode] = "hosted_everything"
else
@chef_config[:repo_mode] = "everything"
end
end
end
attr_reader :chef_config
attr_reader :cwd
attr_reader :cookbook_version
def is_hosted?
@chef_config[:chef_server_url] =~ /\/+organizations\/.+/
end
def chef_fs
@chef_fs ||= create_chef_fs
end
def create_chef_fs
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
@local_fs ||= create_local_fs
end
def create_local_fs
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.
#
# 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 different, manually specified paths to object locations
# (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 the first
# one to match it. Otherwise we expect the path provided to be to the chef
# repo path itself. Paths that are not available on the server are not supported.
#
# 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)
target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
# Check all object paths (cookbooks_dir, data_bags_dir, etc.)
# These are either manually specified by the user or autogenerated relative
# to chef_repo_path.
object_paths.each_pair do |name, paths|
paths.each do |path|
object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
end
end
end
# Check chef_repo_path
Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
# We're using realest_path here but we really don't need to - we can just expand the
# path and use realpath because a repo_path if provided *must* exist.
realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
return "/"
end
end
nil
end
# The current directory, relative to server root. This is a case-sensitive server path.
# It only exists if the current directory is a child of one of the recognized object_paths below.
def base_path
@base_path ||= server_path(@cwd)
end
# Print the given server path, relative to the current directory
def format_path(entry)
server_path = entry.respond_to?(:display_path) ? entry.display_path : entry.path
if base_path && server_path[0, base_path.length] == base_path
if server_path == base_path
return "."
elsif server_path[base_path.length, 1] == "/"
return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
elsif base_path == "/" && server_path[0, 1] == "/"
return server_path[1, server_path.length - 1]
end
end
server_path
end
private
def object_paths
@object_paths ||= begin
result = {}
case @chef_config[:repo_mode]
when "static"
object_names = STATIC_MODE_OBJECT_NAMES
when "hosted_everything"
object_names = HOSTED_EVERYTHING_MODE_OBJECT_NAMES
else
object_names = EVERYTHING_MODE_OBJECT_NAMES
end
object_names.each do |object_name|
# cookbooks -> cookbook_path
singular_name = INFLECTIONS[object_name]
raise "Unknown object name #{object_name}" unless singular_name
variable_name = "#{singular_name}_path"
paths = Array(@chef_config[variable_name]).flatten
result[object_name] = paths.map { |path| File.expand_path(path) }
end
result
end
end
end
end
end