summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorsawanoboly <sawanoboriyu@higanworks.com>2014-09-12 13:01:48 +0900
committersawanoboly <sawanoboriyu@higanworks.com>2014-09-12 13:01:48 +0900
commit1ab9ae58068da346bf2262cb0b8acc14a72d9f2b (patch)
tree4bb2f07e095d0653a0fe67374c843e130c704d1b /lib
parent5c94009b6053e7ae71d528485f3560e2bf8a7851 (diff)
parent34d956c6b96087e6ca4bfbc9080037ded481709d (diff)
downloadchef-1ab9ae58068da346bf2262cb0b8acc14a72d9f2b.tar.gz
Merge remote-tracking branch 'upstream/master' into prevew_archive_before_site_share
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/application.rb33
-rw-r--r--lib/chef/application/apply.rb4
-rw-r--r--lib/chef/application/client.rb14
-rw-r--r--lib/chef/application/solo.rb34
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb66
-rw-r--r--lib/chef/chef_fs/config.rb82
-rw-r--r--lib/chef/chef_fs/data_handler/acl_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/client_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/container_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/cookbook_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/data_handler_base.rb78
-rw-r--r--lib/chef/chef_fs/data_handler/environment_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/group_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/node_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/organization_data_handler.rb30
-rw-r--r--lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb17
-rw-r--r--lib/chef/chef_fs/data_handler/organization_members_data_handler.rb17
-rw-r--r--lib/chef/chef_fs/data_handler/role_data_handler.rb2
-rw-r--r--lib/chef/chef_fs/data_handler/user_data_handler.rb3
-rw-r--r--lib/chef/chef_fs/file_system.rb1
-rw-r--r--lib/chef/chef_fs/file_system/acl_entry.rb2
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb6
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb86
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb49
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_file.rb4
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/org_entry.rb34
-rw-r--r--lib/chef/chef_fs/file_system/organization_invites_entry.rb58
-rw-r--r--lib/chef/chef_fs/file_system/organization_members_entry.rb57
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb17
-rw-r--r--lib/chef/chef_fs/knife.rb2
-rw-r--r--lib/chef/client.rb1
-rw-r--r--lib/chef/config.rb140
-rw-r--r--lib/chef/config_fetcher.rb27
-rw-r--r--lib/chef/cookbook/cookbook_version_loader.rb161
-rw-r--r--lib/chef/cookbook/metadata.rb118
-rw-r--r--lib/chef/cookbook_loader.rb76
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb13
-rw-r--r--lib/chef/cookbook_version.rb12
-rw-r--r--lib/chef/digester.rb14
-rw-r--r--lib/chef/dsl/data_query.rb51
-rw-r--r--lib/chef/dsl/platform_introspection.rb42
-rw-r--r--lib/chef/dsl/recipe.rb14
-rw-r--r--lib/chef/encrypted_data_bag_item.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/decryptor.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/encryptor.rb14
-rw-r--r--lib/chef/exceptions.rb5
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb2
-rw-r--r--lib/chef/http/basic_client.rb14
-rw-r--r--lib/chef/http/json_output.rb11
-rw-r--r--lib/chef/json_compat.rb2
-rw-r--r--lib/chef/knife.rb157
-rw-r--r--lib/chef/knife/bootstrap.rb122
-rw-r--r--lib/chef/knife/bootstrap/archlinux-gems.erb2
-rw-r--r--lib/chef/knife/bootstrap/centos5-gems.erb62
-rw-r--r--lib/chef/knife/bootstrap/chef-full.erb1
-rw-r--r--lib/chef/knife/bootstrap/fedora13-gems.erb44
-rw-r--r--lib/chef/knife/bootstrap/ubuntu10.04-apt.erb53
-rw-r--r--lib/chef/knife/bootstrap/ubuntu10.04-gems.erb48
-rw-r--r--lib/chef/knife/bootstrap/ubuntu12.04-gems.erb46
-rw-r--r--lib/chef/knife/cookbook_site_download.rb2
-rw-r--r--lib/chef/knife/cookbook_site_list.rb2
-rw-r--r--lib/chef/knife/cookbook_site_search.rb2
-rw-r--r--lib/chef/knife/cookbook_site_share.rb2
-rw-r--r--lib/chef/knife/cookbook_site_show.rb6
-rw-r--r--lib/chef/knife/cookbook_site_unshare.rb2
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb79
-rw-r--r--lib/chef/knife/core/node_editor.rb5
-rw-r--r--lib/chef/knife/core/ui.rb4
-rw-r--r--lib/chef/knife/search.rb68
-rw-r--r--lib/chef/knife/serve.rb2
-rw-r--r--lib/chef/local_mode.rb14
-rw-r--r--lib/chef/mixin/deep_merge.rb9
-rw-r--r--lib/chef/mixin/shell_out.rb50
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb1
-rw-r--r--lib/chef/null_logger.rb72
-rw-r--r--lib/chef/platform.rb3
-rw-r--r--lib/chef/platform/provider_mapping.rb2
-rw-r--r--lib/chef/provider.rb2
-rw-r--r--lib/chef/provider/cron.rb11
-rw-r--r--lib/chef/provider/deploy/revision.rb2
-rw-r--r--lib/chef/provider/git.rb1
-rw-r--r--lib/chef/provider/group/pw.rb9
-rw-r--r--lib/chef/provider/ifconfig.rb31
-rw-r--r--lib/chef/provider/mount/mount.rb2
-rw-r--r--lib/chef/provider/mount/solaris.rb166
-rw-r--r--lib/chef/provider/package/aix.rb16
-rw-r--r--lib/chef/provider/package/ips.rb60
-rw-r--r--lib/chef/provider/package/macports.rb16
-rw-r--r--lib/chef/provider/package/pacman.rb8
-rw-r--r--lib/chef/provider/package/portage.rb8
-rw-r--r--lib/chef/provider/package/rpm.rb16
-rw-r--r--lib/chef/provider/package/solaris.rb16
-rw-r--r--lib/chef/provider/package/yum.rb2
-rw-r--r--lib/chef/provider/remote_file/cache_control_data.rb2
-rw-r--r--lib/chef/provider/service/debian.rb20
-rw-r--r--lib/chef/provider/service/freebsd.rb162
-rw-r--r--lib/chef/provider/service/gentoo.rb4
-rw-r--r--lib/chef/provider/service/init.rb10
-rw-r--r--lib/chef/provider/service/insserv.rb6
-rw-r--r--lib/chef/provider/service/macosx.rb4
-rw-r--r--lib/chef/provider/service/simple.rb10
-rw-r--r--lib/chef/provider/service/solaris.rb2
-rw-r--r--lib/chef/provider/service/systemd.rb18
-rw-r--r--lib/chef/provider/service/upstart.rb12
-rw-r--r--lib/chef/provider/service/windows.rb108
-rw-r--r--lib/chef/provider/subversion.rb12
-rw-r--r--lib/chef/provider/user/dscl.rb704
-rw-r--r--lib/chef/provider/user/windows.rb12
-rw-r--r--lib/chef/provider/whyrun_safe_ruby_block.rb2
-rw-r--r--lib/chef/recipe.rb1
-rw-r--r--lib/chef/resource.rb11
-rw-r--r--lib/chef/resource/freebsd_package.rb12
-rw-r--r--lib/chef/resource/mount.rb9
-rw-r--r--lib/chef/resource/scm.rb10
-rw-r--r--lib/chef/resource/user.rb18
-rw-r--r--lib/chef/resource/windows_service.rb53
-rw-r--r--lib/chef/resource_collection.rb25
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--lib/chef/search/query.rb136
-rw-r--r--lib/chef/util/path_helper.rb60
-rw-r--r--lib/chef/util/windows/net_user.rb5
-rw-r--r--lib/chef/version.rb2
-rw-r--r--lib/chef/win32/api/net.rb1
-rw-r--r--lib/chef/workstation_config_loader.rb177
128 files changed, 2971 insertions, 1309 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 5b404a3a50..0430d4acfa 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -46,6 +46,7 @@ class Chef::Application
configure_chef
configure_logging
configure_proxy_environment_variables
+ configure_encoding
end
# Get this party started
@@ -73,7 +74,6 @@ class Chef::Application
end
end
-
# Parse configuration (options and config file)
def configure_chef
parse_options
@@ -82,10 +82,11 @@ class Chef::Application
# Parse the config file
def load_config_file
- config_fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
+ config_fetcher = Chef::ConfigFetcher.new(config[:config_file])
if config[:config_file].nil?
Chef::Log.warn("No config file found or specified on command line, using command line options.")
elsif config_fetcher.config_missing?
+ pp config_missing: true
Chef::Log.warn("*****************************************")
Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
Chef::Log.warn("*****************************************")
@@ -175,6 +176,11 @@ class Chef::Application
configure_no_proxy
end
+ # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't)
+ def configure_encoding
+ Encoding.default_external = Chef::Config[:ruby_encoding]
+ end
+
# Called prior to starting the application, by the run method
def setup_application
raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application"
@@ -219,30 +225,39 @@ class Chef::Application
# Set ENV['http_proxy']
def configure_http_proxy
if http_proxy = Chef::Config[:http_proxy]
- env['http_proxy'] = configure_proxy("http", http_proxy,
- Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass])
+ http_proxy_string = configure_proxy("http", http_proxy,
+ Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass])
+ env['http_proxy'] = http_proxy_string unless env['http_proxy']
+ env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY']
end
end
# Set ENV['https_proxy']
def configure_https_proxy
if https_proxy = Chef::Config[:https_proxy]
- env['https_proxy'] = configure_proxy("https", https_proxy,
- Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass])
+ https_proxy_string = configure_proxy("https", https_proxy,
+ Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass])
+ env['https_proxy'] = https_proxy_string unless env['https_proxy']
+ env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY']
end
end
# Set ENV['ftp_proxy']
def configure_ftp_proxy
if ftp_proxy = Chef::Config[:ftp_proxy]
- env['ftp_proxy'] = configure_proxy("ftp", ftp_proxy,
+ ftp_proxy_string = configure_proxy("ftp", ftp_proxy,
Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass])
+ env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy']
+ env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY']
end
end
# Set ENV['no_proxy']
def configure_no_proxy
- env['no_proxy'] = Chef::Config[:no_proxy] if Chef::Config[:no_proxy]
+ if Chef::Config[:no_proxy]
+ env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy']
+ env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY']
+ end
end
# Builds a proxy uri. Examples:
@@ -256,7 +271,7 @@ class Chef::Application
# pass = password
def configure_proxy(scheme, path, user, pass)
begin
- path = "#{scheme}://#{path}" unless path.start_with?(scheme)
+ path = "#{scheme}://#{path}" unless path.include?('://')
# URI.split returns the following parts:
# [scheme, userinfo, host, port, registry, path, opaque, query, fragment]
parts = URI.split(URI.encode(path))
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index ab35b35389..ea9154c6f2 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -134,6 +134,10 @@ class Chef::Application::Apply < Chef::Application
@recipe_text = STDIN.read
temp_recipe_file
else
+ if !ARGV[0]
+ puts opt_parser
+ Chef::Application.exit! "No recipe file provided", 1
+ end
@recipe_filename = ARGV[0]
@recipe_text,@recipe_fh = read_recipe_file @recipe_filename
end
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index c581bb0da0..6c06ad656d 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -24,6 +24,7 @@ require 'chef/daemon'
require 'chef/log'
require 'chef/config_fetcher'
require 'chef/handler/error_report'
+require 'chef/workstation_config_loader'
class Chef::Application::Client < Chef::Application
@@ -219,9 +220,10 @@ class Chef::Application::Client < Chef::Application
:long => "--chef-zero-port PORT",
:description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
- option :config_file_jail,
- :long => "--config-file-jail PATH",
- :description => "Directory under which config files are allowed to be loaded (no client.rb or knife.rb outside this path will be loaded)."
+ option :disable_config,
+ :long => "--disable-config",
+ :description => "Refuse to load a config file and use defaults. This is for development and not a stable API",
+ :boolean => true
option :run_lock_timeout,
:long => "--run-lock-timeout SECONDS",
@@ -273,11 +275,9 @@ class Chef::Application::Client < Chef::Application
end
def load_config_file
- Chef::Config.config_file_jail = config[:config_file_jail] if config[:config_file_jail]
- if !config.has_key?(:config_file)
+ if !config.has_key?(:config_file) && !config[:disable_config]
if config[:local_mode]
- require 'chef/knife'
- config[:config_file] = Chef::Knife.locate_config_file
+ config[:config_file] = Chef::WorkstationConfigLoader.new(nil, Chef::Log).config_location
else
config[:config_file] = Chef::Config.platform_specific_path("/etc/chef/client.rb")
end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index 5e54535014..f0e578d5ef 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -185,24 +185,22 @@ class Chef::Application::Solo < Chef::Application
Chef::Config[:interval] ||= 1800
end
- if Chef::Config[:json_attribs]
- config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
- @chef_client_json = config_fetcher.fetch_json
- end
-
if Chef::Config[:recipe_url]
cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ }
recipes_path = File.expand_path(File.join(cookbooks_path, '..'))
Chef::Log.debug "Creating path #{recipes_path} to extract recipes into"
- FileUtils.mkdir_p recipes_path
- path = File.join(recipes_path, 'recipes.tgz')
- File.open(path, 'wb') do |f|
- open(Chef::Config[:recipe_url]) do |r|
- f.write(r.read)
- end
- end
- Chef::Mixin::Command.run_command(:command => "tar zxvf #{path} -C #{recipes_path}")
+ FileUtils.mkdir_p(recipes_path)
+ tarball_path = File.join(recipes_path, 'recipes.tgz')
+ fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
+ Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}")
+ end
+
+ # json_attribs shuld be fetched after recipe_url tarball is unpacked.
+ # Otherwise it may fail if points to local file from tarball.
+ if Chef::Config[:json_attribs]
+ config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
+ @chef_client_json = config_fetcher.fetch_json
end
end
@@ -246,4 +244,14 @@ class Chef::Application::Solo < Chef::Application
end
end
+ private
+
+ def fetch_recipe_tarball(url, path)
+ Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
+ File.open(path, 'wb') do |f|
+ open(url) do |r|
+ f.write(r.read)
+ end
+ end
+ end
end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index b2435d8201..484ab07390 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -27,7 +27,61 @@ require 'fileutils'
class Chef
module ChefFS
+ #
+ # Translation layer between chef-zero's DataStore (a place where it expects
+ # files to be stored) and ChefFS (the user's repository directory layout).
+ #
+ # chef-zero expects the data store to store files *its* way--for example, it
+ # expects get("nodes/blah") to return the JSON text for the blah node, and
+ # it expects get("cookbooks/blah/1.0.0") to return the JSON definition of
+ # the blah cookbook version 1.0.0.
+ #
+ # The repository is defined the way the *user* wants their layout. These
+ # two things are very similar in layout (for example, nodes are stored under
+ # the nodes/ directory and their filename is the name of the node).
+ #
+ # However, there are a few differences that make this more than just a raw
+ # file store:
+ #
+ # 1. Cookbooks are stored much differently.
+ # - chef-zero places JSON text with the checksums for the cookbook at
+ # /cookbooks/NAME/VERSION, and expects the JSON to contain URLs to the
+ # actual files, which are stored elsewhere.
+ # - The repository contains an actual directory with just the cookbook
+ # files and a metadata.rb containing a version #. There is no JSON to
+ # be found.
+ # - Further, if versioned_cookbooks is false, that directory is named
+ # /cookbooks/NAME and only one version exists. If versioned_cookbooks
+ # is true, the directory is named /cookbooks/NAME-VERSION.
+ # - Therefore, ChefFSDataStore calculates the cookbook JSON by looking at
+ # the files in the cookbook and checksumming them, and reading metadata.rb
+ # for the version and dependency information.
+ # - ChefFSDataStore also modifies the cookbook file URLs so that they point
+ # to /file_store/repo/<filename> (the path to the actual file under the
+ # repository root). For example, /file_store/repo/apache2/metadata.rb or
+ # /file_store/repo/cookbooks/apache2/recipes/default.rb).
+ #
+ # 2. Sandboxes don't exist in the repository.
+ # - ChefFSDataStore lets cookbooks be uploaded into a temporary memory
+ # storage, and when the cookbook is committed, copies the files onto the
+ # disk in the correct place (/cookbooks/apache2/recipes/default.rb).
+ # 3. Data bags:
+ # - The Chef server expects data bags in /data/BAG/ITEM
+ # - The repository stores data bags in /data_bags/BAG/ITEM
+ #
+ # 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json).
+ #
class ChefFSDataStore
+ #
+ # Create a new ChefFSDataStore
+ #
+ # ==== Arguments
+ #
+ # [chef_fs]
+ # A +ChefFS::FileSystem+ object representing the repository root.
+ # Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+
+ # object, created from +ChefFS::Config.local_fs+.
+ #
def initialize(chef_fs)
@chef_fs = chef_fs
@memory_store = ChefZero::DataStore::MemoryStore.new
@@ -103,7 +157,7 @@ class Chef
value.each do |file|
if file.is_a?(Hash) && file.has_key?('checksum')
relative = ['file_store', 'repo', 'cookbooks']
- if Chef::Config.versioned_cookbooks
+ if chef_fs.versioned_cookbooks
relative << "#{path[1]}-#{path[2]}"
else
relative << path[1]
@@ -190,7 +244,7 @@ class Chef
elsif path[0] == 'cookbooks' && path.length == 1
with_entry(path) do |entry|
begin
- if Chef::Config.versioned_cookbooks
+ if chef_fs.versioned_cookbooks
# /cookbooks/name-version -> /cookbooks/name
entry.children.map { |child| split_name_version(child.name)[0] }.uniq
else
@@ -203,7 +257,7 @@ class Chef
end
elsif path[0] == 'cookbooks' && path.length == 2
- if Chef::Config.versioned_cookbooks
+ if chef_fs.versioned_cookbooks
result = with_entry([ 'cookbooks' ]) do |entry|
# list /cookbooks/name = filter /cookbooks/name-version down to name
entry.children.map { |child| split_name_version(child.name) }.
@@ -261,7 +315,7 @@ class Chef
end
def write_cookbook(path, data, *options)
- if Chef::Config.versioned_cookbooks
+ if chef_fs.versioned_cookbooks
cookbook_path = File.join('cookbooks', "#{path[1]}-#{path[2]}")
else
cookbook_path = File.join('cookbooks', path[1])
@@ -318,7 +372,7 @@ class Chef
elsif path[0] == 'cookbooks'
if path.length == 2
raise ChefZero::DataStore::DataNotFoundError.new(path)
- elsif Chef::Config.versioned_cookbooks
+ elsif chef_fs.versioned_cookbooks
if path.length >= 3
# cookbooks/name/version -> cookbooks/name-version
path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1]
@@ -351,7 +405,7 @@ class Chef
end
elsif path[0] == 'cookbooks'
- if Chef::Config.versioned_cookbooks
+ if chef_fs.versioned_cookbooks
# cookbooks/name-version/... -> cookbooks/name/version/...
if path.length >= 2
name, version = split_name_version(path[1])
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index e08b976961..fcad6c919f 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -22,17 +22,87 @@ require 'chef/chef_fs/path_utils'
class Chef
module ChefFS
#
- # Helpers to take Chef::Config and create chef_fs and local_fs from it
+ # Helpers to take Chef::Config and create chef_fs and local_fs (ChefFS
+ # objects representing the server and local repository, respectively).
#
class Config
- def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {})
+ #
+ # 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 = 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 @chef_config[:chef_server_url] =~ /\/+organizations\/.+/
+ if is_hosted?
@chef_config[:repo_mode] = 'hosted_everything'
else
@chef_config[:repo_mode] = 'everything'
@@ -44,6 +114,10 @@ class Chef
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
@@ -59,7 +133,7 @@ class Chef
def create_local_fs
require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
- Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
+ Chef::ChefFS::FileSystem::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/data_handler/acl_data_handler.rb b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
index 5ce4e335f4..8def8a543d 100644
--- a/lib/chef/chef_fs/data_handler/acl_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
@@ -4,9 +4,9 @@ class Chef
module ChefFS
module DataHandler
class AclDataHandler < DataHandlerBase
- def normalize(node, entry)
+ def normalize(acl, entry)
# Normalize the order of the keys for easier reading
- result = normalize_hash(node, {
+ result = normalize_hash(acl, {
'create' => {},
'read' => {},
'update' => {},
diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb
index 4b6b8f5c79..d81f35e861 100644
--- a/lib/chef/chef_fs/data_handler/client_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb
@@ -22,7 +22,7 @@ class Chef
result
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/data_handler/container_data_handler.rb b/lib/chef/chef_fs/data_handler/container_data_handler.rb
index 8b108bcf73..980453cbab 100644
--- a/lib/chef/chef_fs/data_handler/container_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/container_data_handler.rb
@@ -11,7 +11,7 @@ class Chef
})
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'containername'
end
diff --git a/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
index d2e2a3ef6c..56b7e0b765 100644
--- a/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
@@ -23,7 +23,7 @@ class Chef
})
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'cookbook_name' || key == 'version'
end
diff --git a/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
index 240a42756d..1306922081 100644
--- a/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
@@ -34,7 +34,7 @@ class Chef
normalize_for_post(data_bag_item, entry)
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'id'
end
diff --git a/lib/chef/chef_fs/data_handler/data_handler_base.rb b/lib/chef/chef_fs/data_handler/data_handler_base.rb
index a9bbc0bf1b..a3dc92405c 100644
--- a/lib/chef/chef_fs/data_handler/data_handler_base.rb
+++ b/lib/chef/chef_fs/data_handler/data_handler_base.rb
@@ -1,17 +1,32 @@
class Chef
module ChefFS
module DataHandler
+ #
+ # The base class for all *DataHandlers.
+ #
+ # DataHandlers' job is to know the innards of Chef objects and manipulate
+ # JSON for them, adding defaults and formatting them.
+ #
class DataHandlerBase
+ #
+ # Remove all default values from a Chef object's JSON so that the only
+ # thing you see are the values that have been explicitly set.
+ # Achieves this by calling normalize({}, entry) to get the list of
+ # defaults, and subtracting anything that is the same.
+ #
def minimize(object, entry)
default_object = default(entry)
object.each_pair do |key, value|
- if default_object[key] == value && !preserve_key(key)
+ if default_object[key] == value && !preserve_key?(key)
object.delete(key)
end
end
object
end
+ #
+ # Takes a name like blah.json and removes the .json from it.
+ #
def remove_dot_json(name)
if name.length < 5 || name[-5,5] != ".json"
raise "Invalid name #{path}: must end in .json"
@@ -19,14 +34,34 @@ class Chef
name[0,name.length-5]
end
- def preserve_key(key)
+ #
+ # Return true if minimize() should preserve a key even if it is the same
+ # as the default. Often used for ids and names.
+ #
+ def preserve_key?(key)
false
end
+ #
+ # Get the default value for an entry. Calls normalize({}, entry).
+ #
def default(entry)
normalize({}, entry)
end
+ #
+ # Utility function to help subclasses do normalize(). Pass in a hash
+ # and a list of keys with defaults, and normalize will:
+ #
+ # 1. Fill in the defaults
+ # 2. Put the actual values in the order of the defaults
+ # 3. Move any other values to the end
+ #
+ # == Example
+ #
+ # normalize_hash({x: 100, c: 2, a: 1}, { a: 10, b: 20, c: 30})
+ # -> { a: 1, b: 20, c: 2, x: 100}
+ #
def normalize_hash(object, defaults)
# Make a normalized result in the specified order for diffing
result = {}
@@ -39,14 +74,25 @@ class Chef
result
end
+ # Specialized function to normalize an object before POSTing it, since
+ # some object types want slightly different values on POST.
+ # If not overridden, this just calls normalize()
def normalize_for_post(object, entry)
normalize(object, entry)
end
+ # Specialized function to normalize an object before PUTing it, since
+ # some object types want slightly different values on PUT.
+ # If not overridden, this just calls normalize().
def normalize_for_put(object, entry)
normalize(object, entry)
end
+ #
+ # normalize a run list (an array of run list items).
+ # Leaves recipe[name] and role[name] alone, and translates
+ # name to recipe[name]. Then calls uniq on the result.
+ #
def normalize_run_list(run_list)
run_list.map{|item|
case item.to_s
@@ -60,22 +106,46 @@ class Chef
}.uniq
end
+ #
+ # Bring in an instance of this object from Ruby. (Like roles/x.rb)
+ #
def from_ruby(ruby)
chef_class.from_file(ruby).to_hash
end
+ #
+ # Turn a JSON hash into a bona fide Chef object (like Chef::Node).
+ #
def chef_object(object)
chef_class.json_create(object)
end
+ #
+ # Write out the Ruby file for this instance. (Like roles/x.rb)
+ #
def to_ruby(object)
raise NotImplementedError
end
+ #
+ # Get the class for instances of this type. Must be overridden.
+ #
def chef_class
raise NotImplementedError
end
+ #
+ # Helper to write out a Ruby file for a JSON hash. Writes out only
+ # the keys specified in "keys"; anything else must be emitted by the
+ # caller.
+ #
+ # == Example
+ #
+ # to_ruby_keys({"name" => "foo", "environment" => "desert", "foo": "bar"}, [ "name", "environment" ])
+ # ->
+ # 'name "foo"
+ # environment "desert"'
+ #
def to_ruby_keys(object, keys)
result = ''
keys.each do |key|
@@ -115,6 +185,10 @@ class Chef
result
end
+ #
+ # Verify that the JSON hash for this type has a key that matches its name.
+ # Calls the on_error block with the error, if there is one.
+ #
def verify_integrity(object, entry, &on_error)
base_name = remove_dot_json(entry.name)
if object['name'] != base_name
diff --git a/lib/chef/chef_fs/data_handler/environment_data_handler.rb b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
index 9da10ebfa5..5105f2ac49 100644
--- a/lib/chef/chef_fs/data_handler/environment_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
@@ -17,7 +17,7 @@ class Chef
})
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/data_handler/group_data_handler.rb b/lib/chef/chef_fs/data_handler/group_data_handler.rb
index 619822fe70..4d1b10f321 100644
--- a/lib/chef/chef_fs/data_handler/group_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/group_data_handler.rb
@@ -36,7 +36,7 @@ class Chef
result
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/data_handler/node_data_handler.rb b/lib/chef/chef_fs/data_handler/node_data_handler.rb
index f2c97c734f..04faa527f0 100644
--- a/lib/chef/chef_fs/data_handler/node_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/node_data_handler.rb
@@ -21,7 +21,7 @@ class Chef
result
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/data_handler/organization_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
new file mode 100644
index 0000000000..da911c08f0
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
@@ -0,0 +1,30 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationDataHandler < DataHandlerBase
+ def normalize(organization, entry)
+ result = normalize_hash(organization, {
+ 'name' => entry.org,
+ 'full_name' => entry.org,
+ 'org_type' => 'Business',
+ 'clientname' => "#{entry.org}-validator",
+ 'billing_plan' => 'platform-free',
+ })
+ result
+ end
+
+ def preserve_key?(key)
+ return key == 'name'
+ end
+
+ def verify_integrity(object, entry, &on_error)
+ if entry.org != object['name']
+ on_error.call("Name must be '#{entry.org}' (is '#{object['name']}')")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
new file mode 100644
index 0000000000..db56ecc504
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
@@ -0,0 +1,17 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationInvitesDataHandler < DataHandlerBase
+ def normalize(invites, entry)
+ invites.map { |invite| invite.is_a?(Hash) ? invite['username'] : invite }.sort.uniq
+ end
+
+ def minimize(invites, entry)
+ invites
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
new file mode 100644
index 0000000000..afa331775c
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
@@ -0,0 +1,17 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class OrganizationMembersDataHandler < DataHandlerBase
+ def normalize(members, entry)
+ members.map { |member| member.is_a?(Hash) ? member['user']['username'] : member }.sort.uniq
+ end
+
+ def minimize(members, entry)
+ members
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/role_data_handler.rb b/lib/chef/chef_fs/data_handler/role_data_handler.rb
index bc1c076280..21c3013e9f 100644
--- a/lib/chef/chef_fs/data_handler/role_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/role_data_handler.rb
@@ -23,7 +23,7 @@ class Chef
result
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/data_handler/user_data_handler.rb b/lib/chef/chef_fs/data_handler/user_data_handler.rb
index 66a780690a..2b50ce38d8 100644
--- a/lib/chef/chef_fs/data_handler/user_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/user_data_handler.rb
@@ -7,6 +7,7 @@ class Chef
def normalize(user, entry)
normalize_hash(user, {
'name' => remove_dot_json(entry.name),
+ 'username' => remove_dot_json(entry.name),
'admin' => false,
'json_class' => 'Chef::WebUIUser',
'chef_type' => 'webui_user',
@@ -16,7 +17,7 @@ class Chef
})
end
- def preserve_key(key)
+ def preserve_key?(key)
return key == 'name'
end
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
index 4d15d7af33..730fa0e5cc 100644
--- a/lib/chef/chef_fs/file_system.rb
+++ b/lib/chef/chef_fs/file_system.rb
@@ -273,7 +273,6 @@ class Chef
# case we shouldn't waste time trying PUT if we know the file doesn't
# exist.
# Will need to decide how that works with checksums, though.
-
error = false
begin
dest_path = format_path.call(dest_entry) if ui
diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb
index 1bd03a6095..b2545af5ae 100644
--- a/lib/chef/chef_fs/file_system/acl_entry.rb
+++ b/lib/chef/chef_fs/file_system/acl_entry.rb
@@ -32,7 +32,7 @@ class Chef
end
def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, e), "ACLs cannot be deleted."
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self), "ACLs cannot be deleted."
end
def write(file_contents)
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
index b151db6973..20a3f4e2be 100644
--- 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
@@ -35,7 +35,7 @@ class Chef
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 Chef::Config[:versioned_cookbooks]
+ if root.versioned_cookbooks
_canonical_name = canonical_cookbook_name(File.basename(file_path))
fail "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z" unless _canonical_name
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 6ccdc2cf5f..9acfe4b936 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
@@ -31,8 +31,12 @@ class Chef
@data_handler = data_handler
end
+ def write_pretty_json=(value)
+ @write_pretty_json = value
+ end
+
def write_pretty_json
- root.write_pretty_json
+ @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
end
def data_handler
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 d615e0f415..ac272d4c1a 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
@@ -33,32 +33,71 @@ 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
- def initialize(child_paths)
+ #
+ # 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(invitations.json members.json org.json)
def children
- @children ||= child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+ @children ||= begin
+ result = child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+ result += root_dir.children.select { |c| CHILDREN.include?(c.name) } if root_dir
+ result.sort_by { |c| c.name }
+ end
end
def can_have_child?(name, is_dir)
- child_paths.has_key?(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)
- child_paths[name].each do |path|
- begin
- Dir.mkdir(path)
- rescue Errno::EEXIST
+ 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
- child = make_child_entry(name)
@children = nil
child
end
@@ -67,17 +106,17 @@ class Chef
nil
end
- # Used to print out the filesystem
+ # Used to print out a human-readable file system description
def fs_description
- repo_path = File.dirname(child_paths['cookbooks'][0])
- result = "repository at #{repo_path}\n"
- if Chef::Config[:versioned_cookbooks]
+ 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| File.dirname(path) != repo_path }
+ if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
result << " #{name} at #{paths.join(', ')}\n"
end
end
@@ -86,6 +125,27 @@ class Chef
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)
paths = child_paths[name].select do |path|
File.exists?(path)
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
index 0083ee4cfa..370308ee0a 100644
--- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -23,6 +23,9 @@ 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/environments_dir'
require 'chef/chef_fs/data_handler/client_data_handler'
require 'chef/chef_fs/data_handler/role_data_handler'
@@ -33,7 +36,35 @@ require 'chef/chef_fs/data_handler/container_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]
@@ -41,6 +72,7 @@ class Chef
@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
@@ -51,6 +83,7 @@ class Chef
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}"
@@ -81,10 +114,13 @@ class Chef
end
def org
- @org ||= if URI.parse(chef_server_url).path =~ /^\/+organizations\/+([^\/]+)$/
- $1
- else
- nil
+ @org ||= begin
+ path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
+ if File.dirname(path) == '/organizations'
+ File.basename(path)
+ else
+ nil
+ end
end
end
@@ -102,7 +138,10 @@ class Chef
RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
- NodesDir.new(self)
+ NodesDir.new(self),
+ OrgEntry.new("org.json", self),
+ OrganizationMembersEntry.new("members.json", self),
+ OrganizationInvitesEntry.new("invitations.json", self)
]
elsif repo_mode != 'static'
result += [
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
index d7411e1c74..03652dc376 100644
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -32,7 +32,7 @@ class Chef
@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 Chef::Config[:versioned_cookbooks]
+ if root.versioned_cookbooks
if name =~ VALID_VERSIONED_COOKBOOK_NAME
@cookbook_name = $1
@version = $2
diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb
index 7868322590..16203b727c 100644
--- a/lib/chef/chef_fs/file_system/cookbook_file.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_file.rb
@@ -18,7 +18,7 @@
require 'chef/chef_fs/file_system/base_fs_object'
require 'chef/http/simple'
-require 'digest/md5'
+require 'openssl'
class Chef
module ChefFS
@@ -74,7 +74,7 @@ class Chef
private
def calc_checksum(value)
- Digest::MD5.hexdigest(value)
+ OpenSSL::Digest::MD5.hexdigest(value)
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
index d4857cdabd..27bedd3827 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -51,7 +51,7 @@ class Chef
def children
@children ||= begin
- if Chef::Config[:versioned_cookbooks]
+ 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|
@@ -71,7 +71,7 @@ class Chef
end
def upload_cookbook_from(other, options = {})
- Chef::Config[:versioned_cookbooks] ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(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
@@ -155,7 +155,7 @@ class Chef
def can_have_child?(name, is_dir)
return false if !is_dir
- return false if Chef::Config[:versioned_cookbooks] && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
+ return false if root.versioned_cookbooks && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
return true
end
end
diff --git a/lib/chef/chef_fs/file_system/org_entry.rb b/lib/chef/chef_fs/file_system/org_entry.rb
new file mode 100644
index 0000000000..852956e1e5
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/org_entry.rb
@@ -0,0 +1,34 @@
+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 initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ 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
new file mode 100644
index 0000000000..cb26326050
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
@@ -0,0 +1,58 @@
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_invites_data_handler'
+
+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(JSON.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
new file mode 100644
index 0000000000..eb524d5ea2
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb
@@ -0,0 +1,57 @@
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/data_handler/organization_members_data_handler'
+
+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(JSON.parse(contents, :create_additions => false))
+ members = minimize_value(_read_json)
+ (desired_members - members).each do |member|
+ begin
+ rest.post(File.join(api_path, member), {})
+ rescue Net::HTTPServerException => e
+ if e.response.code == '404'
+ 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/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
index 67252a6f2f..ac47ff4f25 100644
--- a/lib/chef/chef_fs/file_system/rest_list_entry.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb
@@ -80,13 +80,13 @@ class Chef
end
def read
- Chef::JSONCompat.to_json_pretty(_read_hash)
+ Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
end
- def _read_hash
+ def _read_json
begin
# Minimize the value (get rid of defaults) so the results don't look terrible
- minimize_value(root.get_json(api_path))
+ 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
@@ -119,7 +119,7 @@ class Chef
# Grab this value
begin
- value = _read_hash
+ value = _read_json
rescue Chef::ChefFS::FileSystem::NotFoundError
return [ false, :none, other_value_json ]
end
@@ -169,7 +169,16 @@ class Chef
end
end
end
+
+ def api_error_text(response)
+ begin
+ JSON.parse(response.body)['error'].join("\n")
+ rescue
+ response.body
+ end
+ end
end
+
end
end
end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 652c728550..86872dab71 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -68,7 +68,7 @@ class Chef
end
end
- @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config)
+ @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config, ui)
Chef::ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 69e191bdf8..2de3ca3e64 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -25,7 +25,6 @@ require 'chef/log'
require 'chef/rest'
require 'chef/api_client'
require 'chef/api_client/registration'
-require 'chef/platform/query_helpers'
require 'chef/node'
require 'chef/role'
require 'chef/file_cache'
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 9d465a3cea..1963a95aab 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -23,6 +23,7 @@ require 'chef/log'
require 'chef/exceptions'
require 'mixlib/config'
require 'chef/util/selinux'
+require 'chef/util/path_helper'
require 'pathname'
class Chef
@@ -30,6 +31,8 @@ class Chef
extend Mixlib::Config
+ PathHelper = Chef::Util::PathHelper
+
# Evaluates the given string as config.
#
# +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
@@ -58,37 +61,13 @@ class Chef
configuration.inspect
end
- def self.on_windows?
- RUBY_PLATFORM =~ /mswin|mingw|windows/
- end
-
- BACKSLASH = '\\'.freeze
-
- def self.platform_path_separator
- if on_windows?
- File::ALT_SEPARATOR || BACKSLASH
- else
- File::SEPARATOR
- end
- end
-
- def self.path_join(*args)
- args = args.flatten
- args.inject do |joined_path, component|
- unless joined_path[-1,1] == platform_path_separator
- joined_path += platform_path_separator
- end
- joined_path += component
- end
- end
-
def self.platform_specific_path(path)
- if on_windows?
- # turns /etc/chef/client.rb into C:/chef/client.rb
- system_drive = env['SYSTEMDRIVE'] ? env['SYSTEMDRIVE'] : ""
- path = File.join(system_drive, path.split('/')[2..-1])
- # ensure all forward slashes are backslashes
- path.gsub!(File::SEPARATOR, (File::ALT_SEPARATOR || '\\'))
+ path = PathHelper.cleanpath(path)
+ if Chef::Platform.windows?
+ # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
+ if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
+ path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
+ end
end
path
end
@@ -101,17 +80,13 @@ class Chef
configurable(:config_file)
default(:config_dir) do
- if local_mode
- path_join(user_home, ".chef#{platform_path_separator}")
+ if config_file
+ PathHelper.dirname(config_file)
else
- config_file && ::File.dirname(config_file)
+ PathHelper.join(user_home, ".chef", "")
end
end
- # No config file (client.rb / knife.rb / etc.) will be loaded outside this path.
- # Major use case is tests, where we don't want to load the user's config files.
- configurable(:config_file_jail)
-
default :formatters, []
# Override the config dispatch to set the value of multiple server options simultaneously
@@ -119,7 +94,7 @@ class Chef
# === Parameters
# url<String>:: String to be set for all of the chef-server-api URL's
#
- configurable(:chef_server_url).writes_value { |url| url.strip }
+ configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
# When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
# So while this is basically identical to what method_missing would do, we pull
@@ -150,7 +125,7 @@ class Chef
# In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
# This allows us to run config-free.
path = cwd
- until File.directory?(path_join(path, "cookbooks"))
+ until File.directory?(PathHelper.join(path, "cookbooks"))
new_path = File.expand_path('..', path)
if new_path == path
Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.")
@@ -164,9 +139,9 @@ class Chef
def self.derive_path_from_chef_repo_path(child_path)
if chef_repo_path.kind_of?(String)
- path_join(chef_repo_path, child_path)
+ PathHelper.join(chef_repo_path, child_path)
else
- chef_repo_path.map { |path| path_join(path, child_path)}
+ chef_repo_path.map { |path| PathHelper.join(path, child_path)}
end
end
@@ -238,7 +213,7 @@ class Chef
# this is under the user's home directory.
default(:cache_path) do
if local_mode
- "#{config_dir}local-mode-cache"
+ PathHelper.join(config_dir, 'local-mode-cache')
else
primary_cache_root = platform_specific_path("/var")
primary_cache_path = platform_specific_path("/var/chef")
@@ -247,8 +222,7 @@ class Chef
# Otherwise, we'll create .chef under the user's home directory and use that as
# the cache path.
unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
- secondary_cache_path = File.join(user_home, '.chef')
- secondary_cache_path.gsub!(File::SEPARATOR, platform_path_separator) # Safety, mainly for Windows...
+ secondary_cache_path = PathHelper.join(user_home, '.chef')
Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
secondary_cache_path
else
@@ -263,20 +237,20 @@ class Chef
end
# Where cookbook files are stored on the server (by content checksum)
- default(:checksum_path) { path_join(cache_path, "checksums") }
+ default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
# Where chef's cache files should be stored
- default(:file_cache_path) { path_join(cache_path, "cache") }
+ default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
# Where backups of chef-managed files should go
- default(:file_backup_path) { path_join(cache_path, "backup") }
+ default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
# The chef-client (or solo) lockfile.
#
# If your `file_cache_path` resides on a NFS (or non-flock()-supporting
# fs), it's recommended to set this to something like
# '/tmp/chef-client-running.pid'
- default(:lockfile) { path_join(file_cache_path, "chef-client-running.pid") }
+ default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
## Daemonization Settings ##
# What user should Chef run as?
@@ -372,7 +346,7 @@ class Chef
# Path to the default CA bundle files.
default :ssl_ca_path, nil
default(:ssl_ca_file) do
- if on_windows? and embedded_path = embedded_dir
+ if Chef::Platform.windows? and embedded_path = embedded_dir
cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem")
cacert_path if File.exist?(cacert_path)
else
@@ -384,7 +358,7 @@ class Chef
# certificates in this directory will be added to whatever CA bundle ruby
# is using. Use this to add self-signed certs for your Chef Server or local
# HTTP file servers.
- default(:trusted_certs_dir) { config_dir && path_join(config_dir, "trusted_certs") }
+ default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") }
# Where should chef-solo download recipes from?
default :recipe_url, nil
@@ -417,10 +391,6 @@ class Chef
# This secret is used to decrypt encrypted data bag items.
default(:encrypted_data_bag_secret) do
- # We have to check for the existence of the default file before setting it
- # since +Chef::Config[:encrypted_data_bag_secret]+ is read by older
- # bootstrap templates to determine if the local secret should be uploaded to
- # node being bootstrapped. This should be removed in Chef 12.
if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
platform_specific_path("/etc/chef/encrypted_data_bag_secret")
else
@@ -456,15 +426,15 @@ class Chef
default :validation_client_name, "chef-validator"
# When creating a new client via the validation_client account, Chef 11
- # servers allow the client to generate a key pair locally and sent the
+ # servers allow the client to generate a key pair locally and send the
# public key to the server. This is more secure and helps offload work from
# the server, enhancing scalability. If enabled and the remote server
# implements only the Chef 10 API, client registration will not work
# properly.
#
- # The default value is `false` (Server generates client keys). Set to
- # `true` to enable client-side key generation.
- default(:local_key_generation) { false }
+ # The default value is `true`. Set to `false` to disable client-side key
+ # generation (server generates client keys).
+ default(:local_key_generation) { true }
# Zypper package provider gpg checks. Set to true to enable package
# gpg signature checking. This will be default in the
@@ -492,7 +462,7 @@ class Chef
default(:syntax_check_cache_path) { cache_options[:path] }
# Deprecated:
- default(:cache_options) { { :path => path_join(file_cache_path, "checksums") } }
+ default(:cache_options) { { :path => PathHelper.join(file_cache_path, "checksums") } }
# Set to false to silence Chef 11 deprecation warnings:
default :chef11_deprecation_warnings, true
@@ -505,6 +475,9 @@ class Chef
default :ssh_gateway, nil
default :bootstrap_version, nil
default :bootstrap_proxy, nil
+ default :bootstrap_template, "chef-full"
+ default :secret, nil
+ default :secret_file, nil
default :identity_file, nil
default :host_key_verify, nil
default :forward_agent, nil
@@ -537,7 +510,7 @@ class Chef
# Those lists of regular expressions define what chef considers a
# valid user and group name
- if on_windows?
+ if Chef::Platform.windows?
set_defaults_for_windows
else
set_defaults_for_nix
@@ -550,7 +523,7 @@ class Chef
end
def self.windows_home_path
- windows_home_path = env['SYSTEMDRIVE'] + env['HOMEPATH'] if env['SYSTEMDRIVE'] && env['HOMEPATH']
+ env['SYSTEMDRIVE'] + env['HOMEPATH'] if env['SYSTEMDRIVE'] && env['HOMEPATH']
end
# returns a platform specific path to the user home dir if set, otherwise default to current directory.
@@ -614,6 +587,51 @@ class Chef
default :normal_attribute_whitelist, nil
default :override_attribute_whitelist, nil
+ # Chef requires an English-language UTF-8 locale to function properly. We attempt
+ # to use the 'locale -a' command and search through a list of preferences until we
+ # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be
+ # able to use that even if there is no English locale on the server, but Mac, Solaris,
+ # AIX, etc do not have that locale. We then try to find an English locale and fall
+ # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try
+ # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
+ # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then
+ # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding
+ # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
+ # default rather than drop English.
+ #
+ # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
+ # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
+ default :internal_locale do
+ begin
+ locales = `locale -a`.split
+ case
+ when locales.include?('C.UTF-8')
+ 'C.UTF-8'
+ when locales.include?('en_US.UTF-8')
+ 'en_US.UTF-8'
+ when locales.include?('en.UTF-8')
+ 'en.UTF-8'
+ when guesses = locales.select { |l| l =~ /^en_.*UTF-8$'/ }
+ guesses.first
+ else
+ Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
+ 'C'
+ end
+ rescue
+ Chef::Log.warn "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+ 'en_US.UTF-8'
+ end
+ end
+
+ # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
+ # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
+ # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
+ # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be
+ # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
+ # magic tags to make ruby correctly identify the encoding being used. Changing this default will
+ # break Chef community cookbooks and is very highly discouraged.
+ default :ruby_encoding, Encoding::UTF_8
+
# If installed via an omnibus installer, this gives the path to the
# "embedded" directory which contains all of the software packaged with
# omnibus. This is used to locate the cacert.pem file on windows.
diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb
index 1d0693eaa2..a8aad0740d 100644
--- a/lib/chef/config_fetcher.rb
+++ b/lib/chef/config_fetcher.rb
@@ -7,11 +7,9 @@ class Chef
class ConfigFetcher
attr_reader :config_location
- attr_reader :config_file_jail
- def initialize(config_location, config_file_jail=nil)
+ def initialize(config_location)
@config_location = config_location
- @config_file_jail = config_file_jail
end
def fetch_json
@@ -48,24 +46,11 @@ class Chef
def config_missing?
return false if remote_config?
- # Check if the config file exists, and check if it is underneath the config file jail
- begin
- real_config_file = Pathname.new(config_location).realpath.to_s
- rescue Errno::ENOENT
- return true
- end
-
- # If realpath succeeded, the file exists
- return false if !config_file_jail
-
- begin
- real_jail = Pathname.new(config_file_jail).realpath.to_s
- rescue Errno::ENOENT
- Chef::Log.warn("Config file jail #{config_file_jail} does not exist: will not load any config file.")
- return true
- end
-
- !Chef::ChefFS::PathUtils.descendant_of?(real_config_file, real_jail)
+ # Check if the config file exists
+ Pathname.new(config_location).realpath.to_s
+ false
+ rescue Errno::ENOENT
+ return true
end
def http
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index fac8c80993..47258c4d4e 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -18,21 +18,29 @@ class Chef
UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze
- attr_reader :cookbook_name
attr_reader :cookbook_settings
attr_reader :cookbook_paths
attr_reader :metadata_filenames
attr_reader :frozen
attr_reader :uploaded_cookbook_version_file
+ attr_reader :cookbook_path
+
+ # The cookbook's name as inferred from its directory.
+ attr_reader :inferred_cookbook_name
+
+ attr_reader :metadata_error
+
def initialize(path, chefignore=nil)
@cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded
# We keep a list of all cookbook paths that have been merged in
- @cookbook_paths = [ @cookbook_path ]
- @cookbook_name = File.basename( path )
+ @cookbook_paths = [ cookbook_path ]
+
+ @inferred_cookbook_name = File.basename( path )
@chefignore = chefignore
- @metadata = Hash.new
+ @metadata = nil
@relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/
+ @metadata_loaded = false
@cookbook_settings = {
:attribute_filenames => {},
:definition_filenames => {},
@@ -46,9 +54,29 @@ class Chef
}
@metadata_filenames = []
+ @metadata_error = nil
+ end
+
+ # Load the cookbook. Raises an error if the cookbook_path given to the
+ # constructor doesn't point to a valid cookbook.
+ def load!
+ file_paths_map = load
+
+ if empty?
+ raise Exceptions::CookbookNotFoundInRepo, "The directory #{cookbook_path} does not contain a cookbook"
+ end
+ file_paths_map
end
- def load_cookbooks
+ # Load the cookbook. Does not raise an error if given a non-cookbook
+ # directory as the cookbook_path. This behavior is provided for
+ # compatibility, it is recommended to use #load! instead.
+ def load
+ metadata # force lazy evaluation to occur
+
+ # re-raise any exception that occurred when reading the metadata
+ raise_metadata_error!
+
load_as(:attribute_filenames, 'attributes', '*.rb')
load_as(:definition_filenames, 'definitions', '*.rb')
load_as(:recipe_filenames, 'recipes', '*.rb')
@@ -61,31 +89,37 @@ class Chef
remove_ignored_files
- if File.exists?(File.join(@cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
- @uploaded_cookbook_version_file = File.join(@cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
+ if empty?
+ Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
+ end
+ @cookbook_settings
+ end
+
+ alias :load_cookbooks :load
+
+ def metadata_filenames
+ return @metadata_filenames unless @metadata_filenames.empty?
+ if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
+ @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
end
- if File.exists?(File.join(@cookbook_path, "metadata.rb"))
- @metadata_filenames << File.join(@cookbook_path, "metadata.rb")
- elsif File.exists?(File.join(@cookbook_path, "metadata.json"))
- @metadata_filenames << File.join(@cookbook_path, "metadata.json")
+ if File.exists?(File.join(cookbook_path, "metadata.rb"))
+ @metadata_filenames << File.join(cookbook_path, "metadata.rb")
+ elsif File.exists?(File.join(cookbook_path, "metadata.json"))
+ @metadata_filenames << File.join(cookbook_path, "metadata.json")
elsif @uploaded_cookbook_version_file
@metadata_filenames << @uploaded_cookbook_version_file
end
# Set frozen based on .uploaded-cookbook-version.json
set_frozen
-
- if empty?
- Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
- end
- @cookbook_settings
+ @metadata_filenames
end
def cookbook_version
return nil if empty?
- Chef::CookbookVersion.new(@cookbook_name.to_sym, *@cookbook_paths).tap do |c|
+ Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c|
c.attribute_filenames = cookbook_settings[:attribute_filenames].values
c.definition_filenames = cookbook_settings[:definition_filenames].values
c.recipe_filenames = cookbook_settings[:recipe_filenames].values
@@ -95,16 +129,32 @@ class Chef
c.resource_filenames = cookbook_settings[:resource_filenames].values
c.provider_filenames = cookbook_settings[:provider_filenames].values
c.root_filenames = cookbook_settings[:root_filenames].values
- c.metadata_filenames = @metadata_filenames
- c.metadata = metadata(c)
+ c.metadata_filenames = metadata_filenames
+ c.metadata = metadata
+
c.freeze_version if @frozen
end
end
+ def cookbook_name
+ # The `name` attribute is now required in metadata, so
+ # inferred_cookbook_name generally should not be used. Per CHEF-2923,
+ # we have to not raise errors in cookbook metadata immediately, so that
+ # users can still `knife cookbook upload some-cookbook` when an
+ # unrelated cookbook has an error in its metadata. This situation
+ # could prevent us from reading the `name` attribute from the metadata
+ # entirely, but the name is used as a hash key in CookbookLoader, so we
+ # fall back to the inferred name here.
+ (metadata.name || @inferred_cookbook_name).to_sym
+ end
+
# Generates the Cookbook::Metadata object
- def metadata(cookbook_version)
- @metadata = Chef::Cookbook::Metadata.new(cookbook_version)
- @metadata_filenames.each do |metadata_file|
+ def metadata
+ return @metadata unless @metadata.nil?
+
+ @metadata = Chef::Cookbook::Metadata.new
+
+ metadata_filenames.each do |metadata_file|
case metadata_file
when /\.rb$/
apply_ruby_metadata(metadata_file)
@@ -116,51 +166,75 @@ class Chef
raise RuntimeError, "Invalid metadata file: #{metadata_file} for cookbook: #{cookbook_version}"
end
end
+
+ @metadata
+
+ # Rescue errors so that users can upload cookbooks via `knife cookbook
+ # upload` even if some cookbooks in their chef-repo have errors in
+ # their metadata. We only rescue StandardError because you have to be
+ # doing something *really* terrible to raise an exception that inherits
+ # directly from Exception in your metadata.rb file.
+ rescue StandardError => e
+ @metadata_error = e
@metadata
end
+ def raise_metadata_error!
+ raise @metadata_error unless @metadata_error.nil?
+ # Metadata won't be valid if the cookbook is empty. If the cookbook is
+ # actually empty, a metadata error here would be misleading, so don't
+ # raise it (if called by #load!, a different error is raised).
+ if !empty? && !metadata.valid?
+ message = "Cookbook loaded at path(s) [#{@cookbook_paths.join(', ')}] has invalid metadata: #{metadata.errors.join('; ')}"
+ raise Exceptions::MetadataNotValid, message
+ end
+ false
+ end
+
def empty?
- @cookbook_settings.values.all? { |files_hash| files_hash.empty? } && @metadata_filenames.size == 0
+ cookbook_settings.values.all? { |files_hash| files_hash.empty? } && metadata_filenames.size == 0
end
def merge!(other_cookbook_loader)
other_cookbook_settings = other_cookbook_loader.cookbook_settings
- @cookbook_settings.each do |file_type, file_list|
+ cookbook_settings.each do |file_type, file_list|
file_list.merge!(other_cookbook_settings[file_type])
end
- @metadata_filenames.concat(other_cookbook_loader.metadata_filenames)
+ metadata_filenames.concat(other_cookbook_loader.metadata_filenames)
@cookbook_paths += other_cookbook_loader.cookbook_paths
@frozen = true if other_cookbook_loader.frozen
+ @metadata = nil # reset metadata so it gets reloaded and all metadata files applied.
+ self
end
def chefignore
- @chefignore ||= Chefignore.new(File.basename(@cookbook_path))
+ @chefignore ||= Chefignore.new(File.basename(cookbook_path))
end
def load_root_files
- Dir.glob(File.join(@cookbook_path, '*'), File::FNM_DOTMATCH).each do |file|
+ Dir.glob(File.join(cookbook_path, '*'), File::FNM_DOTMATCH).each do |file|
next if File.directory?(file)
next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE
- @cookbook_settings[:root_filenames][file[@relative_path, 1]] = file
+ cookbook_settings[:root_filenames][file[@relative_path, 1]] = file
end
end
def load_recursively_as(category, category_dir, glob)
- file_spec = File.join(@cookbook_path, category_dir, '**', glob)
+ file_spec = File.join(cookbook_path, category_dir, '**', glob)
Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file|
next if File.directory?(file)
- @cookbook_settings[category][file[@relative_path, 1]] = file
+ cookbook_settings[category][file[@relative_path, 1]] = file
end
end
def load_as(category, *path_glob)
- Dir[File.join(@cookbook_path, *path_glob)].each do |file|
- @cookbook_settings[category][file[@relative_path, 1]] = file
+ Dir[File.join(cookbook_path, *path_glob)].each do |file|
+ cookbook_settings[category][file[@relative_path, 1]] = file
end
end
def remove_ignored_files
- @cookbook_settings.each_value do |file_list|
+ cookbook_settings.each_value do |file_list|
file_list.reject! do |relative_path, full_path|
chefignore.ignored?(relative_path)
end
@@ -171,7 +245,7 @@ class Chef
begin
@metadata.from_file(file)
rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Error evaluating metadata.rb for #@cookbook_name in " + file)
+ Chef::Log.error("Error evaluating metadata.rb for #@inferred_cookbook_name in " + file)
raise
end
end
@@ -180,17 +254,26 @@ class Chef
begin
@metadata.from_json(IO.read(file))
rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in " + file)
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file)
raise
end
end
def apply_json_cookbook_version_metadata(file)
begin
- data = Chef::JSONCompat.from_json(IO.read(file), :create_additions => false)
+ data = Chef::JSONCompat.parse(IO.read(file))
@metadata.from_hash(data['metadata'])
+ # the JSON cookbok metadata file is only used by chef-zero.
+ # The Chef Server API currently does not enforce that the metadata
+ # have a `name` field, but that will cause an error when attempting
+ # to load the cookbook. To keep compatibility, we fake it by setting
+ # the metadata name from the cookbook version object's name.
+ #
+ # This behavior can be removed if/when Chef Server enforces that the
+ # metadata contains a name key.
+ @metadata.name(data['cookbook_name']) unless data['metadata'].key?('name')
rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in " + file)
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file)
raise
end
end
@@ -198,10 +281,10 @@ class Chef
def set_frozen
if uploaded_cookbook_version_file
begin
- data = Chef::JSONCompat.from_json(IO.read(uploaded_cookbook_version_file), :create_additions => false)
+ data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file))
@frozen = data['frozen?']
rescue Chef::Exceptions::JSON::ParseError
- Chef::Log.error("Couldn't parse cookbook metadata JSON for #@cookbook_name in #{uploaded_cookbook_version_file}")
+ Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in #{uploaded_cookbook_version_file}")
raise
end
end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 8d3f8b84aa..3964354d50 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -18,6 +18,7 @@
# limitations under the License.
#
+require 'chef/exceptions'
require 'chef/mash'
require 'chef/mixin/from_file'
require 'chef/mixin/params_validate'
@@ -67,18 +68,17 @@ class Chef
include Chef::Mixin::ParamsValidate
include Chef::Mixin::FromFile
- attr_reader :cookbook,
- :platforms,
- :dependencies,
- :recommendations,
- :suggestions,
- :conflicting,
- :providing,
- :replacing,
- :attributes,
- :groupings,
- :recipes,
- :version
+ attr_reader :platforms
+ attr_reader :dependencies
+ attr_reader :recommendations
+ attr_reader :suggestions
+ attr_reader :conflicting
+ attr_reader :providing
+ attr_reader :replacing
+ attr_reader :attributes
+ attr_reader :groupings
+ attr_reader :recipes
+ attr_reader :version
# Builds a new Chef::Cookbook::Metadata object.
#
@@ -90,14 +90,16 @@ class Chef
#
# === Returns
# metadata<Chef::Cookbook::Metadata>
- def initialize(cookbook=nil, maintainer='YOUR_COMPANY_NAME', maintainer_email='YOUR_EMAIL', license='none')
- @cookbook = cookbook
- @name = cookbook ? cookbook.name : ""
- @long_description = ""
- self.maintainer(maintainer)
- self.maintainer_email(maintainer_email)
- self.license(license)
- self.description('A fabulous new cookbook')
+ def initialize
+ @name = nil
+
+ @description = ''
+ @long_description = ''
+ @license = 'All rights reserved'
+
+ @maintainer = nil
+ @maintainer_email = nil
+
@platforms = Mash.new
@dependencies = Mash.new
@recommendations = Mash.new
@@ -108,15 +110,9 @@ class Chef
@attributes = Mash.new
@groupings = Mash.new
@recipes = Mash.new
- @version = Version.new "0.0.0"
- if cookbook
- @recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e|
- e = self.name.to_s if e =~ /::default$/
- r[e] ||= ""
- self.provides e
- r
- end
- end
+ @version = Version.new("0.0.0")
+
+ @errors = []
end
def ==(other)
@@ -125,6 +121,32 @@ class Chef
end
end
+ # Whether this metadata is valid. In order to be valid, all required
+ # fields must be set. Chef's validation implementation checks the content
+ # of a given field when setting (and raises an error if the content does
+ # not meet the criteria), so the content of the fields is not considered
+ # when checking validity.
+ #
+ # === Returns
+ # valid<Boolean>:: Whether this metadata object is valid
+ def valid?
+ run_validation
+ @errors.empty?
+ end
+
+ # A list of validation errors for this metadata object. See #valid? for
+ # comments about the validation criteria.
+ #
+ # If there are any validation errors, one or more error strings will be
+ # returned. Otherwise an empty array is returned.
+ #
+ # === Returns
+ # error messages<Array>:: Whether this metadata object is valid
+ def errors
+ run_validation
+ @errors
+ end
+
# Sets the cookbooks maintainer, or returns it.
#
# === Parameters
@@ -365,6 +387,32 @@ class Chef
@recipes[name] = description
end
+ # Sets the cookbook's recipes to the list of recipes in the given
+ # +cookbook+. Any recipe that already has a description (if set by the
+ # #recipe method) will not be updated.
+ #
+ # === Parameters
+ # cookbook<CookbookVersion>:: CookbookVersion object representing the cookbook
+ # description<String>:: The description of the recipe
+ #
+ # === Returns
+ # recipe_unqualified_names<Array>:: An array of the recipe names given by the cookbook
+ def recipes_from_cookbook_version(cookbook)
+ cookbook.fully_qualified_recipe_names.map do |recipe_name|
+ unqualified_name =
+ if recipe_name =~ /::default$/
+ self.name.to_s
+ else
+ recipe_name
+ end
+
+ @recipes[unqualified_name] ||= ""
+ provides(unqualified_name)
+
+ unqualified_name
+ end
+ end
+
# Adds an attribute )hat a user needs to configure for this cookbook. Takes
# a name (with the / notation for a nested attribute), followed by any of
# these options
@@ -480,9 +528,9 @@ class Chef
def self.validate_json(json_str)
o = Chef::JSONCompat.from_json(json_str)
metadata = new()
- VERSION_CONSTRAINTS.each do |method_name, hash_key|
- if constraints = o[hash_key]
- constraints.each do |cb_name, constraints|
+ VERSION_CONSTRAINTS.each do |dependency_type, hash_key|
+ if dependency_group = o[hash_key]
+ dependency_group.each do |cb_name, constraints|
metadata.send(method_name, cb_name, *Array(constraints))
end
end
@@ -497,6 +545,12 @@ class Chef
private
+ def run_validation
+ if name.nil?
+ @errors = ["The `name' attribute is required in cookbook metadata"]
+ end
+ end
+
def new_args_format(caller_name, dep_name, version_constraints)
if version_constraints.empty?
">= 0.0.0"
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index 27cf978acb..d569cdd008 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -50,6 +50,9 @@ class Chef
repo_path = File.expand_path(repo_path)
end
+ @preloaded_cookbooks = false
+ @loaders_by_name = {}
+
# Used to track which cookbooks appear in multiple places in the cookbook repos
# and are merged in to a single cookbook by file shadowing. This behavior is
# deprecated, so users of this class may issue warnings to the user by checking
@@ -64,25 +67,25 @@ class Chef
end
def load_cookbooks
- @repo_paths.each do |repo_path|
- Dir[File.join(repo_path, "*")].each do |cookbook_path|
- load_cookbook(File.basename(cookbook_path), [repo_path])
- end
+ preload_cookbooks
+ @loaders_by_name.each do |cookbook_name, _loaders|
+ load_cookbook(cookbook_name)
end
@cookbooks_by_name
end
- def load_cookbook(cookbook_name, repo_paths=nil)
- repo_paths ||= @repo_paths
- repo_paths.each do |repo_path|
- @chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path)
- cookbook_path = File.join(repo_path, cookbook_name.to_s)
- next unless File.directory?(cookbook_path) and Dir[File.join(repo_path, "*")].include?(cookbook_path)
- loader = Cookbook::CookbookVersionLoader.new(cookbook_path, @chefignores[repo_path])
- loader.load_cookbooks
+ def load_cookbook(cookbook_name)
+ preload_cookbooks
+
+ return nil unless @loaders_by_name.key?(cookbook_name.to_s)
+
+ cookbook_loaders_for(cookbook_name).each do |loader|
+ loader.load
+
next if loader.empty?
- cookbook_name = loader.cookbook_name
- @cookbooks_paths[cookbook_name] << cookbook_path # for deprecation warnings
+
+ @cookbooks_paths[cookbook_name] << loader.cookbook_path # for deprecation warnings
+
if @loaded_cookbooks.key?(cookbook_name)
@merged_cookbooks << cookbook_name # for deprecation warnings
@loaded_cookbooks[cookbook_name].merge!(loader)
@@ -130,5 +133,50 @@ class Chef
end
alias :cookbooks :values
+ private
+
+ def preload_cookbooks
+ return false if @preloaded_cookbooks
+
+ all_directories_in_repo_paths.each do |cookbook_path|
+ preload_cookbook(cookbook_path)
+ end
+ @preloaded_cookbooks = true
+ true
+ end
+
+ def preload_cookbook(cookbook_path)
+ repo_path = File.dirname(cookbook_path)
+ @chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path)
+ loader = Cookbook::CookbookVersionLoader.new(cookbook_path, @chefignores[repo_path])
+ add_cookbook_loader(loader)
+ end
+
+ def all_directories_in_repo_paths
+ @all_directories_in_repo_paths ||=
+ all_files_in_repo_paths.select { |path| File.directory?(path) }
+ end
+
+ def all_files_in_repo_paths
+ @all_files_in_repo_paths ||=
+ begin
+ @repo_paths.inject([]) do |all_children, repo_path|
+ all_children += Dir[File.join(repo_path, "*")]
+ end
+ end
+ end
+
+ def add_cookbook_loader(loader)
+ cookbook_name = loader.cookbook_name
+
+ @loaders_by_name[cookbook_name.to_s] ||= []
+ @loaders_by_name[cookbook_name.to_s] << loader
+ loader
+ end
+
+ def cookbook_loaders_for(cookbook_name)
+ @loaders_by_name[cookbook_name.to_s]
+ end
+
end
end
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index 92193fee33..c444c8251b 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -143,7 +143,7 @@ class Chef
http = Net::HTTP.new(url.host, url.port)
if url.scheme == "https"
http.use_ssl = true
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.verify_mode = verify_mode
end
res = http.request(req)
#res = http.start {|http_proc| http_proc.request(req) }
@@ -165,6 +165,17 @@ class Chef
res
end
+ private
+
+ def verify_mode
+ verify_mode = Chef::Config[:ssl_verify_mode]
+ if verify_mode == :verify_none
+ OpenSSL::SSL::VERIFY_NONE
+ elsif verify_mode == :verify_peer
+ OpenSSL::SSL::VERIFY_PEER
+ end
+ end
+
end
class StreamPart
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 4482f778c1..76e6d152b2 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -51,10 +51,14 @@ class Chef
attr_accessor :provider_filenames
attr_accessor :root_filenames
attr_accessor :name
- attr_accessor :metadata
attr_accessor :metadata_filenames
attr_accessor :status
+ # A Chef::Cookbook::Metadata object. It has a setter that fixes up the
+ # metadata to add descriptions of the recipes contained in this
+ # CookbookVersion.
+ attr_reader :metadata
+
# attribute_filenames also has a setter that has non-default
# functionality.
attr_reader :attribute_filenames
@@ -195,6 +199,12 @@ class Chef
attribute_filenames
end
+ def metadata=(metadata)
+ @metadata = metadata
+ @metadata.recipes_from_cookbook_version(self)
+ @metadata
+ end
+
## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
alias :attribute_files :attribute_filenames
alias :attribute_files= :attribute_filenames=
diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb
index 669ff8b8a5..0805bccee3 100644
--- a/lib/chef/digester.rb
+++ b/lib/chef/digester.rb
@@ -18,14 +18,11 @@
# limitations under the License.
#
-require 'digest'
+require 'openssl'
class Chef
class Digester
-
- def self.instance
- @instance ||= new
- end
+ include Singleton
def self.checksum_for_file(*args)
instance.checksum_for_file(*args)
@@ -40,7 +37,7 @@ class Chef
end
def generate_checksum(file)
- checksum_file(file, Digest::SHA256.new)
+ checksum_file(file, OpenSSL::Digest::SHA256.new)
end
def self.generate_md5_checksum_for_file(*args)
@@ -48,11 +45,11 @@ class Chef
end
def generate_md5_checksum_for_file(file)
- checksum_file(file, Digest::MD5.new)
+ checksum_file(file, OpenSSL::Digest::MD5.new)
end
def generate_md5_checksum(io)
- checksum_io(io, Digest::MD5.new)
+ checksum_io(io, OpenSSL::Digest::MD5.new)
end
private
@@ -70,4 +67,3 @@ class Chef
end
end
-
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
index 65e7b185a7..3dafbca6bf 100644
--- a/lib/chef/dsl/data_query.rb
+++ b/lib/chef/dsl/data_query.rb
@@ -53,14 +53,60 @@ class Chef
raise
end
- def data_bag_item(bag, item)
+ def data_bag_item(bag, item, secret=nil)
DataBag.validate_name!(bag.to_s)
DataBagItem.validate_id!(item)
- DataBagItem.load(bag, item)
+
+ item = DataBagItem.load(bag, item)
+ if encrypted?(item.raw_data)
+ Log.debug("Data bag item looks encrypted: #{bag.inspect} #{item.inspect}")
+
+ # Try to load the data bag item secret, if secret is not provided.
+ # Chef::EncryptedDataBagItem.load_secret may throw a variety of errors.
+ begin
+ secret ||= EncryptedDataBagItem.load_secret
+ item = EncryptedDataBagItem.new(item.raw_data, secret)
+ rescue Exception
+ Log.error("Failed to load secret for encrypted data bag item: #{bag.inspect} #{item.inspect}")
+ raise
+ end
+ end
+
+ item
rescue Exception
Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}")
raise
end
+
+ private
+
+ # Tries to autodetect if the item's raw hash appears to be encrypted.
+ def encrypted?(raw_data)
+ data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key.
+ # Assume hashes containing only the "id" key are not encrypted.
+ # Otherwise, remove the keys that don't appear to be encrypted and compare
+ # the result with the hash. If some entry has been removed, then some entry
+ # doesn't appear to be encrypted and we assume the entire hash is not encrypted.
+ data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data
+ end
+
+ # Checks if data looks like it has been encrypted by
+ # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns
+ # true only when there is an exact match between the VersionXEncryptor
+ # keys and the hash's keys.
+ def looks_like_encrypted?(data)
+ return false unless data.is_a?(Hash) && data.has_key?("version")
+ case data["version"]
+ when 1
+ Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
+ when 2
+ Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort
+ when 3
+ Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort
+ else
+ false # version means something else... assume not encrypted.
+ end
+ end
end
end
end
@@ -68,4 +114,3 @@ end
# **DEPRECATED**
# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
require 'chef/mixin/language'
-
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
index 33aa451f30..2a52010a70 100644
--- a/lib/chef/dsl/platform_introspection.rb
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -50,8 +50,12 @@ class Chef
def value_for_node(node)
platform, version = node[:platform].to_s, node[:platform_version].to_s
+ # Check if we match a version constraint via Chef::VersionConstraint and Chef::Version::Platform
+ matched_value = match_versions(node)
if @values.key?(platform) && @values[platform].key?(version)
@values[platform][version]
+ elsif matched_value
+ matched_value
elsif @values.key?(platform) && @values[platform].key?("default")
@values[platform]["default"]
elsif @values.key?("default")
@@ -63,6 +67,44 @@ class Chef
private
+ def match_versions(node)
+ begin
+ platform, version = node[:platform].to_s, node[:platform_version].to_s
+ return nil unless @values.key?(platform)
+ node_version = Chef::Version::Platform.new(version)
+ key_matches = []
+ keys = @values[platform].keys
+ keys.each do |k|
+ begin
+ if Chef::VersionConstraint.new(k).include?(node_version)
+ key_matches << k
+ end
+ rescue Chef::Exceptions::InvalidVersionConstraint => e
+ Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint."
+ Chef::Log.debug(e)
+ end
+ end
+ return @values[platform][version] if key_matches.include?(version)
+ case key_matches.length
+ when 0
+ return nil
+ when 1
+ return @values[platform][key_matches.first]
+ else
+ raise "Multiple matches detected for #{platform} with values #{@values}. The matches are: #{key_matches}"
+ end
+ rescue Chef::Exceptions::InvalidCookbookVersion => e
+ # Lets not break because someone passes a weird string like 'default' :)
+ Chef::Log.debug(e)
+ Chef::Log.debug "InvalidCookbookVersion exceptions are common and expected here: the generic constraint matcher attempted to match something which is not a constraint. Moving on to next version or constraint"
+ return nil
+ rescue Chef::Exceptions::InvalidPlatformVersion => e
+ Chef::Log.debug "Caught InvalidPlatformVersion, this means that Chef::Version::Platform does not know how to turn #{node_version} into an x.y.z format"
+ Chef::Log.debug(e)
+ return nil
+ end
+ end
+
def set(platforms, value)
if platforms.to_s == 'default'
@values["default"] = value
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index 23cfbd558c..3282320b8c 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -85,6 +85,20 @@ class Chef
resource = build_resource(type, name, created_at, &resource_attrs_block)
+ # Some resources (freebsd_package) can be invoked with multiple names
+ # (package || freebsd_package).
+ # https://github.com/opscode/chef/issues/1773
+ # For these resources we want to make sure
+ # their key in resource collection is same as the name they are declared
+ # as. Since this might be a breaking change for resources that define
+ # customer to_s methods, we are working around the issue by letting
+ # resources know of their created_as_type until this issue is fixed in
+ # Chef 12:
+ # https://github.com/opscode/chef/issues/1817
+ if resource.respond_to?(:created_as_type=)
+ resource.created_as_type = type
+ end
+
run_context.resource_collection.insert(resource)
resource
end
diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb
index f722b5dc38..120eb2a4ae 100644
--- a/lib/chef/encrypted_data_bag_item.rb
+++ b/lib/chef/encrypted_data_bag_item.rb
@@ -128,7 +128,7 @@ class Chef::EncryptedDataBagItem
def self.load_secret(path=nil)
path ||= Chef::Config[:encrypted_data_bag_secret]
if !path
- raise ArgumentError, "No secret specified to load_secret and no secret found at #{Chef::Config.platform_specific_path('/etc/chef/encrypted_data_bag_secret')}"
+ raise ArgumentError, "No secret specified and no secret found at #{Chef::Config.platform_specific_path('/etc/chef/encrypted_data_bag_secret')}"
end
secret = case path
when /^\w+:\/\//
diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb
index 97a166b932..86b99cc284 100644
--- a/lib/chef/encrypted_data_bag_item/decryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/decryptor.rb
@@ -152,7 +152,7 @@ class Chef::EncryptedDataBagItem
d = OpenSSL::Cipher.new(algorithm)
d.decrypt
# We must set key before iv: https://bugs.ruby-lang.org/issues/8221
- d.key = Digest::SHA256.digest(key)
+ d.key = OpenSSL::Digest::SHA256.digest(key)
d.iv = iv
d
end
diff --git a/lib/chef/encrypted_data_bag_item/encryptor.rb b/lib/chef/encrypted_data_bag_item/encryptor.rb
index 673b52a3c3..034413c1bd 100644
--- a/lib/chef/encrypted_data_bag_item/encryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/encryptor.rb
@@ -102,7 +102,7 @@ class Chef::EncryptedDataBagItem
encryptor = OpenSSL::Cipher.new(algorithm)
encryptor.encrypt
# We must set key before iv: https://bugs.ruby-lang.org/issues/8221
- encryptor.key = Digest::SHA256.digest(key)
+ encryptor.key = OpenSSL::Digest::SHA256.digest(key)
@iv ||= encryptor.random_iv
encryptor.iv = @iv
encryptor
@@ -125,6 +125,10 @@ class Chef::EncryptedDataBagItem
def serialized_data
FFI_Yajl::Encoder.encode(:json_wrapper => plaintext_data)
end
+
+ def self.encryptor_keys
+ %w( encrypted_data iv version cipher )
+ end
end
class Version2Encryptor < Version1Encryptor
@@ -149,6 +153,10 @@ class Chef::EncryptedDataBagItem
Base64.encode64(raw_hmac)
end
end
+
+ def self.encryptor_keys
+ super + %w( hmac )
+ end
end
class Version3Encryptor < Version1Encryptor
@@ -207,6 +215,10 @@ class Chef::EncryptedDataBagItem
end
end
+ def self.encryptor_keys
+ super + %w( auth_tag )
+ end
+
end
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index bd6d887884..23e223f204 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -83,6 +83,7 @@ class Chef
class RequestedUIDUnavailable < RuntimeError; end
class InvalidHomeDirectory < ArgumentError; end
class DsclCommandFailed < RuntimeError; end
+ class PlistUtilCommandFailed < RuntimeError; end
class UserIDNotFound < ArgumentError; end
class GroupIDNotFound < ArgumentError; end
class ConflictingMembersInGroup < ArgumentError; end
@@ -132,6 +133,8 @@ class Chef
# Version constraints are not allowed in chef-solo
class IllegalVersionConstraint < NotImplementedError; end
+ class MetadataNotValid < StandardError; end
+
# File operation attempted but no permissions to perform it
class InsufficientPermissions < RuntimeError; end
@@ -344,5 +347,7 @@ class Chef
class EncodeError < RuntimeError; end
class ParseError < RuntimeError; end
end
+
+ class InvalidSearchQuery < ArgumentError; end
end
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
index cfee8a2151..aa5eb8485d 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -56,7 +56,7 @@ class Chef
# * could be no read on the node
error_description.section("Authorization Error",<<-E)
This client is not authorized to read some of the information required to
-access its coobooks (HTTP 403).
+access its cookbooks (HTTP 403).
To access its cookbooks, a client needs to be able to read its environment and
all of the cookbooks in its expanded run list.
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
index b9a82499ed..f0f5151dbd 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -71,6 +71,20 @@ class Chef
end
Chef::Log.debug("---- End HTTP Status/Header Data ----")
+ # For non-400's, log the request and response bodies
+ if !response.code || !response.code.start_with?('2')
+ if response.body
+ Chef::Log.debug("---- HTTP Response Body ----")
+ Chef::Log.debug(response.body)
+ Chef::Log.debug("---- End HTTP Response Body -----")
+ end
+ if req_body
+ Chef::Log.debug("---- HTTP Request Body ----")
+ Chef::Log.debug(req_body)
+ Chef::Log.debug("---- End HTTP Request Body ----")
+ end
+ end
+
yield response if block_given?
# http_client.request may not have the return signature we want, so
# force the issue:
diff --git a/lib/chef/http/json_output.rb b/lib/chef/http/json_output.rb
index ae164c6aed..069eb6a87f 100644
--- a/lib/chef/http/json_output.rb
+++ b/lib/chef/http/json_output.rb
@@ -26,6 +26,9 @@ class Chef
# Middleware that takes an HTTP response, parses it as JSON if possible.
class JSONOutput
+ attr_accessor :raw_output
+ attr_accessor :inflate_json_class
+
def initialize(opts={})
@raw_output = opts[:raw_output]
@inflate_json_class = opts[:inflate_json_class]
@@ -44,13 +47,15 @@ class Chef
# needed to keep conditional get stuff working correctly.
return [http_response, rest_request, return_value] if return_value == false
if http_response['content-type'] =~ /json/
- if @raw_output
+ if http_response.body.nil?
+ return_value = nil
+ elsif raw_output
return_value = http_response.body.to_s
else
- if @inflate_json_class
+ if inflate_json_class
return_value = Chef::JSONCompat.from_json(http_response.body.chomp)
else
- return_value = Chef::JSONCompat.from_json(http_response.body.chomp, :create_additions => false)
+ return_value = Chef::JSONCompat.parse(http_response.body.chomp)
end
end
[http_response, rest_request, return_value]
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index ddccfe5fcb..e92d5c36ae 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -101,7 +101,7 @@ class Chef
def to_json(obj, opts = nil)
begin
FFI_Yajl::Encoder.encode(obj, opts)
- rescue FFI_Yajl::EndodeError => e
+ rescue FFI_Yajl::EncodeError => e
raise Chef::Exceptions::JSON::EncodeError, e.message
end
end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 038ab61715..6421384f01 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -20,7 +20,7 @@
require 'forwardable'
require 'chef/version'
require 'mixlib/cli'
-require 'chef/config_fetcher'
+require 'chef/workstation_config_loader'
require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/path_sanity'
require 'chef/knife/core/subcommand_loader'
@@ -159,6 +159,27 @@ class Chef
end
end
+ # Shared with subclasses
+ @@chef_config_dir = nil
+
+ def self.load_config(explicit_config_file)
+ config_loader = WorkstationConfigLoader.new(explicit_config_file, Chef::Log)
+ Chef::Log.debug("Using configuration from #{config_loader.config_location}")
+ config_loader.load
+
+ ui.warn("No knife configuration file found") if config_loader.no_config_found?
+ @@chef_config_dir = config_loader.chef_config_dir
+
+ config_loader
+ rescue Exceptions::ConfigurationError => e
+ ui.error(ui.color("CONFIGURATION ERROR:", :red) + e.message)
+ exit 1
+ end
+
+ def self.chef_config_dir
+ @@chef_config_dir
+ end
+
# Run knife for the given +args+ (ARGV), adding +options+ to the list of
# CLI options that the subcommand knows how to handle.
# ===Arguments
@@ -166,6 +187,16 @@ class Chef
# options::: A Mixlib::CLI option parser hash. These +options+ are how
# subcommands know about global knife CLI options
def self.run(args, options={})
+ # Fallback debug logging. Normally the logger isn't configured until we
+ # read the config, but this means any logging that happens before the
+ # config file is read may be lost. If the KNIFE_DEBUG variable is set, we
+ # setup the logger for debug logging to stderr immediately to catch info
+ # from early in the setup process.
+ if ENV['KNIFE_DEBUG']
+ Chef::Log.init($stderr)
+ Chef::Log.level(:debug)
+ end
+
load_commands
subcommand_class = subcommand_class_from(args)
subcommand_class.options = options.merge!(subcommand_class.options)
@@ -239,40 +270,12 @@ class Chef
exit 10
end
- def self.working_directory
- a = if Chef::Platform.windows?
- ENV['CD']
- else
- ENV['PWD']
- end || Dir.pwd
-
- a
- end
-
def self.reset_config_path!
@@chef_config_dir = nil
end
reset_config_path!
-
- # search upward from current_dir until .chef directory is found
- def self.chef_config_dir
- if @@chef_config_dir.nil? # share this with subclasses
- @@chef_config_dir = false
- full_path = working_directory.split(File::SEPARATOR)
- (full_path.length - 1).downto(0) do |i|
- candidate_directory = File.join(full_path[0..i] + [".chef" ])
- if File.exist?(candidate_directory) && File.directory?(candidate_directory)
- @@chef_config_dir = candidate_directory
- break
- end
- end
- end
- @@chef_config_dir
- end
-
-
public
# Create a new instance of the current class configured for the given
@@ -322,39 +325,6 @@ class Chef
config_file_settings
end
- def self.config_fetcher(candidate_config)
- Chef::ConfigFetcher.new(candidate_config, Chef::Config.config_file_jail)
- end
-
- def self.locate_config_file
- candidate_configs = []
-
- # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
- if ENV['KNIFE_HOME']
- candidate_configs << File.join(ENV['KNIFE_HOME'], 'knife.rb')
- end
- # Look for $PWD/knife.rb
- if Dir.pwd
- candidate_configs << File.join(Dir.pwd, 'knife.rb')
- end
- # Look for $UPWARD/.chef/knife.rb
- if chef_config_dir
- candidate_configs << File.join(chef_config_dir, 'knife.rb')
- end
- # Look for $HOME/.chef/knife.rb
- if ENV['HOME']
- candidate_configs << File.join(ENV['HOME'], '.chef', 'knife.rb')
- end
-
- candidate_configs.each do | candidate_config |
- fetcher = config_fetcher(candidate_config)
- if !fetcher.config_missing?
- return candidate_config
- end
- end
- return nil
- end
-
# Apply Config in this order:
# defaults from mixlib-cli
# settings from config file, via Chef::Config[:knife]
@@ -386,6 +356,8 @@ class Chef
Chef::Config[:log_level] = :debug
end
+ Chef::Config[:log_level] = :debug if ENV['KNIFE_DEBUG']
+
Chef::Config[:node_name] = config[:node_name] if config[:node_name]
Chef::Config[:client_key] = config[:client_key] if config[:client_key]
Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
@@ -416,70 +388,13 @@ class Chef
end
def configure_chef
- if !config[:config_file]
- located_config_file = self.class.locate_config_file
- config[:config_file] = located_config_file if located_config_file
- end
-
- # Don't try to load a knife.rb if it wasn't specified.
- if config[:config_file]
- Chef::Config.config_file = config[:config_file]
- fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
- if fetcher.config_missing?
- ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!")
- exit 1
- end
- Chef::Log.debug("Using configuration from #{config[:config_file]}")
- read_config(fetcher.read_config, config[:config_file])
- else
- # ...but do log a message if no config was found.
- Chef::Config[:color] = config[:color]
- ui.warn("No knife configuration file found")
- end
+ config_loader = self.class.load_config(config[:config_file])
+ config[:config_file] = config_loader.config_location
merge_configs
apply_computed_config
end
- def read_config(config_content, config_file_path)
- Chef::Config.from_string(config_content, config_file_path)
- rescue SyntaxError => e
- ui.error "You have invalid ruby syntax in your config file #{config_file_path}"
- ui.info(ui.color(e.message, :red))
- if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
- line = file_line[/:([\d]+)$/, 1].to_i
- highlight_config_error(config_file_path, line)
- end
- exit 1
- rescue Exception => e
- ui.error "You have an error in your config file #{config_file_path}"
- ui.info "#{e.class.name}: #{e.message}"
- filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
- filtered_trace.each {|line| ui.msg(" " + ui.color(line, :red))}
- if !filtered_trace.empty?
- line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
- highlight_config_error(config_file_path, line_nr.to_i)
- end
-
- exit 1
- end
-
- def highlight_config_error(file, line)
- config_file_lines = []
- IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
- if line == 1
- lines = config_file_lines[0..3]
- lines[0] = ui.color(lines[0], :red)
- else
- lines = config_file_lines[Range.new(line - 2, line)]
- lines[1] = ui.color(lines[1], :red)
- end
- ui.msg ""
- ui.msg ui.color(" # #{file}", :white)
- lines.each {|l| ui.msg(l)}
- ui.msg ""
- end
-
def show_usage
stdout.puts("USAGE: " + self.opt_parser.to_s)
end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index d3d45bad4b..36a0fc1e47 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -94,11 +94,20 @@ class Chef
:description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode",
:proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
+ # DEPR: Remove this option in Chef 13
option :distro,
:short => "-d DISTRO",
:long => "--distro DISTRO",
- :description => "Bootstrap a distro using a template",
- :default => "chef-full"
+ :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.",
+ :proc => Proc.new { |v|
+ Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use -t / --bootstrap-template option instead.")
+ v
+ }
+
+ option :bootstrap_template,
+ :short => "-t TEMPLATE",
+ :long => "--bootstrap-template TEMPLATE",
+ :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates."
option :use_sudo,
:long => "--sudo",
@@ -110,10 +119,14 @@ class Chef
:description => "Execute the bootstrap via sudo with password",
:boolean => false
+ # DEPR: Remove this option in Chef 13
option :template_file,
:long => "--template-file TEMPLATE",
- :description => "Full path to location of template to use",
- :default => false
+ :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.",
+ :proc => Proc.new { |v|
+ Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.")
+ v
+ }
option :run_list,
:short => "-r RUN_LIST",
@@ -141,7 +154,8 @@ class Chef
:proc => Proc.new { |h|
Chef::Config[:knife][:hints] ||= Hash.new
name, path = h.split("=")
- Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new }
+ Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
+ }
option :secret,
:short => "-s SECRET",
@@ -174,53 +188,75 @@ class Chef
:description => "Add options to curl when install chef-client",
:proc => Proc.new { |co| Chef::Config[:knife][:bootstrap_curl_options] = co }
- def find_template(template=nil)
- # Are we bootstrapping using an already shipped template?
- if config[:template_file]
- bootstrap_files = config[:template_file]
- else
- bootstrap_files = []
- bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{config[:distro]}.erb")
- bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{config[:distro]}.erb") if Knife.chef_config_dir
- bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{config[:distro]}.erb") if ENV['HOME']
- bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{config[:distro]}.erb"))
- bootstrap_files.flatten!
+ option :node_ssl_verify_mode,
+ :long => "--node-ssl-verify-mode [peer|none]",
+ :description => "Whether or not to verify the SSL cert for all HTTPS requests.",
+ :proc => Proc.new { |v|
+ valid_values = ["none", "peer"]
+ unless valid_values.include?(v)
+ raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}"
+ end
+ }
+
+ option :node_verify_api_cert,
+ :long => "--[no-]node-verify-api-cert",
+ :description => "Verify the SSL cert for HTTPS requests to the Chef server API.",
+ :boolean => true
+
+ def bootstrap_template
+ # For some reason knife.merge_configs doesn't pick up the default values from
+ # Chef::Config[:knife][:bootstrap_template] unless Chef::Config[:knife][:bootstrap_template]
+ # is forced to pick up the values before calling merge_configs.
+ # We therefore have Chef::Config[:knife][:bootstrap_template] to pick up the defaults
+ # if no option is specified.
+ config[:bootstrap_template] || config[:distro] || config[:template_file] || Chef::Config[:knife][:bootstrap_template]
+ end
+
+ def find_template
+ template = bootstrap_template
+
+ # Use the template directly if it's a path to an actual file
+ if File.exists?(template)
+ Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
+ return template
+
end
- template = Array(bootstrap_files).find do |bootstrap_template|
+ # Otherwise search the template directories until we find the right one
+ bootstrap_files = []
+ bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{template}.erb")
+ bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
+ bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{template}.erb") if ENV['HOME']
+ bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
+ bootstrap_files.flatten!
+
+ template_file = Array(bootstrap_files).find do |bootstrap_template|
Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
File.exists?(bootstrap_template)
end
- unless template
- ui.info("Can not find bootstrap definition for #{config[:distro]}")
+ unless template_file
+ ui.info("Can not find bootstrap definition for #{template}")
raise Errno::ENOENT
end
- Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
+ Chef::Log.debug("Found bootstrap template in #{File.dirname(template_file)}")
- template
+ template_file
end
- def render_template(template=nil)
+ def render_template
+ template_file = find_template
+ template = IO.read(template_file).chomp
context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config)
Erubis::Eruby.new(template).evaluate(context)
end
- def read_template
- IO.read(@template_file).chomp
- end
-
def run
validate_name_args!
- warn_chef_config_secret_key
- @template_file = find_template(config[:bootstrap_template])
@node_name = Array(@name_args).first
- # back compat--templates may use this setting:
- config[:server_name] = @node_name
$stdout.sync = true
-
ui.info("Connecting to #{ui.color(@node_name, :bold)}")
begin
@@ -272,7 +308,7 @@ class Chef
end
def ssh_command
- command = render_template(read_template)
+ command = render_template
if config[:use_sudo]
command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S #{command}" : "sudo #{command}"
@@ -281,28 +317,6 @@ class Chef
command
end
- def warn_chef_config_secret_key
- unless Chef::Config[:encrypted_data_bag_secret].nil?
- ui.warn "* " * 40
- ui.warn(<<-WARNING)
-Specifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
-entry in 'knife.rb' is deprecated. Please see CHEF-4011 for more details. You
-can supress this warning and still distribute the secret key to all bootstrapped
-machines by adding the following to your 'knife.rb' file:
-
- knife[:secret_file] = "/path/to/your/secret"
-
-If you would like to selectively distribute a secret key during bootstrap
-please use the '--secret' or '--secret-file' options of this command instead.
-
-#{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
-behavior will be removed and any 'encrypted_data_bag_secret' entries in
-'knife.rb' will be ignored completely.
-WARNING
- ui.warn "* " * 40
- end
- end
-
end
end
end
diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb
index ab2aa7a7f1..bb84340c05 100644
--- a/lib/chef/knife/bootstrap/archlinux-gems.erb
+++ b/lib/chef/knife/bootstrap/archlinux-gems.erb
@@ -6,7 +6,7 @@ if [ ! -f /usr/bin/chef-client ]; then
pacman -S --noconfirm ruby ntp base-devel
ntpdate -u pool.ntp.org
gem install ohai --no-user-install --no-document --verbose
- gem install chef --no-user-install --no-document --verbose <%= bootstrap_version_string %>
+ gem install chef --no-user-install --no-document --verbose <%= Chef::VERSION %>
fi
mkdir -p /etc/chef
diff --git a/lib/chef/knife/bootstrap/centos5-gems.erb b/lib/chef/knife/bootstrap/centos5-gems.erb
deleted file mode 100644
index 6aacc47179..0000000000
--- a/lib/chef/knife/bootstrap/centos5-gems.erb
+++ /dev/null
@@ -1,62 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- tmp_dir=$(mktemp -d) || exit 1
- pushd "$tmp_dir"
-
- yum install -y wget
-
- wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm
- rpm -Uvh epel-release-5-4.noarch.rpm
- wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://rpm.aegisco.com/aegisco/rhel/aegisco-rhel.rpm
- rpm -Uvh aegisco-rhel.rpm
-
- yum install -y ruby ruby-devel gcc gcc-c++ automake autoconf make
-
- wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz -O - | tar zxf -
- (cd rubygems-1.6.2 && ruby setup.rb --no-format-executable)
-
- popd
- rm -r "$tmp_dir"
-fi
-
-gem update --system
-gem update
-gem install ohai --no-rdoc --no-ri --verbose
-gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
-
-mkdir -p /etc/chef
-
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb
index c953a7e433..a4e85b9d67 100644
--- a/lib/chef/knife/bootstrap/chef-full.erb
+++ b/lib/chef/knife/bootstrap/chef-full.erb
@@ -23,7 +23,6 @@ exists() {
<%= knife_config[:bootstrap_install_command] %>
<% else %>
install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
- version_string="-v <%= chef_version %>"
if ! exists /usr/bin/chef-client; then
echo "Installing Chef Client..."
if exists wget; then
diff --git a/lib/chef/knife/bootstrap/fedora13-gems.erb b/lib/chef/knife/bootstrap/fedora13-gems.erb
deleted file mode 100644
index 0aabc31085..0000000000
--- a/lib/chef/knife/bootstrap/fedora13-gems.erb
+++ /dev/null
@@ -1,44 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-yum install -y ruby ruby-devel gcc gcc-c++ automake autoconf rubygems make
-
-gem update --system
-gem update
-gem install ohai --no-rdoc --no-ri --verbose
-gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
-
-mkdir -p /etc/chef
-
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
deleted file mode 100644
index 4549b94d2b..0000000000
--- a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
+++ /dev/null
@@ -1,53 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- apt-get install -y wget
- echo "chef chef/chef_server_url string <%= @chef_config[:chef_server_url] %>" | debconf-set-selections
- [ -f /etc/apt/sources.list.d/opscode.list ] || echo "deb http://apt.opscode.com <%= chef_version.to_f == 0.10 ? "lucid-0.10" : "lucid" %> main" > /etc/apt/sources.list.d/opscode.list
- wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>-O- http://apt.opscode.com/packages@opscode.com.gpg.key | apt-key add -
-fi
-apt-get update
-apt-get install -y chef
-
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
-EOP
-<% end -%>
-<% end -%>
-
-<% unless @chef_config[:validation_client_name] == "chef-validator" -%>
-[ `grep -qx "validation_client_name \"<%= @chef_config[:validation_client_name] %>\"" /etc/chef/client.rb` ] || echo "validation_client_name \"<%= @chef_config[:validation_client_name] %>\"" >> /etc/chef/client.rb
-<% end -%>
-
-<% if @config[:chef_node_name] %>
-[ `grep -qx "node_name \"<%= @config[:chef_node_name] %>\"" /etc/chef/client.rb` ] || echo "node_name \"<%= @config[:chef_node_name] %>\"" >> /etc/chef/client.rb
-<% end -%>
-
-<% if knife_config[:bootstrap_proxy] %>
-echo 'http_proxy "knife_config[:bootstrap_proxy]"' >> /etc/chef/client.rb
-echo 'https_proxy "knife_config[:bootstrap_proxy]"' >> /etc/chef/client.rb
-<% end -%>
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
deleted file mode 100644
index 62ff7c857e..0000000000
--- a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- apt-get update
- apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras
- wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz -O - | tar zxf -
- (cd rubygems-1.6.2 && ruby setup.rb --no-format-executable)
-fi
-
-gem update --no-rdoc --no-ri
-gem install ohai --no-rdoc --no-ri --verbose
-gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
-
-mkdir -p /etc/chef
-
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
deleted file mode 100644
index 8e9c6583d0..0000000000
--- a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
+++ /dev/null
@@ -1,46 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- aptitude update
- aptitude install -y ruby ruby1.8-dev build-essential wget libruby1.8 rubygems
-fi
-
-gem update --no-rdoc --no-ri
-gem install ohai --no-rdoc --no-ri --verbose
-gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
-
-mkdir -p /etc/chef
-
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb
index 645b1728e6..de6e21d0d2 100644
--- a/lib/chef/knife/cookbook_site_download.rb
+++ b/lib/chef/knife/cookbook_site_download.rb
@@ -58,7 +58,7 @@ class Chef
private
def cookbooks_api_url
- 'http://cookbooks.opscode.com/api/v1/cookbooks'
+ 'https://supermarket.getchef.com/api/v1/cookbooks'
end
def current_cookbook_data
diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb
index fe83b71388..6fcf7e6064 100644
--- a/lib/chef/knife/cookbook_site_list.rb
+++ b/lib/chef/knife/cookbook_site_list.rb
@@ -41,7 +41,7 @@ class Chef
end
def get_cookbook_list(items=10, start=0, cookbook_collection={})
- cookbooks_url = "http://cookbooks.opscode.com/api/v1/cookbooks?items=#{items}&start=#{start}"
+ cookbooks_url = "https://supermarket.getchef.com/api/v1/cookbooks?items=#{items}&start=#{start}"
cr = noauth_rest.get_rest(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb
index b636276cba..ec4d196ee3 100644
--- a/lib/chef/knife/cookbook_site_search.rb
+++ b/lib/chef/knife/cookbook_site_search.rb
@@ -29,7 +29,7 @@ class Chef
end
def search_cookbook(query, items=10, start=0, cookbook_collection={})
- cookbooks_url = "http://cookbooks.opscode.com/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
+ cookbooks_url = "https://supermarket.getchef.com/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
cr = noauth_rest.get_rest(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
index 71bbb00abc..e5c56f654d 100644
--- a/lib/chef/knife/cookbook_site_share.rb
+++ b/lib/chef/knife/cookbook_site_share.rb
@@ -99,7 +99,7 @@ class Chef
end
def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
- uri = "http://cookbooks.opscode.com/api/v1/cookbooks"
+ uri = "https://supermarket.getchef.com/api/v1/cookbooks"
category_string = Chef::JSONCompat.to_json({ 'category'=>cookbook_category })
diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb
index d15098e915..c520c00621 100644
--- a/lib/chef/knife/cookbook_site_show.rb
+++ b/lib/chef/knife/cookbook_site_show.rb
@@ -31,14 +31,14 @@ class Chef
def get_cookbook_data
case @name_args.length
when 1
- noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
+ noauth_rest.get_rest("https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}")
when 2
- noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
+ noauth_rest.get_rest("https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
end
end
def get_cookbook_list(items=10, start=0, cookbook_collection={})
- cookbooks_url = "http://cookbooks.opscode.com/api/v1/cookbooks?items=#{items}&start=#{start}"
+ cookbooks_url = "https://supermarket.getchef.com/api/v1/cookbooks?items=#{items}&start=#{start}"
cr = noauth_rest.get_rest(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb
index a2828549a0..f095885f15 100644
--- a/lib/chef/knife/cookbook_site_unshare.rb
+++ b/lib/chef/knife/cookbook_site_unshare.rb
@@ -41,7 +41,7 @@ class Chef
confirm "Do you really want to unshare the cookbook #{@cookbook_name}"
begin
- rest.delete_rest "http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}"
+ rest.delete_rest "https://supermarket.getchef.com/api/v1/cookbooks/#{@name_args[0]}"
rescue Net::HTTPServerException => e
raise e unless e.message =~ /Forbidden/
ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 742ef226a3..9fa6dcc46f 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -34,14 +34,6 @@ class Chef
@chef_config = chef_config
end
- def bootstrap_version_string
- if @config[:prerelease]
- "--prerelease"
- else
- "--version #{chef_version}"
- end
- end
-
def bootstrap_environment
@chef_config[:environment] || '_default'
end
@@ -52,10 +44,12 @@ class Chef
def encrypted_data_bag_secret
knife_config[:secret] || begin
- if knife_config[:secret_file] && File.exist?(knife_config[:secret_file])
- IO.read(File.expand_path(knife_config[:secret_file]))
- elsif @chef_config[:encrypted_data_bag_secret] && File.exist?(@chef_config[:encrypted_data_bag_secret])
- IO.read(File.expand_path(@chef_config[:encrypted_data_bag_secret]))
+ secret_file_path = knife_config[:secret_file]
+ expanded_secret_file_path = File.expand_path(secret_file_path.to_s)
+ if secret_file_path && File.exist?(expanded_secret_file_path)
+ IO.read(expanded_secret_file_path)
+ else
+ nil
end
end
end
@@ -72,6 +66,36 @@ CONFIG
client_rb << "# Using default node name (fqdn)\n"
end
+ # We configure :verify_api_cert only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if !@config[:node_verify_api_cert].nil? || knife_config.has_key?(:verify_api_cert)
+ value = @config[:node_verify_api_cert].nil? ? knife_config[:verify_api_cert] : @config[:node_verify_api_cert]
+ client_rb << %Q{verify_api_cert #{value}\n}
+ end
+
+ # We configure :ssl_verify_mode only when it's overridden on the CLI
+ # or when specified in the knife config.
+ if @config[:node_ssl_verify_mode] || knife_config.has_key?(:ssl_verify_mode)
+ value = case @config[:node_ssl_verify_mode]
+ when "peer"
+ :verify_peer
+ when "none"
+ :verify_none
+ when nil
+ knife_config[:ssl_verify_mode]
+ else
+ nil
+ end
+
+ if value
+ client_rb << %Q{ssl_verify_mode :#{value}\n}
+ end
+ end
+
+ if @config[:ssl_verify_mode]
+ client_rb << %Q{ssl_verify_mode :#{knife_config[:ssl_verify_mode]}\n}
+ end
+
if knife_config[:bootstrap_proxy]
client_rb << %Q{http_proxy "#{knife_config[:bootstrap_proxy]}"\n}
client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n}
@@ -93,7 +117,7 @@ CONFIG
client_path = @chef_config[:chef_client_path] || 'chef-client'
s = "#{client_path} -j /etc/chef/first-boot.json"
s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2
- s << " -E #{bootstrap_environment}" if chef_version.to_f != 0.9 # only use the -E option on Chef 0.10+
+ s << " -E #{bootstrap_environment}"
s
end
@@ -102,29 +126,26 @@ CONFIG
end
#
- # This function is used by older bootstrap templates other than chef-full
- # and potentially by custom templates as well hence it's logic needs to be
- # preserved for backwards compatibility reasons until we hit Chef 12.
- def chef_version
- knife_config[:bootstrap_version] || Chef::VERSION
- end
-
- #
# chef version string to fetch the latest current version from omnitruck
# If user is on X.Y.Z bootstrap will use the latest X release
# X here can be 10 or 11
def latest_current_chef_version_string
- chef_version_string = if knife_config[:bootstrap_version]
- knife_config[:bootstrap_version]
+ installer_version_string = nil
+ if @config[:prerelease]
+ installer_version_string = "-p"
else
- Chef::VERSION.split(".").first
- end
+ chef_version_string = if knife_config[:bootstrap_version]
+ knife_config[:bootstrap_version]
+ else
+ Chef::VERSION.split(".").first
+ end
- installer_version_string = ["-v", chef_version_string]
+ installer_version_string = ["-v", chef_version_string]
- # If bootstrapping a pre-release version add -p to the installer string
- if chef_version_string.split(".").length > 3
- installer_version_string << "-p"
+ # If bootstrapping a pre-release version add -p to the installer string
+ if chef_version_string.split(".").length > 3
+ installer_version_string << "-p"
+ end
end
installer_version_string.join(" ")
diff --git a/lib/chef/knife/core/node_editor.rb b/lib/chef/knife/core/node_editor.rb
index 073492197c..fe14e18d9d 100644
--- a/lib/chef/knife/core/node_editor.rb
+++ b/lib/chef/knife/core/node_editor.rb
@@ -42,8 +42,8 @@ class Chef
end
def updated?
- pristine_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(node), :create_additions => false)
- updated_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@updated_node), :create_additions => false)
+ pristine_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(node))
+ updated_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@updated_node))
unless pristine_copy == updated_copy
updated_properties = %w{name normal chef_environment run_list default override automatic}.reject do |key|
pristine_copy[key] == updated_copy[key]
@@ -107,4 +107,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index c4d7d73b00..0007480ea2 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -195,8 +195,8 @@ class Chef
# We wouldn't have to do these shenanigans if all the editable objects
# implemented to_hash, or if to_json against a hash returned a string
# with stable key order.
- object_parsed_again = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(object), :create_additions => false)
- output_parsed_again = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(output), :create_additions => false)
+ object_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(object))
+ output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output))
if object_parsed_again != output_parsed_again
output.save
self.msg("Saved #{output}")
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index bc020c0445..34d12168b6 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -71,6 +71,11 @@ class Chef
:long => "--query QUERY",
:description => "The search query; useful to protect queries starting with -"
+ option :filter_result,
+ :short => "-f FILTER",
+ :long => "--filter-result FILTER",
+ :description => "Only bring back specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\""
+
def run
read_cli_args
fuzzify_query
@@ -79,7 +84,6 @@ class Chef
ui.use_presenter Knife::Core::NodePresenter
end
-
q = Chef::Search::Query.new
escaped_query = URI.escape(@query,
Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
@@ -87,14 +91,26 @@ class Chef
result_items = []
result_count = 0
- rows = config[:rows]
- start = config[:start]
+ search_args = Hash.new
+ search_args[:sort] = config[:sort]
+ search_args[:start] = config[:start]
+ search_args[:rows] = config[:rows]
+ if config[:filter_result]
+ search_args[:filter_result] = create_result_filter(config[:filter_result])
+ elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?)
+ search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute])
+ end
+
begin
- q.search(@type, escaped_query, config[:sort], start, rows) do |item|
- formatted_item = format_for_display(item)
- # if formatted_item.respond_to?(:has_key?) && !formatted_item.has_key?('id')
- # formatted_item['id'] = item.has_key?('id') ? item['id'] : item.name
- # end
+ q.search(@type, escaped_query, search_args) do |item|
+ formatted_item = Hash.new
+ if item.is_a?(Hash)
+ # doing a little magic here to set the correct name
+ formatted_item[item["data"]["__display_name"]] = item["data"]
+ formatted_item[item["data"]["__display_name"]].delete("__display_name")
+ else
+ formatted_item = format_for_display(item)
+ end
result_items << formatted_item
result_count += 1
end
@@ -149,10 +165,38 @@ class Chef
end
end
+ # This method turns a set of key value pairs in a string into the appropriate data structure that the
+ # chef-server search api is expecting.
+ # expected input is in the form of:
+ # -f "return_var1=path.to.attribute, return_var2=shorter.path"
+ #
+ # a more concrete example might be:
+ # -f "env=chef_environment, ruby_platform=languages.ruby.platform"
+ #
+ # The end result is a hash where the key is a symbol in the hash (the return variable)
+ # and the path is an array with the path elements as strings (in order)
+ # See lib/chef/search/query.rb for more examples of this.
+ def create_result_filter(filter_string)
+ final_filter = Hash.new
+ filter_string.gsub!(" ", "")
+ filters = filter_string.split(",")
+ filters.each do |f|
+ return_id, attr_path = f.split("=")
+ final_filter[return_id.to_sym] = attr_path.split(".")
+ end
+ return final_filter
+ end
+
+ def create_result_filter_from_attributes(filter_array)
+ final_filter = Hash.new
+ filter_array.each do |f|
+ final_filter[f] = f.split(".")
+ end
+ # adding magic filter so we can actually pull the name as before
+ final_filter["__display_name"] = [ "name" ]
+ return final_filter
+ end
+
end
end
end
-
-
-
-
diff --git a/lib/chef/knife/serve.rb b/lib/chef/knife/serve.rb
index 15994590cd..870177e0be 100644
--- a/lib/chef/knife/serve.rb
+++ b/lib/chef/knife/serve.rb
@@ -33,7 +33,7 @@ class Chef
def run
server = Chef::LocalMode.chef_zero_server
begin
- output "Serving files from:\n#{server.options[:data_store].chef_fs.fs_description}"
+ output "Serving files from:\n#{Chef::LocalMode.chef_fs.fs_description}"
server.stop
server.start(stdout) # to print header
ensure
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index ad1968a6a6..e66acb6b66 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -52,9 +52,10 @@ class Chef
require 'chef/chef_fs/chef_fs_data_store'
require 'chef/chef_fs/config'
- chef_fs = Chef::ChefFS::Config.new.local_fs
- chef_fs.write_pretty_json = true
- data_store = Chef::ChefFS::ChefFSDataStore.new(chef_fs)
+ @chef_fs = Chef::ChefFS::Config.new.local_fs
+ @chef_fs.write_pretty_json = true
+ data_store = Chef::ChefFS::ChefFSDataStore.new(@chef_fs)
+ data_store = ChefZero::DataStore::V1ToV2Adapter.new(data_store, 'chef')
server_options = {}
server_options[:data_store] = data_store
server_options[:log_level] = Chef::Log.level
@@ -62,7 +63,7 @@ class Chef
server_options[:port] = parse_port(Chef::Config.chef_zero.port)
@chef_zero_server = ChefZero::Server.new(server_options)
@chef_zero_server.start_background
- Chef::Log.info("Started chef-zero at #{@chef_zero_server.url} with #{chef_fs.fs_description}")
+ Chef::Log.info("Started chef-zero at #{@chef_zero_server.url} with #{@chef_fs.fs_description}")
Chef::Config.chef_server_url = @chef_zero_server.url
end
end
@@ -72,6 +73,11 @@ class Chef
@chef_zero_server
end
+ # Return the chef_fs object for the current chef-zero server.
+ def self.chef_fs
+ @chef_fs
+ end
+
# If chef_zero_server is non-nil, stop it and remove references to it.
def self.destroy_server_connectivity
if @chef_zero_server
diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb
index a8a4737758..5e3327a526 100644
--- a/lib/chef/mixin/deep_merge.rb
+++ b/lib/chef/mixin/deep_merge.rb
@@ -29,7 +29,6 @@ class Chef
class InvalidSubtractiveMerge < ArgumentError; end
-
OLD_KNOCKOUT_PREFIX = "!merge:".freeze
# Regex to match the "knockout prefix" that was used to indicate
@@ -86,8 +85,12 @@ class Chef
when Hash
if dest.kind_of?(Hash)
source.each do |src_key, src_value|
- if dest[src_key]
- dest[src_key] = deep_merge!(src_value, dest[src_key])
+ if dest.has_key? src_key
+ if dest[src_key].nil?
+ dest[src_key] = nil
+ else
+ dest[src_key] = deep_merge!(src_value, dest[src_key])
+ end
else # dest[src_key] doesn't exist so we take whatever source has
raise_if_knockout_used!(src_value)
dest[src_key] = src_value
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index 3f80290f90..82772b584a 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -20,7 +20,6 @@
require 'chef/shell_out'
require 'mixlib/shellout'
-require 'chef/config'
class Chef
module Mixin
@@ -31,32 +30,39 @@ class Chef
# Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs
# You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality
+ # we use 'en_US.UTF-8' by default because we parse localized strings in English as an API and
+ # generally must support UTF-8 unicode.
def shell_out(*command_args)
- cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
- cmd.live_stream ||= io_for_live_stream
- cmd.run_command
- cmd
+ args = command_args.dup
+ if args.last.is_a?(Hash)
+ options = args.pop.dup
+ env_key = options.has_key?(:env) ? :env : :environment
+ options[env_key] ||= {}
+ options[env_key] = options[env_key].dup
+ options[env_key]['LC_ALL'] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?('LC_ALL')
+ args << options
+ else
+ args << { :environment => { 'LC_ALL' => Chef::Config[:internal_locale] } }
+ end
+
+ shell_out_command(*args)
end
+ # call shell_out (using en_US.UTF-8) and raise errors
def shell_out!(*command_args)
- cmd= shell_out(*command_args)
+ cmd = shell_out(*command_args)
cmd.error!
cmd
end
- # environment['LC_ALL'] should be nil or what the user specified
def shell_out_with_systems_locale(*command_args)
- args = command_args.dup
- if args.last.is_a?(Hash)
- options = args.last
- env_key = options.has_key?(:env) ? :env : :environment
- options[env_key] ||= {}
- options[env_key]['LC_ALL'] ||= nil
- else
- args << { :environment => { 'LC_ALL' => nil } }
- end
+ shell_out_command(*command_args)
+ end
- shell_out(*args)
+ def shell_out_with_systems_locale!(*command_args)
+ cmd = shell_out_with_systems_locale(*command_args)
+ cmd.error!
+ cmd
end
DEPRECATED_OPTIONS =
@@ -83,6 +89,13 @@ class Chef
private
+ def shell_out_command(*command_args)
+ cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
+ cmd.live_stream ||= io_for_live_stream
+ cmd.run_command
+ cmd
+ end
+
def deprecate_option(old_option, new_option)
Chef::Log.logger.warn "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}"
end
@@ -97,3 +110,6 @@ class Chef
end
end
end
+
+# Break circular dep
+require 'chef/config'
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index edcd596341..ff118c1d3d 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
+require 'chef/platform/query_helpers'
require 'win32/api' if Chef::Platform.windows?
require 'chef/win32/api/process' if Chef::Platform.windows?
require 'chef/win32/api/error' if Chef::Platform.windows?
diff --git a/lib/chef/null_logger.rb b/lib/chef/null_logger.rb
new file mode 100644
index 0000000000..5195cc5ce2
--- /dev/null
+++ b/lib/chef/null_logger.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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.
+
+class Chef
+
+ # Null logger implementation that just ignores everything. This is used by
+ # classes that are intended to be reused outside of Chef, but need to offer
+ # logging functionality when used by other Chef code.
+ #
+ # It does not define the full interface provided by Logger, just enough to be
+ # a reasonable duck type. In particular, methods setting the log level, log
+ # device, etc., are not implemented because any code calling those methods
+ # probably expected a real logger and not this "fake" one.
+ class NullLogger
+
+ def fatal(message, &block)
+ end
+
+ def error(message, &block)
+ end
+
+ def warn(message, &block)
+ end
+
+ def info(message, &block)
+ end
+
+ def debug(message, &block)
+ end
+
+ def add(severity, message=nil, progname=nil)
+ end
+
+ def <<(message)
+ end
+
+ def fatal?
+ false
+ end
+
+ def error?
+ false
+ end
+
+ def warn?
+ false
+ end
+
+ def info?
+ false
+ end
+
+ def debug?
+ false
+ end
+
+ end
+end
diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb
index 8f494731ab..841aa1b46c 100644
--- a/lib/chef/platform.rb
+++ b/lib/chef/platform.rb
@@ -16,8 +16,9 @@
# limitations under the License.
#
-require 'chef/platform/provider_mapping'
+# Order of these headers is important: query helpers is needed by many things
require 'chef/platform/query_helpers'
+require 'chef/platform/provider_mapping'
class Chef
class Platform
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index d61298e182..7f79c38a6a 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/config'
require 'chef/log'
+require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/version_constraint/platform'
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index db7629dbcb..bdfe826944 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -19,14 +19,12 @@
require 'chef/mixin/from_file'
require 'chef/mixin/convert_to_class_name'
-require 'chef/dsl/recipe'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
class Chef
class Provider
- include Chef::DSL::Recipe
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 1be15f9f5f..c3be9746df 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -118,6 +118,12 @@ class Chef
when ENV_PATTERN
crontab << line unless cron_found
next
+ when SPECIAL_PATTERN
+ if cron_found
+ cron_found = false
+ crontab << newcron
+ next
+ end
when CRON_PATTERN
if cron_found
cron_found = false
@@ -163,6 +169,11 @@ class Chef
next
when ENV_PATTERN
next if cron_found
+ when SPECIAL_PATTERN
+ if cron_found
+ cron_found = false
+ next
+ end
when CRON_PATTERN
if cron_found
cron_found = false
diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
index 89710088d1..ed65742154 100644
--- a/lib/chef/provider/deploy/revision.rb
+++ b/lib/chef/provider/deploy/revision.rb
@@ -90,7 +90,7 @@ class Chef
def load_cache
begin
- Chef::JSONCompat.from_json(Chef::FileCache.load("revision-deploys/#{new_resource.name}"))
+ Chef::JSONCompat.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}"))
rescue Chef::Exceptions::FileNotFound
sorted_releases_from_filesystem
end
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index 525249a726..aa58fdc4a1 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -283,6 +283,7 @@ class Chef
env['GIT_SSH'] = @new_resource.ssh_wrapper if @new_resource.ssh_wrapper
run_opts[:log_tag] = @new_resource.to_s
run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
+ env.merge!(@new_resource.environment) if @new_resource.environment
run_opts[:environment] = env unless env.empty?
run_opts
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 3ec6f6f668..c39c20da67 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -39,7 +39,14 @@ class Chef
def create_group
command = "pw groupadd"
command << set_options
- member_options = set_members_options
+
+ # pw group[add|mod] -M is used to set the full membership list on a
+ # new or existing group. Because pw groupadd does not support the -m
+ # and -d options used by manage_group, we treat group creation as a
+ # special case and use -M.
+ Chef::Log.debug("#{@new_resource} setting group members: #{@new_resource.members.join(',')}")
+ member_options = [" -M #{@new_resource.members.join(',')}"]
+
if member_options.empty?
run_command(:command => command)
else
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 31f88e5406..ac52100b56 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -19,6 +19,7 @@
require 'chef/log'
require 'chef/mixin/command'
require 'chef/provider'
+require 'chef/resource/file'
require 'chef/exceptions'
require 'erb'
@@ -109,11 +110,11 @@ class Chef
:command => command
)
Chef::Log.info("#{@new_resource} added")
- # Write out the config files
- generate_config
end
end
end
+ # Write out the config files
+ generate_config
end
def action_enable
@@ -140,12 +141,12 @@ class Chef
run_command(
:command => command
)
- delete_config
Chef::Log.info("#{@new_resource} deleted")
end
else
Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
end
+ delete_config
end
def action_disable
@@ -168,27 +169,25 @@ class Chef
! @config_template.nil? and ! @config_path.nil?
end
+ def resource_for_config(path)
+ Chef::Resource::File.new(path, run_context)
+ end
+
def generate_config
return unless can_generate_config?
b = binding
template = ::ERB.new(@config_template)
- converge_by ("generate configuration file : #{@config_path}") do
- network_file = ::File.new(@config_path, "w")
- network_file.puts(template.result(b))
- network_file.close
- end
- Chef::Log.info("#{@new_resource} created configuration file")
+ config = resource_for_config(@config_path)
+ config.content(template.result(b))
+ config.run_action(:create)
+ @new_resource.updated_by_last_action(true) if config.updated?
end
def delete_config
return unless can_generate_config?
- require 'fileutils'
- if ::File.exist?(@config_path)
- converge_by ("delete the #{@config_path}") do
- FileUtils.rm_f(@config_path, :verbose => false)
- end
- end
- Chef::Log.info("#{@new_resource} deleted configuration file")
+ config = resource_for_config(@config_path)
+ config.run_action(:delete)
+ @new_resource.updated_by_last_action(true) if config.updated?
end
private
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 207b0cde6c..2d4a5aadef 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -127,7 +127,7 @@ class Chef
end
def remount_command
- return "mount -o remount #{@new_resource.mount_point}"
+ return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.mount_point}"
end
def remount_fs
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index 462fa32b71..cf04150322 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -1,4 +1,4 @@
-#
+# Encoding: utf-8
# Author:: Hugo Fichter
# Author:: Lamont Granquist (<lamont@getchef.com>)
# Author:: Joshua Timberman (<joshua@getchef.com>)
@@ -25,14 +25,16 @@ require 'forwardable'
class Chef
class Provider
class Mount
+ # Mount Solaris File systems
class Solaris < Chef::Provider::Mount
extend Forwardable
- VFSTAB = "/etc/vfstab".freeze
+ VFSTAB = '/etc/vfstab'.freeze
def_delegator :@new_resource, :device, :device
def_delegator :@new_resource, :device_type, :device_type
def_delegator :@new_resource, :dump, :dump
+ def_delegator :@new_resource, :fsck_device, :fsck_device
def_delegator :@new_resource, :fstype, :fstype
def_delegator :@new_resource, :mount_point, :mount_point
def_delegator :@new_resource, :options, :options
@@ -42,6 +44,7 @@ class Chef
@current_resource = Chef::Resource::Mount.new(new_resource.name)
current_resource.mount_point(mount_point)
current_resource.device(device)
+ current_resource.fsck_device(fsck_device)
current_resource.device_type(device_type)
update_current_resource_state
end
@@ -53,6 +56,14 @@ class Chef
a.whyrun("Assuming device #{device} would have been created")
end
+ unless fsck_device == '-'
+ requirements.assert(:mount, :remount) do |a|
+ a.assertion { ::File.exist?(fsck_device) }
+ a.failure_message(Chef::Exceptions::Mount, "Device #{fsck_device} does not exist")
+ a.whyrun("Assuming device #{fsck_device} would have been created")
+ end
+ end
+
requirements.assert(:mount, :remount) do |a|
a.assertion { ::File.exist?(mount_point) }
a.failure_message(Chef::Exceptions::Mount, "Mount point #{mount_point} does not exist")
@@ -62,7 +73,7 @@ class Chef
def mount_fs
actual_options = options || []
- actual_options.delete("noauto")
+ actual_options.delete('noauto')
command = "mount -F #{fstype}"
command << " -o #{actual_options.join(',')}" unless actual_options.empty?
command << " #{device} #{mount_point}"
@@ -74,59 +85,28 @@ class Chef
end
def remount_fs
- # FIXME: what about options like "-o remount,logging" to enable logging on a UFS device?
- shell_out!("mount -o remount #{mount_point}")
+ # FIXME: Should remount always do the remount or only if the options change?
+ actual_options = options || []
+ actual_options.delete('noauto')
+ mount_options = actual_options.empty? ? '' : ",#{actual_options.join(',')}"
+ shell_out!("mount -o remount#{mount_options} #{mount_point}")
end
def enable_fs
- if !mount_options_unchanged?
+ unless mount_options_unchanged?
# we are enabling because our options have changed, so disable first then re-enable.
# XXX: this should be refactored to be the responsibility of the caller
disable_fs if current_resource.enabled
end
- auto = options.nil? || ! options.include?("noauto")
- actual_options = unless options.nil?
- options.delete("noauto")
- options
- end
-
- autostr = auto ? 'yes' : 'no'
- passstr = pass == 0 ? "-" : pass
- optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(',')
-
- etc_tempfile do |f|
- f.write(IO.read(VFSTAB).chomp)
- f.puts("\n#{device}\t-\t#{mount_point}\t#{fstype}\t#{passstr}\t#{autostr}\t#{optstr}")
- f.close
- # move, preserving modes of destination file
- mover = Chef::FileContentManagement::Deploy.strategy(true)
- mover.deploy(f.path, VFSTAB)
- end
-
+ vfstab_write(merge_vfstab_entry)
end
def disable_fs
- contents = []
-
- found = false
- ::File.readlines(VFSTAB).reverse_each do |line|
- if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/
- found = true
- Chef::Log.debug("#{new_resource} is removed from vfstab")
- next
- end
- contents << line
- end
+ contents, found = delete_vfstab_entry
if found
- etc_tempfile do |f|
- f.write(contents.reverse.join(''))
- f.close
- # move, preserving modes of destination file
- mover = Chef::FileContentManagement::Deploy.strategy(true)
- mover.deploy(f.path, VFSTAB)
- end
+ vfstab_write(contents.reverse)
else
# this is likely some kind of internal error, since we should only call disable_fs when there
# the filesystem we want to disable is enabled.
@@ -135,19 +115,24 @@ class Chef
end
def etc_tempfile
- yield Tempfile.open("vfstab", "/etc")
+ yield Tempfile.open('vfstab', '/etc')
end
def mount_options_unchanged?
- current_resource.fstype == fstype and
- current_resource.options == options and
- current_resource.dump == dump and
- current_resource.pass == pass
+ new_options = options_remove_noauto(options)
+ current_options = options_remove_noauto(current_resource.nil? ? nil : current_resource.options)
+
+ current_resource.fsck_device == fsck_device &&
+ current_resource.fstype == fstype &&
+ current_options == new_options &&
+ current_resource.dump == dump &&
+ current_resource.pass == pass &&
+ current_resource.options.include?('noauto') == !mount_at_boot?
end
def update_current_resource_state
current_resource.mounted(mounted?)
- ( enabled, fstype, options, pass ) = read_vfstab_status
+ (enabled, fstype, options, pass) = read_vfstab_status
current_resource.enabled(enabled)
current_resource.fstype(fstype)
current_resource.options(options)
@@ -158,17 +143,18 @@ class Chef
read_vfstab_status[0]
end
+ # Check for the device in mounttab.
+ # <device> on <mountpoint> type <fstype> <options> on <date>
+ # /dev/dsk/c1t0d0s0 on / type ufs read/write/setuid/devices/intr/largefiles/logging/xattr/onerror=panic/dev=700040 on Tue May 1 11:33:55 2012
def mounted?
mounted = false
- shell_out!("mount -v").stdout.each_line do |line|
- # <device> on <mountpoint> type <fstype> <options> on <date>
- # /dev/dsk/c1t0d0s0 on / type ufs read/write/setuid/devices/intr/largefiles/logging/xattr/onerror=panic/dev=700040 on Tue May 1 11:33:55 2012
+ shell_out!('mount -v').stdout.each_line do |line|
case line
when /^#{device_regex}\s+on\s+#{Regexp.escape(mount_point)}\s+/
Chef::Log.debug("Special device #{device} is mounted as #{mount_point}")
mounted = true
when /^([\/\w]+)\son\s#{Regexp.escape(mount_point)}\s+/
- Chef::Log.debug("Special device #{$1} is mounted as #{mount_point}")
+ Chef::Log.debug("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}")
mounted = false
end
end
@@ -178,7 +164,7 @@ class Chef
private
def read_vfstab_status
- # Check to see if there is a entry in /etc/vfstab. Last entry for a volume wins.
+ # Check to see if there is an entry in /etc/vfstab. Last entry for a volume wins.
enabled = false
fstype = options = pass = nil
::File.foreach(VFSTAB) do |line|
@@ -190,18 +176,18 @@ class Chef
# to mount to fsck point type pass at boot options
when /^#{device_regex}\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
enabled = true
- fstype = $1
- options = $4
+ fstype = Regexp.last_match[1]
+ options = Regexp.last_match[4]
# Store the 'mount at boot' column from vfstab as the 'noauto' option
# in current_resource.options (linux style)
- if $3 == "yes"
+ if Regexp.last_match[3] == 'no'
if options.nil? || options.empty?
- options = "noauto"
+ options = 'noauto'
else
- options += ",noauto"
+ options += ',noauto'
end
end
- pass = ( $2 == "-" ) ? 0 : $2.to_i
+ pass = (Regexp.last_match[2] == '-') ? 0 : Regexp.last_match[2].to_i
Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}")
next
when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/
@@ -210,21 +196,73 @@ class Chef
Chef::Log.debug("Found conflicting mount point #{mount_point} in #{VFSTAB}")
end
end
- [ enabled, fstype, options, pass ]
+ [enabled, fstype, options, pass]
end
def device_should_exist?
- !%w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs}.include?(fstype)
+ !%w(tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs vxfs).include?(fstype)
+ end
+
+ def mount_at_boot?
+ options.nil? || !options.include?('noauto')
+ end
+
+ def vfstab_write(contents)
+ etc_tempfile do |f|
+ f.write(contents.join(''))
+ f.close
+ # move, preserving modes of destination file
+ mover = Chef::FileContentManagement::Deploy.strategy(true)
+ mover.deploy(f.path, VFSTAB)
+ end
+ end
+
+ def vfstab_entry
+ actual_options = unless options.nil?
+ tempops = options.dup
+ tempops.delete('noauto')
+ tempops
+ end
+ autostr = mount_at_boot? ? 'yes' : 'no'
+ passstr = pass == 0 ? '-' : pass
+ optstr = (actual_options.nil? || actual_options.empty?) ? '-' : actual_options.join(',')
+ "\n#{device}\t#{fsck_device}\t#{mount_point}\t#{fstype}\t#{passstr}\t#{autostr}\t#{optstr}\n"
+ end
+
+ def delete_vfstab_entry
+ contents = []
+ found = false
+ ::File.readlines(VFSTAB).reverse_each do |line|
+ if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/
+ found = true
+ Chef::Log.debug("#{new_resource} is removed from vfstab")
+ next
+ end
+ contents << line
+ end
+ [contents, found]
+ end
+
+ def merge_vfstab_entry
+ contents = ::File.readlines(VFSTAB)
+ contents[-1].chomp!
+ contents << vfstab_entry
+ end
+
+ def options_remove_noauto(temp_options)
+ new_options = []
+ new_options += temp_options.nil? ? [] : temp_options
+ new_options.delete('noauto')
+ new_options
end
def device_regex
if ::File.symlink?(device)
- "(?:#{Regexp.escape(device)}|#{Regexp.escape(::File.expand_path(::File.readlink(device),::File.dirname(device)))})"
+ "(?:#{Regexp.escape(device)}|#{Regexp.escape(::File.expand_path(::File.readlink(device), ::File.dirname(device)))})"
else
Regexp.escape(device)
end
end
-
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index 9fb87d6ea0..da3e6d1684 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -112,14 +112,10 @@ class Chef
def install_package(name, version)
Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
if @new_resource.options.nil?
- run_command_with_systems_locale(
- :command => "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}"
- )
+ shell_out!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
- run_command_with_systems_locale(
- :command => "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}"
- )
+ shell_out!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
@@ -128,14 +124,10 @@ class Chef
def remove_package(name, version)
if @new_resource.options.nil?
- run_command_with_systems_locale(
- :command => "installp -u #{name}"
- )
+ shell_out!( "installp -u #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- run_command_with_systems_locale(
- :command => "installp -u #{expand_options(@new_resource.options)} #{name}"
- )
+ shell_out!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index fed3e34124..4090507303 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -39,47 +39,39 @@ class Chef
end
end
- def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.name)
- check_package_state(@new_resource.package_name)
- @current_resource
+ def get_current_version
+ shell_out("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+ return $1.split[0] if line =~ /^\s+Version: (.*)/
+ end
+ return nil
end
- def check_package_state(package)
- Chef::Log.debug("Checking package status for #{package}")
- installed = false
- depends = false
-
- shell_out!("pkg info -r #{package}").stdout.each_line do |line|
- case line
- when /^\s+State: Installed/
- installed = true
- when /^\s+Version: (.*)/
- @candidate_version = $1.split[0]
- if installed
- @current_resource.version($1)
- else
- @current_resource.version(nil)
- end
- end
+ def get_candidate_version
+ shell_out!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+ return $1.split[0] if line =~ /Version: (.*)/
end
+ return nil
+ end
- return installed
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ Chef::Log.debug("Checking package status for #{@new_resource.name}")
+ @current_resource.version(get_current_version)
+ @candidate_version = get_candidate_version
+ @current_resource
end
def install_package(name, version)
package_name = "#{name}@#{version}"
normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
- if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
- command = normal_command.gsub('-q', '-q --accept')
- else
- command = normal_command
- end
- begin
- run_command_with_systems_locale(:command => command)
- rescue
- end
+ command =
+ if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
+ normal_command.gsub('-q', '-q --accept')
+ else
+ normal_command
+ end
+ shell_out(command)
end
def upgrade_package(name, version)
@@ -88,9 +80,7 @@ class Chef
def remove_package(name, version)
package_name = "#{name}@#{version}"
- run_command_with_systems_locale(
- :command => "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}"
- )
+ shell_out!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
end
end
end
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index 6ef303ee4f..05247e6d31 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -45,27 +45,21 @@ class Chef
unless @current_resource.version == version
command = "port#{expand_options(@new_resource.options)} install #{name}"
command << " @#{version}" if version and !version.empty?
- run_command_with_systems_locale(
- :command => command
- )
+ shell_out!(command)
end
end
def purge_package(name, version)
command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
command << " @#{version}" if version and !version.empty?
- run_command_with_systems_locale(
- :command => command
- )
+ shell_out!(command)
end
def remove_package(name, version)
command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
command << " @#{version}" if version and !version.empty?
- run_command_with_systems_locale(
- :command => command
- )
+ shell_out!(command)
end
def upgrade_package(name, version)
@@ -78,9 +72,7 @@ class Chef
# that hasn't been installed.
install_package(name, version)
elsif current_version != version
- run_command_with_systems_locale(
- :command => "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}"
- )
+ shell_out!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
end
end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index 2e8bb7850b..1014ebcaa5 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -86,9 +86,7 @@ class Chef
end
def install_package(name, version)
- run_command_with_systems_locale(
- :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
- )
+ shell_out!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def upgrade_package(name, version)
@@ -96,9 +94,7 @@ class Chef
end
def remove_package(name, version)
- run_command_with_systems_locale(
- :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
- )
+ shell_out!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 6a3587558a..7e0eebd0d9 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -110,9 +110,7 @@ class Chef
pkg = "~#{name}-#{$1}"
end
- run_command_with_systems_locale(
- :command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
- )
+ shell_out!( "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
end
def upgrade_package(name, version)
@@ -126,9 +124,7 @@ class Chef
pkg = "#{@new_resource.package_name}"
end
- run_command_with_systems_locale(
- :command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
- )
+ shell_out!( "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index bbb561bd15..c0a6444252 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -90,13 +90,9 @@ class Chef
def install_package(name, version)
unless @current_resource.version
- run_command_with_systems_locale(
- :command => "rpm #{@new_resource.options} -i #{@new_resource.source}"
- )
+ shell_out!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
else
- run_command_with_systems_locale(
- :command => "rpm #{@new_resource.options} -U #{@new_resource.source}"
- )
+ shell_out!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
end
end
@@ -104,13 +100,9 @@ class Chef
def remove_package(name, version)
if version
- run_command_with_systems_locale(
- :command => "rpm #{@new_resource.options} -e #{name}-#{version}"
- )
+ shell_out!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
else
- run_command_with_systems_locale(
- :command => "rpm #{@new_resource.options} -e #{name}"
- )
+ shell_out!( "rpm #{@new_resource.options} -e #{name}" )
end
end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index 0f45b61e18..19f844b66a 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -112,9 +112,7 @@ class Chef
else
command = "pkgadd -n -d #{@new_resource.source} all"
end
- run_command_with_systems_locale(
- :command => command
- )
+ shell_out!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
if ::File.directory?(@new_resource.source) # CHEF-4469
@@ -122,23 +120,17 @@ class Chef
else
command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
end
- run_command_with_systems_locale(
- :command => command
- )
+ shell_out!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
def remove_package(name, version)
if @new_resource.options.nil?
- run_command_with_systems_locale(
- :command => "pkgrm -n #{name}"
- )
+ shell_out!( "pkgrm -n #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- run_command_with_systems_locale(
- :command => "pkgrm -n#{expand_options(@new_resource.options)} #{name}"
- )
+ shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index d15f22ae16..e77319c254 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -19,6 +19,7 @@
require 'chef/config'
require 'chef/provider/package'
require 'chef/mixin/command'
+require 'chef/mixin/shell_out'
require 'chef/resource/package'
require 'singleton'
require 'chef/mixin/get_source_from_package'
@@ -645,6 +646,7 @@ class Chef
# Cache for our installed and available packages, pulled in from yum-dump.py
class YumCache
include Chef::Mixin::Command
+ include Chef::Mixin::ShellOut
include Singleton
def initialize
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
index 75b2a5535a..f9b729362c 100644
--- a/lib/chef/provider/remote_file/cache_control_data.rb
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -139,7 +139,7 @@ class Chef
end
def load_data
- Chef::JSONCompat.from_json(load_json_data)
+ Chef::JSONCompat.parse(load_json_data)
rescue Chef::Exceptions::FileNotFound, Chef::Exceptions::JSON::ParseError
false
end
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 06fe7fc480..1ebef90349 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -130,15 +130,15 @@ class Chef
def enable_service
if @new_resource.priority.is_a? Integer
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
- run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}")
elsif @new_resource.priority.is_a? Hash
# we call the same command regardless of we're enabling or disabling
# users passing a Hash are responsible for setting their own start priorities
set_priority
else # No priority, go with update-rc.d defaults
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
- run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} defaults")
end
end
@@ -146,16 +146,16 @@ class Chef
def disable_service
if @new_resource.priority.is_a? Integer
# Stop processes in reverse order of start using '100 - start_priority'
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .")
elsif @new_resource.priority.is_a? Hash
# we call the same command regardless of we're enabling or disabling
# users passing a Hash are responsible for setting their own stop priorities
set_priority
else
# no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .")
end
end
@@ -166,8 +166,8 @@ class Chef
priority = o[1]
args += "#{action} #{priority} #{level} . "
end
- run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
- run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} #{args}")
+ shell_out!("/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ shell_out!("/usr/sbin/update-rc.d #{@new_resource.service_name} #{args}")
end
end
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 800cd4ec13..08d58232e1 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -25,89 +25,76 @@ class Chef
class Service
class Freebsd < Chef::Provider::Service::Init
- def load_current_resource
- @current_resource = Chef::Resource::Service.new(@new_resource.name)
- @current_resource.service_name(@new_resource.service_name)
- @rcd_script_found = true
+ attr_reader :enabled_state_found
+
+ def initialize(new_resource, run_context)
+ super
@enabled_state_found = false
- # Determine if we're talking about /etc/rc.d or /usr/local/etc/rc.d
- if ::File.exists?("/etc/rc.d/#{current_resource.service_name}")
- @init_command = "/etc/rc.d/#{current_resource.service_name}"
- elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}")
- @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}"
- else
- @rcd_script_found = false
- return
- end
- Chef::Log.debug("#{@current_resource} found at #{@init_command}")
- determine_current_status!
- # Default to disabled if the service doesn't currently exist
- # at all
- var_name = service_enable_variable_name
- if ::File.exists?("/etc/rc.conf") && var_name
- read_rc_conf.each do |line|
- case line
- when /#{Regexp.escape(var_name)}="(\w+)"/
- @enabled_state_found = true
- if $1 =~ /[Yy][Ee][Ss]/
- @current_resource.enabled true
- elsif $1 =~ /[Nn][Oo][Nn]?[Oo]?[Nn]?[Ee]?/
- @current_resource.enabled false
- end
- end
- end
- end
- unless @current_resource.enabled
- Chef::Log.debug("#{@new_resource.name} enable/disable state unknown")
- @current_resource.enabled false
+ @init_command = nil
+ if ::File.exist?("/etc/rc.d/#{new_resource.service_name}")
+ @init_command = "/etc/rc.d/#{new_resource.service_name}"
+ elsif ::File.exist?("/usr/local/etc/rc.d/#{new_resource.service_name}")
+ @init_command = "/usr/local/etc/rc.d/#{new_resource.service_name}"
end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(new_resource.name)
+ current_resource.service_name(new_resource.service_name)
+
+ return current_resource unless init_command
- @current_resource
+ Chef::Log.debug("#{current_resource} found at #{init_command}")
+
+ determine_current_status! # see Chef::Provider::Service::Simple
+
+ determine_enabled_status!
+ current_resource
end
def define_resource_requirements
shared_resource_requirements
+
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { @rcd_script_found }
- a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script"
+ a.assertion { init_command }
+ a.failure_message Chef::Exceptions::Service, "#{new_resource}: unable to locate the rc.d script"
end
requirements.assert(:all_actions) do |a|
- a.assertion { @enabled_state_found }
+ a.assertion { enabled_state_found }
# for consistentcy with original behavior, this will not fail in non-whyrun mode;
# rather it will silently set enabled state=>false
a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled."
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { @rcd_script_found && service_enable_variable_name != nil }
- a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar"
+ a.assertion { init_command && service_enable_variable_name != nil }
+ a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar"
# No recovery in whyrun mode - the init file is present but not correct.
end
end
def start_service
- if @new_resource.start_command
+ if new_resource.start_command
super
else
- shell_out!("#{@init_command} faststart")
+ shell_out_with_systems_locale!("#{init_command} faststart")
end
end
def stop_service
- if @new_resource.stop_command
+ if new_resource.stop_command
super
else
- shell_out!("#{@init_command} faststop")
+ shell_out_with_systems_locale!("#{init_command} faststop")
end
end
def restart_service
- if @new_resource.restart_command
-
+ if new_resource.restart_command
super
- elsif @new_resource.supports[:restart]
- shell_out!("#{@init_command} fastrestart")
+ elsif new_resource.supports[:restart]
+ shell_out_with_systems_locale!("#{init_command} fastrestart")
else
stop_service
sleep 1
@@ -115,6 +102,16 @@ class Chef
end
end
+ def enable_service
+ set_service_enable("YES") unless current_resource.enabled
+ end
+
+ def disable_service
+ set_service_enable("NO") if current_resource.enabled
+ end
+
+ private
+
def read_rc_conf
::File.open("/etc/rc.conf", 'r') { |file| file.readlines }
end
@@ -127,46 +124,65 @@ class Chef
# The variable name used in /etc/rc.conf for enabling this service
def service_enable_variable_name
- # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf
- # corresponding to this service
- # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need
- # to set mysql_enable="YES" in /etc/rc.conf$
- if @rcd_script_found
- ::File.open(@init_command) do |rcscript|
- rcscript.each_line do |line|
- if line =~ /^name="?(\w+)"?/
- return $1 + "_enable"
+ @service_enable_variable_name ||=
+ begin
+ # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf
+ # corresponding to this service
+ # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need
+ # to set mysql_enable="YES" in /etc/rc.conf$
+ if init_command
+ ::File.open(init_command) do |rcscript|
+ rcscript.each_line do |line|
+ if line =~ /^name="?(\w+)"?/
+ return $1 + "_enable"
+ end
+ end
+ end
+ # some scripts support multiple instances through symlinks such as openvpn.
+ # We should get the service name from rcvar.
+ Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
+ sn = shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ else
+ # for why-run mode when the rcd_script is not there yet
+ new_resource.service_name
+ end
+ end
+ end
+
+ def determine_enabled_status!
+ var_name = service_enable_variable_name
+ if ::File.exist?("/etc/rc.conf") && var_name
+ read_rc_conf.each do |line|
+ case line
+ when /^#{Regexp.escape(var_name)}="(\w+)"/
+ enabled_state_found!
+ if $1 =~ /^yes$/i
+ current_resource.enabled true
+ elsif $1 =~ /^(no|none)$/i
+ current_resource.enabled false
end
end
end
- # some scripts support multiple instances through symlinks such as openvpn.
- # We should get the service name from rcvar.
- Chef::Log.debug("name=\"service\" not found at #{@init_command}. falling back to rcvar")
- sn = shell_out!("#{@init_command} rcvar").stdout[/(\w+_enable)=/, 1]
- return sn
end
- # Fallback allows us to keep running in whyrun mode when
- # the script does not exist.
- @new_resource.service_name
+
+ if current_resource.enabled.nil?
+ Chef::Log.debug("#{new_resource.name} enable/disable state unknown")
+ current_resource.enabled false
+ end
end
def set_service_enable(value)
lines = read_rc_conf
# Remove line that set the old value
- lines.delete_if { |line| line =~ /#{Regexp.escape(service_enable_variable_name)}/ }
+ lines.delete_if { |line| line =~ /^#{Regexp.escape(service_enable_variable_name)}=/ }
# And append the line that sets the new value at the end
lines << "#{service_enable_variable_name}=\"#{value}\""
write_rc_conf(lines)
end
- def enable_service()
- set_service_enable("YES") unless @current_resource.enabled
- end
-
- def disable_service()
- set_service_enable("NO") if @current_resource.enabled
+ def enabled_state_found!
+ @enabled_state_found = true
end
-
end
end
end
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 1559c7893f..a68abfebc9 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -58,10 +58,10 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
end
def enable_service()
- run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default")
+ shell_out!("/sbin/rc-update add #{@new_resource.service_name} default")
end
def disable_service()
- run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default")
+ shell_out!("/sbin/rc-update del #{@new_resource.service_name} default")
end
end
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index cf5da852c3..5d8bb5bb38 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -24,6 +24,8 @@ class Chef
class Service
class Init < Chef::Provider::Service::Simple
+ attr_accessor :init_command
+
def initialize(new_resource, run_context)
super
@init_command = "/etc/init.d/#{@new_resource.service_name}"
@@ -48,7 +50,7 @@ class Chef
if @new_resource.start_command
super
else
- shell_out!("#{default_init_command} start")
+ shell_out_with_systems_locale!("#{default_init_command} start")
end
end
@@ -56,7 +58,7 @@ class Chef
if @new_resource.stop_command
super
else
- shell_out!("#{default_init_command} stop")
+ shell_out_with_systems_locale!("#{default_init_command} stop")
end
end
@@ -64,7 +66,7 @@ class Chef
if @new_resource.restart_command
super
elsif @new_resource.supports[:restart]
- shell_out!("#{default_init_command} restart")
+ shell_out_with_systems_locale!("#{default_init_command} restart")
else
stop_service
sleep 1
@@ -76,7 +78,7 @@ class Chef
if @new_resource.reload_command
super
elsif @new_resource.supports[:reload]
- shell_out!("#{default_init_command} reload")
+ shell_out_with_systems_locale!("#{default_init_command} reload")
end
end
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 35767ee7b9..f4c85dd9d3 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -37,12 +37,12 @@ class Chef
end
def enable_service()
- run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}")
- run_command(:command => "/sbin/insserv -d -f #{@new_resource.service_name}")
+ shell_out!("/sbin/insserv -r -f #{@new_resource.service_name}")
+ shell_out!("/sbin/insserv -d -f #{@new_resource.service_name}")
end
def disable_service()
- run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}")
+ shell_out!("/sbin/insserv -r -f #{@new_resource.service_name}")
end
end
end
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 36930ee4ac..cf5e554559 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -83,7 +83,7 @@ class Chef
if @new_resource.start_command
super
else
- shell_out!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ shell_out_with_systems_locale!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
end
end
end
@@ -95,7 +95,7 @@ class Chef
if @new_resource.stop_command
super
else
- shell_out!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ shell_out_with_systems_locale!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
end
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index f03b8a18a1..bd51d15f84 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -25,6 +25,8 @@ class Chef
class Service
class Simple < Chef::Provider::Service
+ attr_reader :status_load_success
+
def load_current_resource
@current_resource = Chef::Resource::Service.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@@ -83,16 +85,16 @@ class Chef
end
def start_service
- shell_out!(@new_resource.start_command)
+ shell_out_with_systems_locale!(@new_resource.start_command)
end
def stop_service
- shell_out!(@new_resource.stop_command)
+ shell_out_with_systems_locale!(@new_resource.stop_command)
end
def restart_service
if @new_resource.restart_command
- shell_out!(@new_resource.restart_command)
+ shell_out_with_systems_locale!(@new_resource.restart_command)
else
stop_service
sleep 1
@@ -101,7 +103,7 @@ class Chef
end
def reload_service
- shell_out!(@new_resource.reload_command)
+ shell_out_with_systems_locale!(@new_resource.reload_command)
end
protected
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index 0c47a3462b..f0584dcf6d 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -56,7 +56,7 @@ class Chef
alias_method :start_service, :enable_service
def reload_service
- shell_out!("#{default_init_command} refresh #{@new_resource.service_name}")
+ shell_out_with_systems_locale!("#{default_init_command} refresh #{@new_resource.service_name}")
end
def restart_service
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 6231603d03..31feee65d4 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -28,7 +28,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
if @new_resource.status_command
Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
- unless shell_out_with_systems_locale(@new_resource.status_command).error?
+ unless shell_out(@new_resource.status_command).error?
@current_resource.running(true)
else
@status_check_success = false
@@ -61,7 +61,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
if @new_resource.start_command
super
else
- shell_out_with_systems_locale("/bin/systemctl start #{@new_resource.service_name}")
+ shell_out_with_systems_locale!("/bin/systemctl start #{@new_resource.service_name}")
end
end
end
@@ -73,7 +73,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
if @new_resource.stop_command
super
else
- shell_out_with_systems_locale("/bin/systemctl stop #{@new_resource.service_name}")
+ shell_out_with_systems_locale!("/bin/systemctl stop #{@new_resource.service_name}")
end
end
end
@@ -82,7 +82,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
if @new_resource.restart_command
super
else
- shell_out_with_systems_locale("/bin/systemctl restart #{@new_resource.service_name}")
+ shell_out_with_systems_locale!("/bin/systemctl restart #{@new_resource.service_name}")
end
end
@@ -91,7 +91,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
super
else
if @current_resource.running
- shell_out_with_systems_locale("/bin/systemctl reload #{@new_resource.service_name}")
+ shell_out_with_systems_locale!("/bin/systemctl reload #{@new_resource.service_name}")
else
start_service
end
@@ -99,18 +99,18 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
end
def enable_service
- shell_out_with_systems_locale("/bin/systemctl enable #{@new_resource.service_name}")
+ shell_out!("/bin/systemctl enable #{@new_resource.service_name}")
end
def disable_service
- shell_out_with_systems_locale("/bin/systemctl disable #{@new_resource.service_name}")
+ shell_out!("/bin/systemctl disable #{@new_resource.service_name}")
end
def is_active?
- shell_out_with_systems_locale("/bin/systemctl is-active #{@new_resource.service_name} --quiet").exitstatus == 0
+ shell_out("/bin/systemctl is-active #{@new_resource.service_name} --quiet").exitstatus == 0
end
def is_enabled?
- shell_out_with_systems_locale("/bin/systemctl is-enabled #{@new_resource.service_name} --quiet").exitstatus == 0
+ shell_out("/bin/systemctl is-enabled #{@new_resource.service_name} --quiet").exitstatus == 0
end
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index c81a8a50dc..670bf9e5f8 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -97,10 +97,10 @@ class Chef
Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
begin
- if run_command_with_systems_locale(:command => @new_resource.status_command) == 0
+ if shell_out!(@new_resource.status_command) == 0
@current_resource.running true
end
- rescue Chef::Exceptions::Exec
+ rescue
@command_success = false
@current_resource.running false
nil
@@ -153,7 +153,7 @@ class Chef
if @new_resource.start_command
super
else
- run_command_with_systems_locale(:command => "/sbin/start #{@job}")
+ shell_out_with_systems_locale!("/sbin/start #{@job}")
end
end
end
@@ -167,7 +167,7 @@ class Chef
if @new_resource.stop_command
super
else
- run_command_with_systems_locale(:command => "/sbin/stop #{@job}")
+ shell_out_with_systems_locale!("/sbin/stop #{@job}")
end
end
end
@@ -179,7 +179,7 @@ class Chef
# Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
else
if @current_resource.running
- run_command_with_systems_locale(:command => "/sbin/restart #{@job}")
+ shell_out_with_systems_locale!("/sbin/restart #{@job}")
else
start_service
end
@@ -191,7 +191,7 @@ class Chef
super
else
# upstart >= 0.6.3-4 supports reload (HUP)
- run_command_with_systems_locale(:command => "/sbin/reload #{@job}")
+ shell_out_with_systems_locale!("/sbin/reload #{@job}")
end
end
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 2d478fa9fe..d31aad4c9d 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -27,6 +27,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
#Win32::Service.get_start_type
AUTO_START = 'auto start'
+ MANUAL = 'demand start'
DISABLED = 'disabled'
#Win32::Service.get_current_state
@@ -45,11 +46,16 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
def load_current_resource
- @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource = Chef::Resource::WindowsService.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@current_resource.running(current_state == RUNNING)
Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
- @current_resource.enabled(start_type == AUTO_START)
+ case current_start_type
+ when AUTO_START
+ @current_resource.enabled(true)
+ when DISABLED
+ @current_resource.enabled(false)
+ end
Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
@current_resource
end
@@ -125,15 +131,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
def enable_service
if Win32::Service.exists?(@new_resource.service_name)
- if start_type == AUTO_START
- Chef::Log.debug "#{@new_resource} already enabled - nothing to do"
- else
- Win32::Service.configure(
- :service_name => @new_resource.service_name,
- :start_type => Win32::Service::AUTO_START
- )
- @new_resource.updated_by_last_action(true)
- end
+ set_startup_type(:automatic)
else
Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
end
@@ -141,26 +139,76 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
def disable_service
if Win32::Service.exists?(@new_resource.service_name)
- if start_type == AUTO_START
- Win32::Service.configure(
- :service_name => @new_resource.service_name,
- :start_type => Win32::Service::DISABLED
- )
- @new_resource.updated_by_last_action(true)
- else
- Chef::Log.debug "#{@new_resource} already disabled - nothing to do"
- end
+ set_startup_type(:disabled)
else
Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
end
end
+ def action_enable
+ if current_start_type != AUTO_START
+ converge_by("enable service #{@new_resource}") do
+ enable_service
+ Chef::Log.info("#{@new_resource} enabled")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ end
+ load_new_resource_state
+ @new_resource.enabled(true)
+ end
+
+ def action_disable
+ if current_start_type != DISABLED
+ converge_by("disable service #{@new_resource}") do
+ disable_service
+ Chef::Log.info("#{@new_resource} disabled")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ end
+ load_new_resource_state
+ @new_resource.enabled(false)
+ end
+
+ def action_configure_startup
+ case @new_resource.startup_type
+ when :automatic
+ if current_start_type != AUTO_START
+ converge_by("set service #{@new_resource} startup type to automatic") do
+ set_startup_type(:automatic)
+ end
+ else
+ Chef::Log.debug("#{@new_resource} startup_type already automatic - nothing to do")
+ end
+ when :manual
+ if current_start_type != MANUAL
+ converge_by("set service #{@new_resource} startup type to manual") do
+ set_startup_type(:manual)
+ end
+ else
+ Chef::Log.debug("#{@new_resource} startup_type already manual - nothing to do")
+ end
+ when :disabled
+ if current_start_type != DISABLED
+ converge_by("set service #{@new_resource} startup type to disabled") do
+ set_startup_type(:disabled)
+ end
+ else
+ Chef::Log.debug("#{@new_resource} startup_type already disabled - nothing to do")
+ end
+ end
+
+ # Avoid changing enabled from true/false for now
+ @new_resource.enabled(nil)
+ end
+
private
def current_state
Win32::Service.status(@new_resource.service_name).current_state
end
- def start_type
+ def current_start_type
Win32::Service.config_info(@new_resource.service_name).start_type
end
@@ -188,4 +236,22 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
worker.join
end
end
+
+ # Takes Win32::Service start_types
+ def set_startup_type(type)
+ # Set-Service Startup Type => Win32::Service Constant
+ allowed_types = { :automatic => Win32::Service::AUTO_START,
+ :manual => Win32::Service::DEMAND_START,
+ :disabled => Win32::Service::DISABLED }
+ unless allowed_types.keys.include?(type)
+ raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported"
+ end
+
+ Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}"
+ Win32::Service.configure(
+ :service_name => @new_resource.service_name,
+ :start_type => allowed_types[type]
+ )
+ @new_resource.updated_by_last_action(true)
+ end
end
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index 81ed639c53..6cf31c8ec8 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -60,7 +60,7 @@ class Chef
def action_checkout
if target_dir_non_existent_or_empty?
converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do
- run_command(run_options(:command => checkout_command))
+ shell_out!(run_options(command: checkout_command))
end
else
Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
@@ -77,7 +77,7 @@ class Chef
def action_force_export
converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do
- run_command(run_options(:command => export_command))
+ shell_out!(run_options(command: export_command))
end
end
@@ -88,7 +88,7 @@ class Chef
Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}"
unless current_revision_matches_target_revision?
converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do
- run_command(run_options(:command => sync_command))
+ shell_out!(run_options(command: sync_command))
Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
end
end
@@ -100,14 +100,14 @@ class Chef
def sync_command
c = scm :update, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.destination
Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}"
- c
+ c
end
def checkout_command
c = scm :checkout, @new_resource.svn_arguments, verbose, authentication,
"-r#{revision_int}", @new_resource.repository, @new_resource.destination
Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
- c
+ c
end
def export_command
@@ -116,7 +116,7 @@ class Chef
"-r#{revision_int}" << @new_resource.repository << @new_resource.destination
c = scm :export, *args
Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
- c
+ c
end
# If the specified revision isn't an integer ("HEAD" for example), look
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 96b5db24ba..a24a047596 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -16,40 +16,213 @@
# limitations under the License.
#
+require 'mixlib/shellout'
require 'chef/provider/user'
require 'openssl'
+require 'plist'
class Chef
class Provider
class User
+ #
+ # The most tricky bit of this provider is the way it deals with user passwords.
+ # Mac OS X has different password shadow calculations based on the version.
+ # < 10.7 => password shadow calculation format SALTED-SHA1
+ # => stored in: /var/db/shadow/hash/#{guid}
+ # => shadow binary length 68 bytes
+ # => First 4 bytes salt / Next 64 bytes shadow value
+ # = 10.7 => password shadow calculation format SALTED-SHA512
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
+ # => shadow binary length 68 bytes
+ # => First 4 bytes salt / Next 64 bytes shadow value
+ # > 10.7 => password shadow calculation format SALTED-SHA512-PBKDF2
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
+ # => shadow binary length 128 bytes
+ # => Salt / Iterations are stored seperately in the same file
+ #
+ # This provider only supports Mac OSX versions 10.7 and above
class Dscl < Chef::Provider::User
- NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$}
- AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$}
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { mac_osx_version_less_than_10_7? == false }
+ a.failure_message(Chef::Exceptions::User, "Chef::Provider::User::Dscl only supports Mac OS X versions 10.7 and above.")
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/dscl") }
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!")
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/plutil") }
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!")
+ end
+
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && mac_osx_version_greater_than_10_7?
+ # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
+ !salted_sha512?(@new_resource.password)
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512 passwords are not supported on Mac 10.8 and above. \
+If you want to set the user password using shadow info make sure you specify a SALTED-SHA512-PBKDF2 shadow hash \
+in 'password', with the associated 'salt' and 'iterations'.")
+ end
+
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password)
+ # salt and iterations should be specified when
+ # SALTED-SHA512-PBKDF2 password shadow hash is given
+ !@new_resource.salt.nil? && !@new_resource.iterations.nil?
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \
+'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.")
+ end
+
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && !mac_osx_version_greater_than_10_7?
+ # On 10.7 SALTED-SHA512-PBKDF2 is not supported
+ !salted_sha512_pbkdf2?(@new_resource.password)
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hashes are not supported on \
+Mac OS X version 10.7. Please specify a SALTED-SHA512 shadow hash in 'password' attribute to set the \
+user password using shadow hash.")
+ end
- def dscl(*args)
- shell_out("dscl . -#{args.join(' ')}")
end
- def safe_dscl(*args)
- result = dscl(*args)
- return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
- return result.stdout
+ def load_current_resource
+ @current_resource = Chef::Resource::User.new(@new_resource.username)
+ @current_resource.username(@new_resource.username)
+
+ @user_info = read_user_info
+ if @user_info
+ @current_resource.uid(dscl_get(@user_info, :uid))
+ @current_resource.gid(dscl_get(@user_info, :gid))
+ @current_resource.home(dscl_get(@user_info, :home))
+ @current_resource.shell(dscl_get(@user_info, :shell))
+ @current_resource.comment(dscl_get(@user_info, :comment))
+ @authentication_authority = dscl_get(@user_info, :auth_authority)
+
+ if @new_resource.password && dscl_get(@user_info, :password) == "********"
+ # A password is set. Let's get the password information from shadow file
+ shadow_hash_binary = dscl_get(@user_info, :shadow_hash)
+
+ # Calling shell_out directly since we want to give an input stream
+ shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
+ shadow_hash = Plist::parse_xml(shadow_hash_xml)
+
+ if shadow_hash["SALTED-SHA512"]
+ # Convert the shadow value from Base64 encoding to hex before consuming them
+ @password_shadow_conversion_algorithm = "SALTED-SHA512"
+ @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
+ elsif shadow_hash["SALTED-SHA512-PBKDF2"]
+ @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
+ # Convert the entropy from Base64 encoding to hex before consuming them
+ @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
+ @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
+ # Convert the salt from Base64 encoding to hex before consuming them
+ @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
+ else
+ raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}")
+ end
+ end
+
+ convert_group_name if @new_resource.gid
+ else
+ @user_exists = false
+ Chef::Log.debug("#{@new_resource} user does not exist")
+ end
+
+ @current_resource
+ end
+
+ #
+ # Provider Actions
+ #
+
+ def create_user
+ dscl_create_user
+ # set_password modifies the plist file of the user directly. So update
+ # the password first before making any modifications to the user.
+ set_password
+ dscl_create_comment
+ dscl_set_uid
+ dscl_set_gid
+ dscl_set_home
+ dscl_set_shell
+ end
+
+ def manage_user
+ # set_password modifies the plist file of the user directly. So update
+ # the password first before making any modifications to the user.
+ set_password if diverged_password?
+ dscl_create_user if diverged?(:username)
+ dscl_create_comment if diverged?(:comment)
+ dscl_set_uid if diverged?(:uid)
+ dscl_set_gid if diverged?(:gid)
+ dscl_set_home if diverged?(:home)
+ dscl_set_shell if diverged?(:shell)
+ end
+
+ #
+ # Action Helpers
+ #
+
+ #
+ # Create a user using dscl
+ #
+ def dscl_create_user
+ run_dscl("create /Users/#{@new_resource.username}")
+ end
+
+ #
+ # Saves the specified Chef user `comment` into RealName attribute
+ # of Mac user.
+ #
+ def dscl_create_comment
+ run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
end
- # This is handled in providers/group.rb by Etc.getgrnam()
- # def user_exists?(user)
- # users = safe_dscl("list /Users")
- # !! ( users =~ Regexp.new("\n#{user}\n") )
- # end
+ #
+ # Sets the user id for the user using dscl.
+ # If a `uid` is not specified, it finds the next available one starting
+ # from 200 if `system` is set, 500 otherwise.
+ #
+ def dscl_set_uid
+ @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
+
+ if uid_used?(@new_resource.uid)
+ raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+ end
+
+ run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
+ end
- # get a free UID greater than 200
+ #
+ # Find the next available uid on the system. starting with 200 if `system` is set,
+ # 500 otherwise.
+ #
def get_free_uid(search_limit=1000)
- uid = nil; next_uid_guess = 200
- users_uids = safe_dscl("list /Users uid")
- while(next_uid_guess < search_limit + 200)
+ uid = nil
+ base_uid = @new_resource.system ? 200 : 500
+ next_uid_guess = base_uid
+ users_uids = run_dscl("list /Users uid")
+ while(next_uid_guess < search_limit + base_uid)
if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
next_uid_guess += 1
else
@@ -60,22 +233,41 @@ class Chef
return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
end
+ #
+ # Returns true if uid is in use by a different account, false otherwise.
+ #
def uid_used?(uid)
return false unless uid
- users_uids = safe_dscl("list /Users uid")
+ users_uids = run_dscl("list /Users uid")
!! ( users_uids =~ Regexp.new("#{Regexp.escape(uid.to_s)}\n") )
end
- def set_uid
- @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
- if uid_used?(@new_resource.uid)
- raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+ #
+ # Sets the group id for the user using dscl. Fails if a group doesn't
+ # exist on the system with given group id.
+ #
+ def dscl_set_gid
+ unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+ begin
+ possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+ rescue Chef::Exceptions::DsclCommandFailed => e
+ raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+ end
+ @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
end
- safe_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
+ run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
end
- def modify_home
- return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?)
+ #
+ # Sets the home directory for the user. If `:manage_home` is set home
+ # directory is managed (moved / created) for the user.
+ #
+ def dscl_set_home
+ if @new_resource.home.nil? || @new_resource.home.empty?
+ run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory")
+ return
+ end
+
if @new_resource.supports[:manage_home]
validate_home_dir_specification!
@@ -87,199 +279,399 @@ class Chef
move_home
end
end
- safe_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
+ run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
end
- def osx_shadow_hash?(string)
- return !! ( string =~ /^[[:xdigit:]]{1240}$/ )
+ def validate_home_dir_specification!
+ unless @new_resource.home =~ /^\//
+ raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
+ end
end
- def osx_salted_sha1?(string)
- return !! ( string =~ /^[[:xdigit:]]{48}$/ )
+ def current_home_exists?
+ ::File.exist?("#{@current_resource.home}")
end
- def guid
- safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").strip
+ def new_home_exists?
+ ::File.exist?("#{@new_resource.home}")
+ end
+
+ def ditto_home
+ skel = "/System/Library/User Template/English.lproj"
+ raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
+ shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ end
+
+ def move_home
+ Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
+
+ src = @current_resource.home
+ FileUtils.mkdir_p(@new_resource.home)
+ files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
+ ::FileUtils.mv(files,@new_resource.home, :force => true)
+ ::FileUtils.rmdir(src)
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
end
- def shadow_hash_set?
- user_data = safe_dscl("read /Users/#{@new_resource.username}")
- if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/
- true
+ #
+ # Sets the shell for the user using dscl.
+ #
+ def dscl_set_shell
+ if @new_resource.shell || ::File.exists?("#{@new_resource.shell}")
+ run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
else
- false
+ run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
end
end
- def modify_password
- if @new_resource.password
- shadow_hash = nil
+ #
+ # Sets the password for the user based on given password parameters.
+ # Chef supports specifying plain-text passwords and password shadow
+ # hash data.
+ #
+ def set_password
+ # Return if there is no password to set
+ return if @new_resource.password.nil?
+
+ shadow_info = prepare_password_shadow_info
+
+ # Shadow info is saved as binary plist. Convert the info to binary plist.
+ shadow_info_binary = StringIO.new
+ command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -",
+ :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
+ command.run_command
+
+ if @user_info.nil?
+ # User is just created. read_user_info() will read the fresh information
+ # for the user with a cache flush. However with experimentation we've seen
+ # that dscl cache is not immediately updated after the creation of the user
+ # This is odd and needs to be investigated further.
+ sleep 3
+ @user_info = read_user_info
+ end
+
+ # Replace the shadow info in user's plist
+ dscl_set(@user_info, :shadow_hash, shadow_info_binary)
+ save_user_info(@user_info)
+ end
- Chef::Log.debug("#{new_resource} updating password")
- if osx_shadow_hash?(@new_resource.password)
- shadow_hash = @new_resource.password.upcase
+ #
+ # Prepares the password shadow info based on the platform version.
+ #
+ def prepare_password_shadow_info
+ shadow_info = { }
+ entropy = nil
+ salt = nil
+ iterations = nil
+
+ if mac_osx_version_10_7?
+ hash_value = if salted_sha512?(@new_resource.password)
+ @new_resource.password
else
- if osx_salted_sha1?(@new_resource.password)
- salted_sha1 = @new_resource.password.upcase
- else
- hex_salt = ""
- OpenSSL::Random.random_bytes(10).each_byte { |b| hex_salt << b.to_i.to_s(16) }
- hex_salt = hex_salt.slice(0...8)
- salt = [hex_salt].pack("H*")
- sha1 = ::OpenSSL::Digest::SHA1.hexdigest(salt+@new_resource.password)
- salted_sha1 = (hex_salt+sha1).upcase
- end
- shadow_hash = String.new("00000000"*155)
- shadow_hash[168] = salted_sha1
+ # Create a random 4 byte salt
+ salt = OpenSSL::Random.random_bytes(4)
+ encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password)
+ hash_value = salt.unpack('H*').first + encoded_password
end
- ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output|
- output.puts shadow_hash
+ shadow_info["SALTED-SHA512"] = StringIO.new
+ shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
+ shadow_info
+ else
+ if salted_sha512_pbkdf2?(@new_resource.password)
+ entropy = convert_to_binary(@new_resource.password)
+ salt = convert_to_binary(@new_resource.salt)
+ iterations = @new_resource.iterations
+ else
+ salt = OpenSSL::Random.random_bytes(32)
+ iterations = @new_resource.iterations # Use the default if not specified by the user
+
+ entropy = OpenSSL::PKCS5::pbkdf2_hmac(
+ @new_resource.password,
+ salt,
+ iterations,
+ 128,
+ OpenSSL::Digest::SHA512.new
+ )
end
- unless shadow_hash_set?
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'")
+ pbkdf_info = { }
+ pbkdf_info["entropy"] = StringIO.new
+ pbkdf_info["entropy"].string = entropy
+ pbkdf_info["salt"] = StringIO.new
+ pbkdf_info["salt"].string = salt
+ pbkdf_info["iterations"] = iterations
+
+ shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
+ end
+
+ shadow_info
+ end
+
+ #
+ # Removes the user from the system after removing user from his groups
+ # and deleting home directory if needed.
+ #
+ def remove_user
+ if @new_resource.supports[:manage_home]
+ # Remove home directory
+ FileUtils.rm_rf(@current_resource.home)
+ end
+
+ # Remove the user from its groups
+ run_dscl("list /Groups").each_line do |group|
+ if member_of_group?(group.chomp)
+ run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'")
end
end
+
+ # Remove user account
+ run_dscl("delete /Users/#{@new_resource.username}")
end
- def load_current_resource
- super
- raise Chef::Exceptions::User, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl")
+ #
+ # Locks the user.
+ #
+ def lock_user
+ run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
end
- def create_user
- dscl_create_user
- dscl_create_comment
- set_uid
- dscl_set_gid
- modify_home
- dscl_set_shell
- modify_password
+ #
+ # Unlocks the user
+ #
+ def unlock_user
+ auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
+ run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
end
- def manage_user
- dscl_create_user if diverged?(:username)
- dscl_create_comment if diverged?(:comment)
- set_uid if diverged?(:uid)
- dscl_set_gid if diverged?(:gid)
- modify_home if diverged?(:home)
- dscl_set_shell if diverged?(:shell)
- modify_password if diverged?(:password)
+ #
+ # Returns true if the user is locked, false otherwise.
+ #
+ def locked?
+ if @authentication_authority
+ !!(@authentication_authority =~ /DisabledUser/ )
+ else
+ false
+ end
end
- def dscl_create_user
- safe_dscl("create /Users/#{@new_resource.username}")
+ #
+ # This is the interface base User provider requires to provide idempotency.
+ #
+ def check_lock
+ return @locked = locked?
end
- def dscl_create_comment
- safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
+ #
+ # Helper functions
+ #
+
+ #
+ # Returns true if the system state and desired state is different for
+ # given attribute.
+ #
+ def diverged?(parameter)
+ parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
end
- def dscl_set_gid
- unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
- begin
- possible_gid = safe_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
- rescue Chef::Exceptions::DsclCommandFailed => e
- raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
- end
- @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
- end
- safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
+ def parameter_updated?(parameter)
+ not (@new_resource.send(parameter) == @current_resource.send(parameter))
end
- def dscl_set_shell
- if @new_resource.password || ::File.exists?("#{@new_resource.shell}")
- safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
+ #
+ # We need a special check function for password since we support both
+ # plain text and shadow hash data.
+ #
+ # Checks if password needs update based on platform version and the
+ # type of the password specified.
+ #
+ def diverged_password?
+ return false if @new_resource.password.nil?
+
+ # Dscl provider supports both plain text passwords and shadow hashes.
+ if mac_osx_version_10_7?
+ if salted_sha512?(@new_resource.password)
+ diverged?(:password)
+ else
+ !salted_sha512_password_match?
+ end
else
- safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+ # When a system is upgraded to a version 10.7+ shadow hashes of the users
+ # will be updated when the user logs in. So it's possible that we will have
+ # SALTED-SHA512 password in the current_resource. In that case we will force
+ # password to be updated.
+ return true if salted_sha512?(@current_resource.password)
+
+ if salted_sha512_pbkdf2?(@new_resource.password)
+ diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
+ else
+ !salted_sha512_pbkdf2_password_match?
+ end
end
end
- def remove_user
- if @new_resource.supports[:manage_home]
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
- if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY)
- #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory")
- #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"")
- nfs_home = nfs_home_match[1]
- FileUtils.rm_rf(nfs_home)
- end
- end
- # remove the user from its groups
- groups = []
- Etc.group do |group|
- groups << group.name if group.mem.include?(@new_resource.username)
+ #
+ # Returns true if user is member of the specified group, false otherwise.
+ #
+ def member_of_group?(group_name)
+ membership_info = ""
+ begin
+ membership_info = run_dscl("read /Groups/#{group_name}")
+ rescue Chef::Exceptions::DsclCommandFailed
+ # Raised if the group doesn't contain any members
end
- groups.each do |group_name|
- safe_dscl("delete /Groups/#{group_name} GroupMembership '#{@new_resource.username}'")
- end
- # remove user account
- safe_dscl("delete /Users/#{@new_resource.username}")
+ # Output is something like:
+ # GroupMembership: root admin etc
+ members = membership_info.split(" ")
+ members.shift # Get rid of GroupMembership: string
+ members.include?(@new_resource.username)
end
- def locked?
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
- if auth_authority_md = AUTHENTICATION_AUTHORITY.match(user_info)
- !!(auth_authority_md[1] =~ /DisabledUser/ )
- else
- false
+ #
+ # DSCL Helper functions
+ #
+
+ # A simple map of Chef's terms to DSCL's terms.
+ DSCL_PROPERTY_MAP = {
+ :uid => "generateduid",
+ :gid => "gid",
+ :home => "home",
+ :shell => "shell",
+ :comment => "realname",
+ :password => "passwd",
+ :auth_authority => "authentication_authority",
+ :shadow_hash => "ShadowHashData"
+ }.freeze
+
+ # Directory where the user plist files are stored for versions 10.7 and above
+ USER_PLIST_DIRECTORY = "/var/db/dslocal/nodes/Default/users".freeze
+
+ #
+ # Reads the user plist and returns a hash keyed with DSCL properties specified
+ # in DSCL_PROPERTY_MAP. Return nil if the user is not found.
+ #
+ def read_user_info
+ user_info = nil
+
+ # We flush the cache here in order to make sure that we read fresh information
+ # for the user.
+ shell_out("dscacheutil '-flushcache'")
+
+ begin
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
+ user_info = Plist::parse_xml(user_plist_info)
+ rescue Chef::Exceptions::PlistUtilCommandFailed
end
+
+ user_info
end
- def check_lock
- return @locked = locked?
+ #
+ # Saves the given hash keyed with DSCL properties specified
+ # in DSCL_PROPERTY_MAP to the disk.
+ #
+ def save_user_info(user_info)
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ Plist::Emit.save_plist(user_info, user_plist_file)
+ run_plutil("convert binary1 #{user_plist_file}")
end
- def lock_user
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ #
+ # Sets a value in user information hash using Chef attributes as keys.
+ #
+ def dscl_set(user_hash, key, value)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ user_hash[DSCL_PROPERTY_MAP[key]] = [ value ]
+ user_hash
end
- def unlock_user
- auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority")
- auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"")
- safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ #
+ # Gets a value from user information hash using Chef attributes as keys.
+ #
+ def dscl_get(user_hash, key)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ # DSCL values are set as arrays
+ value = user_hash[DSCL_PROPERTY_MAP[key]]
+ value.nil? ? value : value.first
end
- def validate_home_dir_specification!
- unless @new_resource.home =~ /^\//
- raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
- end
+ #
+ # System Helpets
+ #
+
+ def mac_osx_version
+ # This provider will only be invoked on node[:platform] == "mac_os_x"
+ # We do not check or assert that here.
+ node[:platform_version]
end
- def current_home_exists?
- ::File.exist?("#{@current_resource.home}")
+ def mac_osx_version_10_7?
+ mac_osx_version.start_with?("10.7.")
end
- def new_home_exists?
- ::File.exist?("#{@new_resource.home}")
+ def mac_osx_version_less_than_10_7?
+ versions = mac_osx_version.split(".")
+ # Make integer comparison in order not to report 10.10 less than 10.7
+ (versions[0].to_i <= 10 && versions[1].to_i < 7)
end
- def ditto_home
- skel = "/System/Library/User Template/English.lproj"
- raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
- shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ def mac_osx_version_greater_than_10_7?
+ versions = mac_osx_version.split(".")
+ # Make integer comparison in order not to report 10.10 less than 10.7
+ (versions[0].to_i >= 10 && versions[1].to_i > 7)
end
- def move_home
- Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
+ def run_dscl(*args)
+ result = shell_out("dscl . -#{args.join(' ')}")
+ return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
+ result.stdout
+ end
- src = @current_resource.home
- FileUtils.mkdir_p(@new_resource.home)
- files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
- ::FileUtils.mv(files,@new_resource.home, :force => true)
- ::FileUtils.rmdir(src)
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ def run_plutil(*args)
+ result = shell_out("plutil -#{args.join(' ')}")
+ raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0
+ result.stdout
end
- def diverged?(parameter)
- parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
+ def convert_binary_plist_to_xml(binary_plist_string)
+ Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout
end
- def parameter_updated?(parameter)
- not (@new_resource.send(parameter) == @current_resource.send(parameter))
+ def convert_to_binary(string)
+ string.unpack('a2'*(string.size/2)).collect { |i| i.hex.chr }.join
+ end
+
+ def salted_sha512?(string)
+ !!(string =~ /^[[:xdigit:]]{136}$/)
+ end
+
+ def salted_sha512_password_match?
+ # Salt is included in the first 4 bytes of shadow data
+ salt = @current_resource.password.slice(0,8)
+ shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password)
+ @current_resource.password == salt + shadow
end
+
+ def salted_sha512_pbkdf2?(string)
+ !!(string =~ /^[[:xdigit:]]{256}$/)
+ end
+
+ def salted_sha512_pbkdf2_password_match?
+ salt = convert_to_binary(@current_resource.salt)
+
+ OpenSSL::PKCS5::pbkdf2_hmac(
+ @new_resource.password,
+ salt,
+ @current_resource.iterations,
+ 128,
+ OpenSSL::Digest::SHA512.new
+ ).unpack('H*').first == @current_resource.password
+ end
+
end
end
end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index 350f3ff4c0..66ef30c349 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -17,6 +17,7 @@
#
require 'chef/provider/user'
+require 'chef/exceptions'
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
require 'chef/util/windows/net_user'
end
@@ -28,7 +29,7 @@ class Chef
def initialize(new_resource,run_context)
super
- @net_user = Chef::Util::Windows::NetUser.new(@new_resource.name)
+ @net_user = Chef::Util::Windows::NetUser.new(@new_resource.username)
end
def load_current_resource
@@ -37,17 +38,16 @@ class Chef
user_info = nil
begin
user_info = @net_user.get_info
- rescue
- @user_exists = false
- Chef::Log.debug("#{@new_resource} does not exist")
- end
- if user_info
@current_resource.uid(user_info[:user_id])
@current_resource.gid(user_info[:primary_group_id])
@current_resource.comment(user_info[:full_name])
@current_resource.home(user_info[:home_dir])
@current_resource.shell(user_info[:script_path])
+ rescue Chef::Exceptions::UserIDNotFound => e
+ # e.message should be "The user name could not be found" but checking for that could cause a localization bug
+ @user_exists = false
+ Chef::Log.debug("#{@new_resource} does not exist (#{e.message})")
end
@current_resource
diff --git a/lib/chef/provider/whyrun_safe_ruby_block.rb b/lib/chef/provider/whyrun_safe_ruby_block.rb
index 4b491a4f60..e5f35debd7 100644
--- a/lib/chef/provider/whyrun_safe_ruby_block.rb
+++ b/lib/chef/provider/whyrun_safe_ruby_block.rb
@@ -19,7 +19,7 @@
class Chef
class Provider
class WhyrunSafeRubyBlock < Chef::Provider::RubyBlock
- def action_create
+ def action_run
@new_resource.block.call
@new_resource.updated_by_last_action(true)
@run_context.events.resource_update_applied(@new_resource, :create, "execute the whyrun_safe_ruby_block #{@new_resource.name}")
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 5b95d80590..32578da5ab 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -69,7 +69,6 @@ class Chef
@run_context = run_context
# TODO: 5/19/2010 cw/tim: determine whether this can be removed
@params = Hash.new
- @node = deprecated_ivar(run_context.node, :node, :warn)
end
# Used in DSL mixins
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 6adf937f53..70abfbcdb0 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -23,7 +23,7 @@ require 'chef/dsl/data_query'
require 'chef/dsl/registry_helper'
require 'chef/dsl/reboot_pending'
require 'chef/mixin/convert_to_class_name'
-require 'chef//guard_interpreter/resource_guard_interpreter'
+require 'chef/guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
require 'chef/resource/conditional_action_not_nothing'
require 'chef/resource_collection'
@@ -121,8 +121,8 @@ F
end
- FORBIDDEN_IVARS = [:@run_context, :@node, :@not_if, :@only_if, :@enclosing_provider]
- HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider]
+ FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider]
+ HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider]
include Chef::DSL::DataQuery
include Chef::Mixin::ParamsValidate
@@ -253,8 +253,6 @@ F
@guard_interpreter = :default
@elapsed_time = 0
@sensitive = false
-
- @node = run_context ? deprecated_ivar(run_context.node, :node, :warn) : nil
end
# Returns a Hash of attribute => value for the state attributes declared in
@@ -660,6 +658,9 @@ F
end
ensure
@elapsed_time = Time.now - start_time
+ # Reporting endpoint doesn't accept a negative resource duration so set it to 0.
+ # A negative value can occur when a resource changes the system time backwards
+ @elapsed_time = 0 if @elapsed_time < 0
events.resource_completed(self)
end
end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index c7a8d44181..70ef62ae8a 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -31,17 +31,26 @@ class Chef
provides :package, :on_platforms => ["freebsd"]
+ attr_accessor :created_as_type
def initialize(name, run_context=nil)
super
@resource_name = :freebsd_package
+ @created_as_type = "freebsd_package"
end
def after_created
assign_provider
end
-
+ # This resource can be invoked with multiple names package & freebsd_package.
+ # We override the to_s method to ensure the key in resource collection
+ # matches the type resource is declared as using created_as_type. This
+ # logic can be removed once Chef does this for all resource in Chef 12:
+ # https://github.com/opscode/chef/issues/1817
+ def to_s
+ "#{created_as_type}[#{name}]"
+ end
private
@@ -68,4 +77,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 9eafe07253..275c069f61 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -33,6 +33,7 @@ class Chef
@mount_point = name
@device = nil
@device_type = :device
+ @fsck_device = '-'
@fstype = "auto"
@options = ["defaults"]
@dump = 0
@@ -77,6 +78,14 @@ class Chef
)
end
+ def fsck_device(arg=nil)
+ set_or_return(
+ :fsck_device,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
def fstype(arg=nil)
set_or_return(
:fstype,
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 91782e4114..87c217b4cc 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -40,6 +40,7 @@ class Chef
@allowed_actions.push(:checkout, :export, :sync, :diff, :log)
@action = [:sync]
@checkout_branch = "deploy"
+ @environment = nil
end
def destination(arg=nil)
@@ -172,6 +173,15 @@ class Chef
)
end
+ def environment(arg=nil)
+ set_or_return(
+ :environment,
+ arg,
+ :kind_of => [ Hash ]
+ )
+ end
+
+ alias :env :environment
end
end
end
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 05c076229f..9d6e857de7 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -45,6 +45,8 @@ class Chef
:manage_home => false,
:non_unique => false
}
+ @iterations = 27855
+ @salt = nil
@allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
end
@@ -106,6 +108,22 @@ class Chef
)
end
+ def salt(arg=nil)
+ set_or_return(
+ :salt,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def iterations(arg=nil)
+ set_or_return(
+ :iterations,
+ arg,
+ :kind_of => [ Integer ]
+ )
+ end
+
def system(arg=nil)
set_or_return(
:system,
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
new file mode 100644
index 0000000000..5ed8e76cbd
--- /dev/null
+++ b/lib/chef/resource/windows_service.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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/resource/service'
+
+class Chef
+ class Resource
+ class WindowsService < Chef::Resource::Service
+
+ # Until #1773 is resolved, you need to manually specify the windows_service resource
+ # to use action :configure_startup and attribute startup_type
+
+ # provides :service, :on_platforms => ["windows"]
+
+ identity_attr :service_name
+
+ state_attrs :enabled, :running
+
+ def initialize(name, run_context=nil)
+ super
+ @resource_name = :windows_service
+ @provider = Chef::Provider::Service::Windows
+ @allowed_actions.push(:configure_startup)
+ @startup_type = :automatic
+ end
+
+ def startup_type(arg=nil)
+ # Set-Service arguments are automatic and manual
+ # Win32::Service returns 'auto start' or 'demand start' respectively, which the provider currently uses
+ set_or_return(
+ :startup_type,
+ arg,
+ :equal_to => [ :automatic, :manual, :disabled ]
+ )
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 2cbd61cb0c..cc14a03962 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -67,24 +67,33 @@ class Chef
alias_method :push, :<<
def insert(resource)
- is_chef_resource(resource)
if @insert_after_idx
# in the middle of executing a run, so any resources inserted now should
# be placed after the most recent addition done by the currently executing
# resource
- @resources.insert(@insert_after_idx + 1, resource)
- # update name -> location mappings and register new resource
- @resources_by_name.each_key do |key|
- @resources_by_name[key] += 1 if @resources_by_name[key] > @insert_after_idx
- end
- @resources_by_name[resource.to_s] = @insert_after_idx + 1
+ insert_at(@insert_after_idx + 1, resource)
@insert_after_idx += 1
else
+ is_chef_resource(resource)
@resources << resource
@resources_by_name[resource.to_s] = @resources.length - 1
end
end
+ def insert_at(insert_at_index, *resources)
+ resources.each do |resource|
+ is_chef_resource(resource)
+ end
+ @resources.insert(insert_at_index, *resources)
+ # update name -> location mappings and register new resource
+ @resources_by_name.each_key do |key|
+ @resources_by_name[key] += resources.size if @resources_by_name[key] >= insert_at_index
+ end
+ resources.each_with_index do |resource, i|
+ @resources_by_name[resource.to_s] = insert_at_index + i
+ end
+ end
+
def each
@resources.each do |resource|
yield resource
@@ -249,7 +258,7 @@ class Chef
def is_chef_resource(arg)
unless arg.kind_of?(Chef::Resource)
- raise ArgumentError, "Members must be Chef::Resource's"
+ raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource"
end
true
end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 01e8d63040..93ff682288 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -64,6 +64,7 @@ require 'chef/resource/ruby_block'
require 'chef/resource/scm'
require 'chef/resource/script'
require 'chef/resource/service'
+require 'chef/resource/windows_service'
require 'chef/resource/subversion'
require 'chef/resource/smartos_package'
require 'chef/resource/template'
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 4869ec1484..cc43efe1b1 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -23,6 +23,7 @@ require 'chef/node'
require 'chef/role'
require 'chef/data_bag'
require 'chef/data_bag_item'
+require 'chef/exceptions'
class Chef
class Search
@@ -34,17 +35,112 @@ class Chef
@rest = Chef::REST.new(url ||Chef::Config[:chef_server_url])
end
- # Search Solr for objects of a given type, for a given query. If you give
- # it a block, it will handle the paging for you dynamically.
- def search(type, query="*:*", sort='X_CHEF_id_CHEF_X asc', start=0, rows=1000, &block)
- raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol))
- response = @rest.get_rest("search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}")
- if block
- response["rows"].each { |o| block.call(o) unless o.nil?}
+ # This search is only kept for backwards compatibility, since the results of the
+ # new filtered search method will be in a slightly different format
+ def partial_search(type, query='*:*', *args, &block)
+ Chef::Log.warn("DEPRECATED: The 'partial_search' api is deprecated, please use the search api with 'filter_result'")
+ # accept both types of args
+ if args.length == 1 && args[0].is_a?(Hash)
+ args_hash = args[0].dup
+ # partial_search implemented in the partial search cookbook uses the
+ # arg hash :keys instead of :filter_result to filter returned data
+ args_hash[:filter_result] = args_hash[:keys]
+ else
+ args_hash = {}
+ args_hash[:sort] = args[0] if args.length >= 1
+ args_hash[:start] = args[1] if args.length >= 2
+ args_hash[:rows] = args[2] if args.length >= 3
+ end
+
+ unless block.nil?
+ raw_results = search(type,query,args_hash)
+ else
+ raw_results = search(type,query,args_hash,&block)
+ end
+ results = Array.new
+ raw_results[0].each do |r|
+ results << r["data"]
+ end
+ return results
+ end
+
+ #
+ # New search input, designed to be backwards compatible with the old method signature
+ # 'type' and 'query' are the same as before, args now will accept either a Hash of
+ # search arguments with symbols as the keys (ie :sort, :start, :rows) and a :filter_result
+ # option.
+ #
+ # :filter_result should be in the format of another Hash with the structure of:
+ # {
+ # :returned_name1 => ["path", "to", "variable"],
+ # :returned_name2 => ["shorter", "path"]
+ # }
+ # a real world example might be something like:
+ # {
+ # :ip_address => ["ipaddress"],
+ # :ruby_version => ["languages", "ruby", "version"]
+ # }
+ # this will bring back 2 variables 'ip_address' and 'ruby_version' with whatever value was found
+ # an example of the returned json may be:
+ # {"ip_address":"127.0.0.1", "ruby_version": "1.9.3"}
+ #
+ def search(type, query='*:*', *args, &block)
+ validate_type(type)
+ validate_args(args)
+
+ scrubbed_args = Hash.new
+
+ # argify everything
+ if args[0].kind_of?(Hash)
+ scrubbed_args = args[0]
+ else
+ # This api will be deprecated in a future release
+ scrubbed_args = { :sort => args[0], :start => args[1], :rows => args[2] }
+ end
+
+ # set defaults, if they haven't been set yet.
+ scrubbed_args[:sort] ||= 'X_CHEF_id_CHEF_X asc'
+ scrubbed_args[:start] ||= 0
+ scrubbed_args[:rows] ||= 1000
+
+ do_search(type, query, scrubbed_args, &block)
+ end
+
+ def list_indexes
+ @rest.get_rest("search")
+ end
+
+ private
+ def validate_type(t)
+ unless t.kind_of?(String) || t.kind_of?(Symbol)
+ msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." +
+ "Useage: search(:node, QUERY, [OPTIONAL_ARGS])" +
+ " `knife search environment QUERY (options)`"
+ raise Chef::Exceptions::InvalidSearchQuery, msg
+ end
+ end
+
+ def validate_args(a)
+ max_args = 3
+ raise Chef::Exceptions::InvalidSearchQuery, "Too many arguments! (#{a.size} for <= #{max_args})" if a.size > max_args
+ end
+
+ def escape(s)
+ s && URI.escape(s.to_s)
+ end
+
+ # new search api that allows for a cleaner implementation of things like return filters
+ # (formerly known as 'partial search').
+ # Also args should never be nil, but that is required for Ruby 1.8 compatibility
+ def do_search(type, query="*:*", args=nil, &block)
+ query_string = create_query_string(type, query, args)
+ response = call_rest_service(query_string, args)
+ unless block.nil?
+ response["rows"].each { |rowset| block.call(rowset) unless rowset.nil?}
unless (response["start"] + response["rows"].length) >= response["total"]
- nstart = response["start"] + rows
- search(type, query, sort, nstart, rows, &block)
+ args[:start] = response["start"] + args[:rows]
+ do_search(type, query, args, &block)
end
true
else
@@ -52,14 +148,26 @@ class Chef
end
end
- def list_indexes
- @rest.get_rest("search")
+ # create the full rest url string
+ def create_query_string(type, query, args)
+ # create some default variables just so we don't break backwards compatibility
+ sort = args[:sort]
+ start = args[:start]
+ rows = args[:rows]
+
+ return "search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}"
end
- private
- def escape(s)
- s && URI.escape(s.to_s)
+ def call_rest_service(query_string, args)
+ if args.key?(:filter_result)
+ response = @rest.post_rest(query_string, args[:filter_result])
+ response_rows = response['rows'].map { |row| row['data'] }
+ else
+ response = @rest.get_rest(query_string)
+ response_rows = response['rows']
end
+ return response
+ end
end
end
end
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 534a9087ae..e9fb4e7773 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -16,15 +16,50 @@
# limitations under the License.
#
-require 'chef/platform'
-require 'chef/exceptions'
-
class Chef
class Util
class PathHelper
# Maximum characters in a standard Windows path (260 including drive letter and NUL)
WIN_MAX_PATH = 259
+ def self.dirname(path)
+ if Chef::Platform.windows?
+ # Find the first slash, not counting trailing slashes
+ end_slash = path.size
+ while true
+ slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
+ if !slash
+ return end_slash == path.size ? '.' : path_separator
+ elsif slash == end_slash - 1
+ end_slash = slash
+ else
+ return path[0..slash-1]
+ end
+ end
+ else
+ ::File.dirname(path)
+ end
+ end
+
+ BACKSLASH = '\\'.freeze
+
+ def self.path_separator
+ if Chef::Platform.windows?
+ File::ALT_SEPARATOR || BACKSLASH
+ else
+ File::SEPARATOR
+ end
+ end
+
+ def self.join(*args)
+ args.flatten.inject do |joined_path, component|
+ # Joined path ends with /
+ joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
+ component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
+ joined_path += "#{path_separator}#{component}"
+ end
+ end
+
def self.validate_path(path)
if Chef::Platform.windows?
unless printable?(path)
@@ -32,7 +67,7 @@ class Chef
Chef::Log.error(msg)
raise Chef::Exceptions::ValidationFailed, msg
end
-
+
if windows_max_length_exceeded?(path)
Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
path.insert(0, "\\\\?\\")
@@ -50,7 +85,7 @@ class Chef
return true
end
end
-
+
false
end
@@ -75,7 +110,7 @@ class Chef
if Chef::Platform.windows?
# Add the \\?\ API prefix on Windows unless add_prefix is false
# Downcase on Windows where paths are still case-insensitive
- abs_path.gsub!(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+ abs_path.gsub!(::File::SEPARATOR, path_separator)
if add_prefix && abs_path !~ /^\\\\?\\/
abs_path.insert(0, "\\\\?\\")
end
@@ -86,9 +121,22 @@ class Chef
abs_path
end
+ def self.cleanpath(path)
+ path = Pathname.new(path).cleanpath.to_s
+ # ensure all forward slashes are backslashes
+ if Chef::Platform.windows?
+ path = path.gsub(File::SEPARATOR, path_separator)
+ end
+ path
+ end
+
def self.paths_eql?(path1, path2)
canonical_path(path1) == canonical_path(path2)
end
end
end
end
+
+# Break a require loop when require chef/util/path_helper
+require 'chef/platform'
+require 'chef/exceptions'
diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 5cca348c8e..5df1a8aaa4 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -17,6 +17,7 @@
#
require 'chef/util/windows'
+require 'chef/exceptions'
#wrapper around a subset of the NetUser* APIs.
#nothing Chef specific, but not complete enough to be its own gem, so util for now.
@@ -137,7 +138,9 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
ptr = 0.chr * PTR_SIZE
rc = NetUserGetInfo.call(nil, @name, 3, ptr)
- if rc != NERR_Success
+ if rc == NERR_UserNotFound
+ raise Chef::Exceptions::UserIDNotFound, get_last_error(rc)
+ elsif rc != NERR_Success
raise ArgumentError, get_last_error(rc)
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index ea1002ee37..be449d4fa0 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -17,7 +17,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.0.0.alpha.0'
+ VERSION = '12.0.0.alpha.1'
end
#
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index cb028020cf..eeb2b078a4 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -33,6 +33,7 @@ class Chef
MAX_PREFERRED_LENGTH = 0xFFFF
NERR_Success = 0
+ NERR_UserNotFound = 2221
ffi_lib "netapi32"
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
new file mode 100644
index 0000000000..6715d4eec2
--- /dev/null
+++ b/lib/chef/workstation_config_loader.rb
@@ -0,0 +1,177 @@
+#
+# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Copyright:: Copyright (c) 2014 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/config_fetcher'
+require 'chef/config'
+require 'chef/null_logger'
+
+class Chef
+
+ class WorkstationConfigLoader
+
+ # Path to a config file requested by user, (e.g., via command line option). Can be nil
+ attr_reader :explicit_config_file
+
+ # TODO: initialize this with a logger for Chef and Knife
+ def initialize(explicit_config_file, logger=nil)
+ @explicit_config_file = explicit_config_file
+ @config_location = nil
+ @logger = logger || NullLogger.new
+ end
+
+ def no_config_found?
+ config_location.nil?
+ end
+
+ def config_location
+ @config_location ||= (explicit_config_file || locate_local_config)
+ end
+
+ def chef_config_dir
+ if @chef_config_dir.nil?
+ @chef_config_dir = false
+ full_path = working_directory.split(File::SEPARATOR)
+ (full_path.length - 1).downto(0) do |i|
+ candidate_directory = File.join(full_path[0..i] + [".chef" ])
+ if File.exist?(candidate_directory) && File.directory?(candidate_directory)
+ @chef_config_dir = candidate_directory
+ break
+ end
+ end
+ end
+ @chef_config_dir
+ end
+
+ def load
+ # Ignore it if there's no explicit_config_file and can't find one at a
+ # default path.
+ return false if config_location.nil?
+
+ if explicit_config_file && !path_exists?(config_location)
+ raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist"
+ end
+
+ # Have to set Chef::Config.config_file b/c other config is derived from it.
+ Chef::Config.config_file = config_location
+ read_config(IO.read(config_location), config_location)
+ end
+
+ # (Private API, public for test purposes)
+ def env
+ ENV
+ end
+
+ # (Private API, public for test purposes)
+ def path_exists?(path)
+ Pathname.new(path).expand_path.exist?
+ end
+
+ private
+
+ def have_config?(path)
+ if path_exists?(path)
+ logger.info("Using config at #{path}")
+ true
+ else
+ logger.debug("Config not found at #{path}, trying next option")
+ false
+ end
+ end
+
+ def locate_local_config
+ candidate_configs = []
+
+ # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
+ if env['KNIFE_HOME']
+ candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
+ candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
+ end
+ # Look for $PWD/knife.rb
+ if Dir.pwd
+ candidate_configs << File.join(Dir.pwd, 'config.rb')
+ candidate_configs << File.join(Dir.pwd, 'knife.rb')
+ end
+ # Look for $UPWARD/.chef/knife.rb
+ if chef_config_dir
+ candidate_configs << File.join(chef_config_dir, 'config.rb')
+ candidate_configs << File.join(chef_config_dir, 'knife.rb')
+ end
+ # Look for $HOME/.chef/knife.rb
+ if env['HOME']
+ candidate_configs << File.join(env['HOME'], '.chef', 'config.rb')
+ candidate_configs << File.join(env['HOME'], '.chef', 'knife.rb')
+ end
+
+ candidate_configs.find do | candidate_config |
+ have_config?(candidate_config)
+ end
+ end
+
+ def working_directory
+ a = if Chef::Platform.windows?
+ env['CD']
+ else
+ env['PWD']
+ end || Dir.pwd
+
+ a
+ end
+
+ def read_config(config_content, config_file_path)
+ Chef::Config.from_string(config_content, config_file_path)
+ rescue SignalException
+ raise
+ rescue SyntaxError => e
+ message = ""
+ message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
+ message << "#{e.class.name}: #{e.message}\n"
+ if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
+ line = file_line[/:([\d]+)$/, 1].to_i
+ message << highlight_config_error(config_file_path, line)
+ end
+ raise Exceptions::ConfigurationError, message
+ rescue Exception => e
+ message = "You have an error in your config file #{config_file_path}\n\n"
+ message << "#{e.class.name}: #{e.message}\n"
+ filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
+ filtered_trace.each {|bt_line| message << " " << bt_line << "\n" }
+ if !filtered_trace.empty?
+ line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
+ message << highlight_config_error(config_file_path, line_nr.to_i)
+ end
+ raise Exceptions::ConfigurationError, message
+ end
+
+
+ def highlight_config_error(file, line)
+ config_file_lines = []
+ IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
+ if line == 1
+ lines = config_file_lines[0..3]
+ else
+ lines = config_file_lines[Range.new(line - 2, line)]
+ end
+ "Relevant file content:\n" + lines.join("\n") + "\n"
+ end
+
+ def logger
+ @logger
+ end
+
+ end
+end