summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chef.rb2
-rw-r--r--lib/chef/api_client.rb160
-rw-r--r--lib/chef/api_client_v1.rb325
-rw-r--r--lib/chef/application.rb2
-rw-r--r--lib/chef/application/apply.rb18
-rw-r--r--lib/chef/application/client.rb6
-rw-r--r--lib/chef/application/solo.rb2
-rw-r--r--lib/chef/application/windows_service_manager.rb31
-rw-r--r--lib/chef/chef_class.rb58
-rw-r--r--lib/chef/chef_fs/config.rb46
-rw-r--r--lib/chef/chef_fs/file_pattern.rb19
-rw-r--r--lib/chef/chef_fs/file_system/acl_dir.rb7
-rw-r--r--lib/chef/chef_fs/file_system/acls_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_dir.rb5
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb7
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb11
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb11
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb27
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb13
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb26
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb11
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb14
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/environments_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/file_system_entry.rb11
-rw-r--r--lib/chef/chef_fs/file_system/memory_dir.rb5
-rw-r--r--lib/chef/chef_fs/file_system/multiplexed_dir.rb15
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb13
-rw-r--r--lib/chef/chef_fs/knife.rb42
-rw-r--r--lib/chef/chef_fs/path_utils.rb99
-rw-r--r--lib/chef/client.rb21
-rw-r--r--lib/chef/config.rb33
-rw-r--r--lib/chef/constants.rb27
-rw-r--r--lib/chef/cookbook/metadata.rb28
-rw-r--r--lib/chef/cookbook/synchronizer.rb2
-rw-r--r--lib/chef/cookbook_version.rb6
-rw-r--r--lib/chef/delayed_evaluator.rb21
-rw-r--r--lib/chef/deprecation/mixin/template.rb3
-rw-r--r--lib/chef/deprecation/provider/cookbook_file.rb2
-rw-r--r--lib/chef/deprecation/provider/file.rb2
-rw-r--r--lib/chef/deprecation/provider/remote_file.rb3
-rw-r--r--lib/chef/deprecation/provider/template.rb2
-rw-r--r--lib/chef/deprecation/warnings.rb7
-rw-r--r--lib/chef/dsl/reboot_pending.rb2
-rw-r--r--lib/chef/dsl/recipe.rb33
-rw-r--r--lib/chef/dsl/resources.rb10
-rw-r--r--lib/chef/event_dispatch/base.rb73
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb24
-rw-r--r--lib/chef/event_dispatch/dsl.rb64
-rw-r--r--lib/chef/exceptions.rb31
-rw-r--r--lib/chef/formatters/base.rb3
-rw-r--r--lib/chef/formatters/doc.rb46
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb16
-rw-r--r--lib/chef/formatters/minimal.rb4
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb5
-rw-r--r--lib/chef/http/http_request.rb2
-rw-r--r--lib/chef/knife.rb90
-rw-r--r--lib/chef/knife/bootstrap.rb6
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb1
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb4
-rw-r--r--lib/chef/knife/bootstrap/templates/archlinux-gems.erb76
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-aix.erb72
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-full.erb10
-rw-r--r--lib/chef/knife/client_bulk_delete.rb4
-rw-r--r--lib/chef/knife/client_create.rb8
-rw-r--r--lib/chef/knife/client_delete.rb6
-rw-r--r--lib/chef/knife/client_edit.rb12
-rw-r--r--lib/chef/knife/client_list.rb4
-rw-r--r--lib/chef/knife/client_reregister.rb4
-rw-r--r--lib/chef/knife/client_show.rb4
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb7
-rw-r--r--lib/chef/knife/core/custom_manifest_loader.rb69
-rw-r--r--lib/chef/knife/core/gem_glob_loader.rb138
-rw-r--r--lib/chef/knife/core/hashed_command_loader.rb80
-rw-r--r--lib/chef/knife/core/object_loader.rb1
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb277
-rw-r--r--lib/chef/knife/node_run_list_remove.rb13
-rw-r--r--lib/chef/knife/null.rb10
-rw-r--r--lib/chef/knife/osc_user_create.rb6
-rw-r--r--lib/chef/knife/osc_user_delete.rb4
-rw-r--r--lib/chef/knife/osc_user_edit.rb6
-rw-r--r--lib/chef/knife/osc_user_list.rb4
-rw-r--r--lib/chef/knife/osc_user_reregister.rb4
-rw-r--r--lib/chef/knife/osc_user_show.rb4
-rw-r--r--lib/chef/knife/rehash.rb62
-rw-r--r--lib/chef/knife/ssl_check.rb5
-rw-r--r--lib/chef/knife/user_create.rb6
-rw-r--r--lib/chef/knife/user_delete.rb8
-rw-r--r--lib/chef/knife/user_edit.rb9
-rw-r--r--lib/chef/knife/user_list.rb4
-rw-r--r--lib/chef/knife/user_reregister.rb4
-rw-r--r--lib/chef/knife/user_show.rb4
-rw-r--r--lib/chef/log.rb6
-rw-r--r--lib/chef/mixin/deprecation.rb16
-rw-r--r--lib/chef/mixin/params_validate.rb497
-rw-r--r--lib/chef/mixin/template.rb47
-rw-r--r--lib/chef/mixin/wide_string.rb72
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb54
-rw-r--r--lib/chef/mixin/windows_env_helper.rb5
-rw-r--r--lib/chef/node.rb19
-rw-r--r--lib/chef/node_map.rb127
-rw-r--r--lib/chef/osc_user.rb194
-rw-r--r--lib/chef/platform/handler_map.rb40
-rw-r--r--lib/chef/platform/priority_map.rb41
-rw-r--r--lib/chef/platform/provider_handler_map.rb (renamed from lib/chef/mixin/wstring.rb)20
-rw-r--r--lib/chef/platform/provider_mapping.rb14
-rw-r--r--lib/chef/platform/provider_priority_map.rb24
-rw-r--r--lib/chef/platform/query_helpers.rb5
-rw-r--r--lib/chef/platform/rebooter.rb2
-rw-r--r--lib/chef/platform/resource_handler_map.rb29
-rw-r--r--lib/chef/platform/resource_priority_map.rb29
-rw-r--r--lib/chef/property.rb536
-rw-r--r--lib/chef/provider.rb237
-rw-r--r--lib/chef/provider/batch.rb8
-rw-r--r--lib/chef/provider/deploy.rb12
-rw-r--r--lib/chef/provider/directory.rb16
-rw-r--r--lib/chef/provider/dsc_resource.rb4
-rw-r--r--lib/chef/provider/dsc_script.rb2
-rw-r--r--lib/chef/provider/group/pw.rb2
-rw-r--r--lib/chef/provider/ifconfig.rb4
-rw-r--r--lib/chef/provider/lwrp_base.rb76
-rw-r--r--lib/chef/provider/mount.rb10
-rw-r--r--lib/chef/provider/mount/aix.rb2
-rw-r--r--lib/chef/provider/package.rb33
-rw-r--r--lib/chef/provider/package/aix.rb1
-rw-r--r--lib/chef/provider/package/apt.rb1
-rw-r--r--lib/chef/provider/package/dpkg.rb16
-rw-r--r--lib/chef/provider/package/homebrew.rb1
-rw-r--r--lib/chef/provider/package/ips.rb1
-rw-r--r--lib/chef/provider/package/macports.rb1
-rw-r--r--lib/chef/provider/package/openbsd.rb1
-rw-r--r--lib/chef/provider/package/pacman.rb1
-rw-r--r--lib/chef/provider/package/paludis.rb1
-rw-r--r--lib/chef/provider/package/portage.rb2
-rw-r--r--lib/chef/provider/package/rpm.rb4
-rw-r--r--lib/chef/provider/package/rubygems.rb2
-rw-r--r--lib/chef/provider/package/smartos.rb1
-rw-r--r--lib/chef/provider/package/solaris.rb2
-rw-r--r--lib/chef/provider/package/yum.rb23
-rw-r--r--lib/chef/provider/package/zypper.rb1
-rw-r--r--lib/chef/provider/powershell_script.rb63
-rw-r--r--lib/chef/provider/registry_key.rb10
-rw-r--r--lib/chef/provider/remote_directory.rb2
-rw-r--r--lib/chef/provider/service.rb40
-rw-r--r--lib/chef/provider/service/aix.rb2
-rw-r--r--lib/chef/provider/service/debian.rb10
-rw-r--r--lib/chef/provider/service/freebsd.rb2
-rw-r--r--lib/chef/provider/service/gentoo.rb6
-rw-r--r--lib/chef/provider/service/init.rb6
-rw-r--r--lib/chef/provider/service/insserv.rb6
-rw-r--r--lib/chef/provider/service/invokercd.rb6
-rw-r--r--lib/chef/provider/service/macosx.rb6
-rw-r--r--lib/chef/provider/service/openbsd.rb5
-rw-r--r--lib/chef/provider/service/redhat.rb66
-rw-r--r--lib/chef/provider/service/simple.rb4
-rw-r--r--lib/chef/provider/service/systemd.rb8
-rw-r--r--lib/chef/provider/service/upstart.rb11
-rw-r--r--lib/chef/provider/template/content.rb10
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/provider/windows_script.rb8
-rw-r--r--lib/chef/provider_resolver.rb112
-rw-r--r--lib/chef/recipe.rb9
-rw-r--r--lib/chef/resource.rb770
-rw-r--r--lib/chef/resource/action_provider.rb69
-rw-r--r--lib/chef/resource/chef_gem.rb6
-rw-r--r--lib/chef/resource/deploy.rb10
-rw-r--r--lib/chef/resource/dsc_script.rb4
-rw-r--r--lib/chef/resource/file/verification.rb9
-rw-r--r--lib/chef/resource/ips_package.rb1
-rw-r--r--lib/chef/resource/lwrp_base.rb11
-rw-r--r--lib/chef/resource/macports_package.rb3
-rw-r--r--lib/chef/resource/mount.rb8
-rw-r--r--lib/chef/resource/openbsd_package.rb11
-rw-r--r--lib/chef/resource/package.rb5
-rw-r--r--lib/chef/resource/registry_key.rb2
-rw-r--r--lib/chef/resource/service.rb12
-rw-r--r--lib/chef/resource/solaris_package.rb5
-rw-r--r--lib/chef/resource/yum_package.rb11
-rw-r--r--lib/chef/resource_resolver.rb83
-rw-r--r--lib/chef/run_context.rb487
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb11
-rw-r--r--lib/chef/user.rb240
-rw-r--r--lib/chef/user_v1.rb335
-rw-r--r--lib/chef/util/powershell/ps_credential.rb4
-rw-r--r--lib/chef/util/windows/net_group.rb191
-rw-r--r--lib/chef/util/windows/net_use.rb106
-rw-r--r--lib/chef/util/windows/volume.rb38
-rw-r--r--lib/chef/version.rb6
-rw-r--r--lib/chef/win32/api.rb1
-rw-r--r--lib/chef/win32/api/file.rb20
-rw-r--r--lib/chef/win32/api/net.rb206
-rw-r--r--lib/chef/win32/api/registry.rb45
-rw-r--r--lib/chef/win32/api/system.rb23
-rw-r--r--lib/chef/win32/api/unicode.rb43
-rw-r--r--lib/chef/win32/crypto.rb1
-rw-r--r--lib/chef/win32/file.rb31
-rw-r--r--lib/chef/win32/mutex.rb3
-rw-r--r--lib/chef/win32/net.rb170
-rw-r--r--lib/chef/win32/process.rb13
-rw-r--r--lib/chef/win32/registry.rb26
-rw-r--r--lib/chef/win32/security.rb2
-rw-r--r--lib/chef/win32/security/token.rb2
-rwxr-xr-xlib/chef/win32/system.rb62
-rw-r--r--lib/chef/win32/unicode.rb36
-rw-r--r--lib/chef/win32/version.rb4
-rw-r--r--lib/chef/workstation_config_loader.rb161
209 files changed, 5783 insertions, 2650 deletions
diff --git a/lib/chef.rb b/lib/chef.rb
index 6bce976439..1a0b802adb 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -31,5 +31,5 @@ require 'chef/daemon'
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
-
+require 'chef/event_dispatch/dsl'
require 'chef/chef_class'
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index ad31fb7d7b..b7b9f7dc43 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -1,7 +1,7 @@
#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright (c) 2008, 2015 Chef Software, Inc.
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Nuo Yan (<nuo@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,18 +23,18 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
-require 'chef/exceptions'
-require 'chef/mixin/api_version_request_handling'
require 'chef/server_api'
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
class Chef
class ApiClient
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- include Chef::Mixin::ApiVersionRequestHandling
-
- SUPPORTED_API_VERSIONS = [0,1]
# Create a new Chef::ApiClient object.
def initialize
@@ -43,25 +43,6 @@ class Chef
@private_key = nil
@admin = false
@validator = false
- @create_key = nil
- end
-
- def chef_rest_v0
- @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
- end
-
- def chef_rest_v1
- @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1"})
- end
-
- # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- def http_api
- @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
- end
-
- # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- def self.http_api
- Chef::REST.new(Chef::Config[:chef_server_url])
end
# Gets or sets the client name.
@@ -113,8 +94,7 @@ class Chef
)
end
- # Private key. The server will return it as a string.
- # Set to true under API V0 to have the server regenerate the default key.
+ # Gets or sets the private key.
#
# @params [Optional String] The string representation of the private key.
# @return [String] The current value.
@@ -122,19 +102,7 @@ class Chef
set_or_return(
:private_key,
arg,
- :kind_of => [String, TrueClass, FalseClass]
- )
- end
-
- # Used to ask server to generate key pair under api V1
- #
- # @params [Optional True/False] Should be true or false - default is false.
- # @return [True/False] The current value
- def create_key(arg=nil)
- set_or_return(
- :create_key,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [String, FalseClass]
)
end
@@ -145,14 +113,13 @@ class Chef
def to_hash
result = {
"name" => @name,
+ "public_key" => @public_key,
"validator" => @validator,
"admin" => @admin,
'json_class' => self.class.name,
"chef_type" => "client"
}
- result["private_key"] = @private_key unless @private_key.nil?
- result["public_key"] = @public_key unless @public_key.nil?
- result["create_key"] = @create_key unless @create_key.nil?
+ result["private_key"] = @private_key if @private_key
result
end
@@ -166,11 +133,10 @@ class Chef
def self.from_hash(o)
client = Chef::ApiClient.new
client.name(o["name"] || o["clientname"])
+ client.private_key(o["private_key"]) if o.key?("private_key")
+ client.public_key(o["public_key"])
client.admin(o["admin"])
client.validator(o["validator"])
- client.private_key(o["private_key"]) if o.key?("private_key")
- client.public_key(o["public_key"]) if o.key?("public_key")
- client.create_key(o["create_key"]) if o.key?("create_key")
client
end
@@ -182,6 +148,10 @@ class Chef
from_hash(Chef::JSONCompat.parse(j))
end
+ def self.http_api
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+ end
+
def self.reregister(name)
api_client = load(name)
api_client.reregister
@@ -218,11 +188,11 @@ class Chef
# Save this client via the REST API, returns a hash including the private key
def save
begin
- update
+ http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator})
rescue Net::HTTPServerException => e
# If that fails, go ahead and try and update it
if e.response.code == "404"
- create
+ http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator })
else
raise e
end
@@ -230,95 +200,18 @@ class Chef
end
def reregister
- # Try API V0 and if it fails due to V0 not being supported, raise the proper error message.
- # reregister only supported in API V0 or lesser.
- reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
if reregistered_self.respond_to?(:[])
private_key(reregistered_self["private_key"])
else
private_key(reregistered_self.private_key)
end
self
- rescue Net::HTTPServerException => e
- # if there was a 406 related to versioning, give error explaining that
- # only API version 0 is supported for reregister command
- if e.response.code == "406" && e.response["x-ops-server-api-version"]
- version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
- min_version = version_header["min_version"]
- max_version = version_header["max_version"]
- error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
- raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
- else
- raise e
- end
- end
-
- # Updates the client via the REST API
- def update
- # NOTE: API V1 dropped support for updating client keys via update (aka PUT),
- # but this code never supported key updating in the first place. Since
- # it was never implemented, we will simply ignore that functionality
- # as it is being deprecated.
- # Delete this comment after V0 support is dropped.
- payload = { :name => name }
- payload[:validator] = validator unless validator.nil?
-
- # DEPRECATION
- # This field is ignored in API V1, but left for backwards-compat,
- # can remove after API V0 is no longer supported.
- payload[:admin] = admin unless admin.nil?
-
- begin
- new_client = chef_rest_v1.put("clients/#{name}", payload)
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- new_client = chef_rest_v0.put("clients/#{name}", payload)
- end
-
- new_client
end
# Create the client via the REST API
def create
- payload = {
- :name => name,
- :validator => validator,
- # this field is ignored in API V1, but left for backwards-compat,
- # can remove after OSC 11 support is finished?
- :admin => admin
- }
- begin
- # try API V1
- raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil?
-
- payload[:public_key] = public_key unless public_key.nil?
- payload[:create_key] = create_key unless create_key.nil?
-
- new_client = chef_rest_v1.post("clients", payload)
-
- # get the private_key out of the chef_key hash if it exists
- if new_client['chef_key']
- if new_client['chef_key']['private_key']
- new_client['private_key'] = new_client['chef_key']['private_key']
- end
- new_client['public_key'] = new_client['chef_key']['public_key']
- new_client.delete('chef_key')
- end
-
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
-
- # under API V0, a key pair will always be created unless public_key is
- # passed on initial POST
- payload[:public_key] = public_key unless public_key.nil?
-
- new_client = chef_rest_v0.post("clients", payload)
- end
- Chef::ApiClient.from_hash(self.to_hash.merge(new_client))
+ http_api.post("clients", self)
end
# As a string
@@ -326,5 +219,14 @@ class Chef
"client[#{@name}]"
end
+ def inspect
+ "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " +
+ "public_key:'#{public_key}' private_key:'#{private_key}'"
+ end
+
+ def http_api
+ @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+ end
+
end
end
diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb
new file mode 100644
index 0000000000..80f0d2517c
--- /dev/null
+++ b/lib/chef/api_client_v1.rb
@@ -0,0 +1,325 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Nuo Yan (<nuo@chef.io>)
+# Copyright:: Copyright (c) 2008, 2015 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'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/exceptions'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/server_api'
+require 'chef/api_client'
+
+# COMPATIBILITY NOTE
+#
+# This ApiClientV1 code attempts to make API V1 requests and falls back to
+# API V0 requests when it fails. New development should occur here instead
+# of Chef::ApiClient as this will replace that namespace when Chef 13 is released.
+#
+# If you need to default to API V0 behavior (i.e. you need GET client to return
+# a public key, etc), please use Chef::ApiClient and update your code to support
+# API V1 before you pull in Chef 13.
+class Chef
+ class ApiClientV1
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
+
+ # Create a new Chef::ApiClientV1 object.
+ def initialize
+ @name = ''
+ @public_key = nil
+ @private_key = nil
+ @admin = false
+ @validator = false
+ @create_key = nil
+ end
+
+ def chef_rest_v0
+ @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0", :inflate_json_class => false})
+ end
+
+ def chef_rest_v1
+ @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+ end
+
+ def self.http_api
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+ end
+
+ # Gets or sets the client name.
+ #
+ # @params [Optional String] The name must be alpha-numeric plus - and _.
+ # @return [String] The current value of the name.
+ def name(arg=nil)
+ set_or_return(
+ :name,
+ arg,
+ :regex => /^[\-[:alnum:]_\.]+$/
+ )
+ end
+
+ # Gets or sets whether this client is an admin.
+ #
+ # @params [Optional True/False] Should be true or false - default is false.
+ # @return [True/False] The current value
+ def admin(arg=nil)
+ set_or_return(
+ :admin,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ # Gets or sets the public key.
+ #
+ # @params [Optional String] The string representation of the public key.
+ # @return [String] The current value.
+ def public_key(arg=nil)
+ set_or_return(
+ :public_key,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ # Gets or sets whether this client is a validator.
+ #
+ # @params [Boolean] whether or not the client is a validator. If
+ # `nil`, retrieves the already-set value.
+ # @return [Boolean] The current value
+ def validator(arg=nil)
+ set_or_return(
+ :validator,
+ arg,
+ :kind_of => [TrueClass, FalseClass]
+ )
+ end
+
+ # Private key. The server will return it as a string.
+ # Set to true under API V0 to have the server regenerate the default key.
+ #
+ # @params [Optional String] The string representation of the private key.
+ # @return [String] The current value.
+ def private_key(arg=nil)
+ set_or_return(
+ :private_key,
+ arg,
+ :kind_of => [String, TrueClass, FalseClass]
+ )
+ end
+
+ # Used to ask server to generate key pair under api V1
+ #
+ # @params [Optional True/False] Should be true or false - default is false.
+ # @return [True/False] The current value
+ def create_key(arg=nil)
+ set_or_return(
+ :create_key,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ # The hash representation of the object. Includes the name and public_key.
+ # Private key is included if available.
+ #
+ # @return [Hash]
+ def to_hash
+ result = {
+ "name" => @name,
+ "validator" => @validator,
+ "admin" => @admin,
+ "chef_type" => "client"
+ }
+ result["private_key"] = @private_key unless @private_key.nil?
+ result["public_key"] = @public_key unless @public_key.nil?
+ result["create_key"] = @create_key unless @create_key.nil?
+ result
+ end
+
+ # The JSON representation of the object.
+ #
+ # @return [String] the JSON string.
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def self.from_hash(o)
+ client = Chef::ApiClientV1.new
+ client.name(o["name"] || o["clientname"])
+ client.admin(o["admin"])
+ client.validator(o["validator"])
+ client.private_key(o["private_key"]) if o.key?("private_key")
+ client.public_key(o["public_key"]) if o.key?("public_key")
+ client.create_key(o["create_key"]) if o.key?("create_key")
+ client
+ end
+
+ def self.from_json(j)
+ Chef::ApiClientV1.from_hash(Chef::JSONCompat.from_json(j))
+ end
+
+ def self.reregister(name)
+ api_client = Chef::ApiClientV1.load(name)
+ api_client.reregister
+ end
+
+ def self.list(inflate=false)
+ if inflate
+ response = Hash.new
+ Chef::Search::Query.new.search(:client) do |n|
+ n = self.from_hash(n) if n.instance_of?(Hash)
+ response[n.name] = n
+ end
+ response
+ else
+ http_api.get("clients")
+ end
+ end
+
+ # Load a client by name via the API
+ def self.load(name)
+ response = http_api.get("clients/#{name}")
+ Chef::ApiClientV1.from_hash(response)
+ end
+
+ # Remove this client via the REST API
+ def destroy
+ chef_rest_v1.delete("clients/#{@name}")
+ end
+
+ # Save this client via the REST API, returns a hash including the private key
+ def save
+ begin
+ update
+ rescue Net::HTTPServerException => e
+ # If that fails, go ahead and try and update it
+ if e.response.code == "404"
+ create
+ else
+ raise e
+ end
+ end
+ end
+
+ def reregister
+ # Try API V0 and if it fails due to V0 not being supported, raise the proper error message.
+ # reregister only supported in API V0 or lesser.
+ reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ if reregistered_self.respond_to?(:[])
+ private_key(reregistered_self["private_key"])
+ else
+ private_key(reregistered_self.private_key)
+ end
+ self
+ rescue Net::HTTPServerException => e
+ # if there was a 406 related to versioning, give error explaining that
+ # only API version 0 is supported for reregister command
+ if e.response.code == "406" && e.response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+ min_version = version_header["min_version"]
+ max_version = version_header["max_version"]
+ error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+ raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+ else
+ raise e
+ end
+ end
+
+ # Updates the client via the REST API
+ def update
+ # NOTE: API V1 dropped support for updating client keys via update (aka PUT),
+ # but this code never supported key updating in the first place. Since
+ # it was never implemented, we will simply ignore that functionality
+ # as it is being deprecated.
+ # Delete this comment after V0 support is dropped.
+ payload = { :name => name }
+ payload[:validator] = validator unless validator.nil?
+
+ # DEPRECATION
+ # This field is ignored in API V1, but left for backwards-compat,
+ # can remove after API V0 is no longer supported.
+ payload[:admin] = admin unless admin.nil?
+
+ begin
+ new_client = chef_rest_v1.put("clients/#{name}", payload)
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ new_client = chef_rest_v0.put("clients/#{name}", payload)
+ end
+
+ Chef::ApiClientV1.from_hash(new_client)
+ end
+
+ # Create the client via the REST API
+ def create
+ payload = {
+ :name => name,
+ :validator => validator,
+ # this field is ignored in API V1, but left for backwards-compat,
+ # can remove after OSC 11 support is finished?
+ :admin => admin
+ }
+ begin
+ # try API V1
+ raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil?
+
+ payload[:public_key] = public_key unless public_key.nil?
+ payload[:create_key] = create_key unless create_key.nil?
+
+ new_client = chef_rest_v1.post("clients", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_client['chef_key']
+ if new_client['chef_key']['private_key']
+ new_client['private_key'] = new_client['chef_key']['private_key']
+ end
+ new_client['public_key'] = new_client['chef_key']['public_key']
+ new_client.delete('chef_key')
+ end
+
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+
+ # under API V0, a key pair will always be created unless public_key is
+ # passed on initial POST
+ payload[:public_key] = public_key unless public_key.nil?
+
+ new_client = chef_rest_v0.post("clients", payload)
+ end
+ Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client))
+ end
+
+ # As a string
+ def to_s
+ "client[#{@name}]"
+ end
+
+ end
+end
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 0563822ede..970544c068 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -382,7 +382,7 @@ class Chef
def emit_warnings
if Chef::Config[:chef_gem_compile_time]
- Chef::Log.deprecation "setting chef_gem_compile_time to true is deprecated"
+ Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated"
end
end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index e9768b218c..243b441119 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -49,6 +49,24 @@ class Chef::Application::Apply < Chef::Application
:description => "Load attributes from a JSON file or URL",
:proc => nil
+ option :force_logger,
+ :long => "--force-logger",
+ :description => "Use logger output instead of formatter output",
+ :boolean => true,
+ :default => false
+
+ option :force_formatter,
+ :long => "--force-formatter",
+ :description => "Use formatter output instead of logger output",
+ :boolean => true,
+ :default => false
+
+ option :formatter,
+ :short => "-F FORMATTER",
+ :long => "--format FORMATTER",
+ :description => "output format to use",
+ :proc => lambda { |format| Chef::Config.add_formatter(format) }
+
option :log_level,
:short => "-l LEVEL",
:long => "--log_level LEVEL",
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index 409680b553..73eda81343 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -449,9 +449,9 @@ class Chef::Application::Client < Chef::Application
end
def audit_mode_settings_explanation
- "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `:audit_mode = :enabled` in your config file." +
- "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `:audit_mode = :disabled` in your config file." +
- "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `:audit_mode = :audit_only` in your config file." +
+ "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." +
+ "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." +
+ "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." +
"\nAudit mode is disabled by default."
end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index dd09d65b42..5bb2a1ceb0 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -214,7 +214,7 @@ class Chef::Application::Solo < Chef::Application
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}")
+ Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command
end
# json_attribs shuld be fetched after recipe_url tarball is unpacked.
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index de8ed657c2..44526c1720 100644
--- a/lib/chef/application/windows_service_manager.rb
+++ b/lib/chef/application/windows_service_manager.rb
@@ -78,7 +78,7 @@ class Chef
raise ArgumentError, "Service definition is not provided" if service_options.nil?
- required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]
+ required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
required_options.each do |req_option|
if !service_options.has_key?(req_option)
@@ -92,6 +92,8 @@ class Chef
@service_file_path = service_options[:service_file_path]
@service_start_name = service_options[:run_as_user]
@password = service_options[:run_as_password]
+ @delayed_start = service_options[:delayed_start]
+ @dependencies = service_options[:dependencies]
end
def run(params = ARGV)
@@ -113,17 +115,22 @@ class Chef
cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
::Win32::Service.new(
- :service_name => @service_name,
- :display_name => @service_display_name,
- :description => @service_description,
- # Prior to 0.8.5, win32-service creates interactive services by default,
- # and we don't want that, so we need to override the service type.
- :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
- :start_type => ::Win32::Service::SERVICE_AUTO_START,
- :binary_path_name => cmd,
- :service_start_name => @service_start_name,
- :password => @password,
- )
+ :service_name => @service_name,
+ :display_name => @service_display_name,
+ :description => @service_description,
+ # Prior to 0.8.5, win32-service creates interactive services by default,
+ # and we don't want that, so we need to override the service type.
+ :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
+ :start_type => ::Win32::Service::SERVICE_AUTO_START,
+ :binary_path_name => cmd,
+ :service_start_name => @service_start_name,
+ :password => @password,
+ :dependencies => @dependencies
+ )
+ ::Win32::Service.configure(
+ :service_name => @service_name,
+ :delayed_start => @delayed_start
+ ) unless @delayed_start.nil?
puts "Service '#{@service_name}' has successfully been installed."
end
when 'status'
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index f1dd797c04..c2cb9e2b24 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -28,6 +28,8 @@
require 'chef/platform/provider_priority_map'
require 'chef/platform/resource_priority_map'
+require 'chef/platform/provider_handler_map'
+require 'chef/platform/resource_handler_map'
class Chef
class << self
@@ -50,7 +52,14 @@ class Chef
#
attr_reader :run_context
+ # Register an event handler with user specified block
#
+ # @return[Chef::EventDispatch::Base] handler object
+ def event_handler(&block)
+ dsl = Chef::EventDispatch::DSL.new('Chef client DSL')
+ dsl.instance_eval(&block)
+ end
+
# Get the array of providers associated with a resource_name for the current node
#
# @param resource_name [Symbol] name of the resource as a symbol
@@ -58,7 +67,7 @@ class Chef
# @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
#
def get_provider_priority_array(resource_name)
- result = provider_priority_map.get_priority_array(node, resource_name)
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -71,7 +80,7 @@ class Chef
# @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
#
def get_resource_priority_array(resource_name)
- result = resource_priority_map.get_priority_array(node, resource_name)
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -86,7 +95,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
#
def set_provider_priority_array(resource_name, priority_array, *filter, &block)
- result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -101,7 +110,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
#
def set_resource_priority_array(resource_name, priority_array, *filter, &block)
- result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -160,19 +169,48 @@ class Chef
@node = nil
@provider_priority_map = nil
@resource_priority_map = nil
+ @provider_handler_map = nil
+ @resource_handler_map = nil
end
# @api private
def provider_priority_map
- @provider_priority_map ||= begin
- # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
- Chef::Platform::ProviderPriorityMap.instance
- end
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+ @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance
end
# @api private
def resource_priority_map
- @resource_priority_map ||= begin
- Chef::Platform::ResourcePriorityMap.instance
+ @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance
+ end
+ # @api private
+ def provider_handler_map
+ @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance
+ end
+ # @api private
+ def resource_handler_map
+ @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance
+ end
+
+ #
+ # Emit a deprecation message.
+ #
+ # @param message The message to send.
+ # @param location The location. Defaults to the caller who called you (since
+ # generally the person who triggered the check is the one that needs to be
+ # fixed).
+ #
+ # @example
+ # Chef.deprecation("Deprecated!")
+ #
+ # @api private this will likely be removed in favor of an as-yet unwritten
+ # `Chef.log`
+ def log_deprecation(message, location=caller(2..2)[0])
+ # `run_context.events` is the primary deprecation target if we're in a
+ # run. If we are not yet in a run, print to `Chef::Log`.
+ if run_context && run_context.events
+ run_context.events.deprecation(message, location)
+ else
+ Chef::Log.deprecation(message, location)
end
end
end
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 6666a3deee..40cbb36530 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -111,7 +111,7 @@ class Chef
#
def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
@chef_config = chef_config
- @cwd = cwd
+ @cwd = File.expand_path(cwd)
@cookbook_version = options[:cookbook_version]
if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil?
@@ -166,34 +166,37 @@ class Chef
# server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
# server_path('/home/*/chef_repo/cookbooks/blah') == nil
#
- # If there are multiple paths (cookbooks, roles, data bags, etc. can all
- # have separate paths), and cwd+the path reaches into one of them, we will
- # return a path relative to that. Otherwise we will return a path to
- # chef_repo.
+ # If there are multiple different, manually specified paths to object locations
+ # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the
+ # path reaches into one of them, we will return a path relative to the first
+ # one to match it. Otherwise we expect the path provided to be to the chef
+ # repo path itself. Paths that are not available on the server are not supported.
#
# Globs are allowed as well, but globs outside server paths are NOT
# (presently) supported. See above examples. TODO support that.
#
# If the path does not reach into ANY specified directory, nil is returned.
def server_path(file_path)
- pwd = File.expand_path(Dir.pwd)
- absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
+ target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
# Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+ # These are either manually specified by the user or autogenerated relative
+ # to chef_repo_path.
object_paths.each_pair do |name, paths|
paths.each do |path|
- realest_path = Chef::ChefFS::PathUtils.realest_path(path)
- if PathUtils.descendant_of?(absolute_pwd, realest_path)
- relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
- return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+ object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
+ if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
+ return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
end
end
end
# Check chef_repo_path
Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
- realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
- if absolute_pwd == realest_chef_repo_path
+ # We're using realest_path here but we really don't need to - we can just expand the
+ # path and use realpath because a repo_path if provided *must* exist.
+ realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
+ if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
return '/'
end
end
@@ -201,15 +204,10 @@ class Chef
nil
end
- # The current directory, relative to server root
+ # The current directory, relative to server root. This is a case-sensitive server path.
+ # It only exists if the current directory is a child of one of the recognized object_paths below.
def base_path
- @base_path ||= begin
- if @chef_config[:chef_repo_path]
- server_path(File.expand_path(@cwd))
- else
- nil
- end
- end
+ @base_path ||= server_path(@cwd)
end
# Print the given server path, relative to the current directory
@@ -217,10 +215,10 @@ class Chef
server_path = entry.path
if base_path && server_path[0,base_path.length] == base_path
if server_path == base_path
- return "."
- elsif server_path[base_path.length,1] == "/"
+ return '.'
+ elsif server_path[base_path.length,1] == '/'
return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
- elsif base_path == "/" && server_path[0,1] == "/"
+ elsif base_path == '/' && server_path[0,1] == '/'
return server_path[1, server_path.length - 1]
end
end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 134d22cbd5..b2351dac68 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -72,7 +72,7 @@ class Chef
def could_match_children?(path)
return false if path == '' # Empty string is not a path
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
@@ -111,7 +111,7 @@ class Chef
#
# This method assumes +could_match_children?(path)+ is +true+.
def exact_child_name_under(path)
- path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path)
dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
return nil if exact_parts.length <= dirs_in_path
return exact_parts[dirs_in_path]
@@ -149,7 +149,7 @@ class Chef
# abc/*/def.match?('abc/foo/def') == true
# abc/*/def.match?('abc/foo') == false
def match?(path)
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
!!regexp.match(path)
@@ -160,17 +160,6 @@ class Chef
pattern
end
- # Given a relative file pattern and a directory, makes a new file pattern
- # starting with the directory.
- #
- # FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
- #
- # BUG: this does not support patterns starting with <tt>..</tt>
- def self.relative_to(dir, pattern)
- return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
- FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
- end
-
private
def regexp
@@ -195,7 +184,7 @@ class Chef
def calculate
if !@regexp
- @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
full_regexp_parts = []
normalized_parts = []
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
index c2354d478d..9f68d7cda7 100644
--- a/lib/chef/chef_fs/file_system/acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/acl_dir.rb
@@ -28,10 +28,9 @@ class Chef
parent.parent.child(name).api_path
end
- def child(name)
+ def make_child_entry(name, exists = nil)
result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- AclEntry.new(name, self) : NonexistentFSObject.new(name, self)
+ result || AclEntry.new(name, self, exists)
end
def can_have_child?(name, is_dir)
@@ -42,7 +41,7 @@ class Chef
if @children.nil?
# Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
- @children = names.map { |name| AclEntry.new(name, self, true) }
+ @children = names.map { |name| make_child_entry(name, true) }
end
@children
end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
index 938bf73fb2..a8c63726b7 100644
--- a/lib/chef/chef_fs/file_system/acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/acls_dir.rb
@@ -40,8 +40,12 @@ class Chef
parent.api_path
end
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
def can_have_child?(name, is_dir)
- is_dir ? ENTITY_TYPES.include(name) : name == 'organization.json'
+ is_dir ? ENTITY_TYPES.include?(name) : name == 'organization.json'
end
def children
diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb
index 8cc277facc..47e33f961a 100644
--- a/lib/chef/chef_fs/file_system/base_fs_dir.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -31,11 +31,6 @@ class Chef
true
end
- # Override child(name) to provide a child object by name without the network read
- def child(name)
- children.select { |child| child.name == name }.first || NonexistentFSObject.new(name, self)
- end
-
def can_have_child?(name, is_dir)
true
end
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
index 43e6a513d7..916ab8297d 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -95,7 +95,10 @@ class Chef
# directly perform a network request to retrieve the y.json data bag. No
# network request was necessary to retrieve
def child(name)
- NonexistentFSObject.new(name, self)
+ if can_have_child?(name, true) || can_have_child?(name, false)
+ result = make_child_entry(name)
+ end
+ result || NonexistentFSObject.new(name, self)
end
# Override children to report your *actual* list of children as an array.
@@ -171,7 +174,7 @@ class Chef
# Important directory attributes: name, parent, path, root
# Overridable attributes: dir?, child(name), path_for_printing
- # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
+ # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to, make_child_entry
end # class BaseFsObject
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
index a7f1d733b1..4391bdbfcd 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
@@ -58,14 +58,7 @@ class Chef
end
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
end
def can_have_child?(name, is_dir)
@@ -99,7 +92,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
index 66709ccf68..914412f839 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
@@ -34,14 +34,7 @@ class Chef
attr_reader :recursive
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
end
def can_have_child?(name, is_dir)
@@ -78,7 +71,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
index 7c60b51114..5b495666c3 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
@@ -37,21 +37,14 @@ class Chef
attr_reader :chefignore
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select do |entry|
- # empty cookbooks and cookbook directories are ignored
- if !entry.can_upload?
- Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
- false
- else
- true
- end
- end
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ super.select do |entry|
+ # empty cookbooks and cookbook directories are ignored
+ if !entry.can_upload?
+ Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
+ false
+ else
+ true
+ end
end
end
@@ -61,7 +54,7 @@ class Chef
def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
cookbook_name = File.basename(cookbook_path)
- child = make_child(cookbook_name)
+ child = make_child_entry(cookbook_name)
# Use the copy/diff algorithm to copy it down so we don't destroy
# chefignored data. This is terribly un-thread-safe.
@@ -80,7 +73,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemCookbookDir.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
index 0b14750744..39172e7ab9 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
@@ -70,20 +70,9 @@ class Chef
Chef::JSONCompat.to_json_pretty(object)
end
- def children
- # Except cookbooks and data bag dirs, all things must be json files
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemEntry.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
index d03baf91fe..267fe30456 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
@@ -68,13 +68,13 @@ class Chef
attr_reader :child_paths
attr_reader :versioned_cookbooks
- CHILDREN = %w(invitations.json members.json org.json)
+ CHILDREN = %w(org.json invitations.json members.json)
def children
@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 }
+ result = child_paths.keys.sort.map { |name| make_child_entry(name) }
+ result += CHILDREN.map { |name| make_child_entry(name) }
+ result.select { |c| c && c.exists? }.sort_by { |c| c.name }
end
end
@@ -149,19 +149,23 @@ class Chef
# 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)
+ if CHILDREN.include?(name)
+ return nil if !root_dir
+ return root_dir.child(name)
end
+
+ paths = (child_paths[name] || []).select { |path| File.exists?(path) }
if paths.size == 0
- return nil
+ return NonexistentFSObject.new(name, self)
end
- if name == 'cookbooks'
+ case name
+ when 'cookbooks'
dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
- elsif name == 'data_bags'
+ when 'data_bags'
dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
- elsif name == 'policies'
+ when 'policies'
dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) }
- elsif name == 'acls'
+ when 'acls'
dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
else
data_handler = case name
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 370308ee0a..e3ffd644ad 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
@@ -90,11 +90,11 @@ class Chef
end
def rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true)
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
end
def get_json(path)
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path)
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path)
end
def chef_rest
@@ -110,7 +110,8 @@ class Chef
end
def can_have_child?(name, is_dir)
- is_dir && children.any? { |child| child.name == name }
+ result = children.select { |child| child.name == name }.first
+ result && !!result.dir? == !!is_dir
end
def org
@@ -124,6 +125,10 @@ class Chef
end
end
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
def children
@children ||= begin
result = [
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
index 03652dc376..c0f0390e98 100644
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/chef_fs/command_line'
require 'chef/chef_fs/file_system/rest_list_dir'
require 'chef/chef_fs/file_system/cookbook_subdir'
require 'chef/chef_fs/file_system/cookbook_file'
@@ -71,16 +72,15 @@ class Chef
"#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
end
- def child(name)
+ def make_child_entry(name)
# Since we're ignoring the rules and doing a network request here,
# we need to make sure we don't rethrow the exception. (child(name)
# is not supposed to fail.)
begin
- result = children.select { |child| child.name == name }.first
- return result if result
+ children.select { |child| child.name == name }.first
rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
end
- return NonexistentFSObject.new(name, self)
end
def can_have_child?(name, is_dir)
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
index d6246f1e60..560ceb4886 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
@@ -31,7 +31,7 @@ class Chef
def children
if @children.nil?
names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
- @children = names.uniq.map { |name| AclEntry.new(name, self, true) }
+ @children = names.uniq.map { |name| make_child_entry(name, true) }
end
@children
end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
index 27bedd3827..6f49c28996 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -36,17 +36,9 @@ class Chef
super("cookbooks", parent)
end
- def child(name)
- if @children
- result = self.children.select { |child| child.name == name }.first
- if result
- result
- else
- NonexistentFSObject.new(name, self)
- end
- else
- CookbookDir.new(name, self)
- end
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookDir.new(name, self)
end
def children
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
index 6d0685d3b7..1cb61bbd1a 100644
--- a/lib/chef/chef_fs/file_system/data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb
@@ -27,16 +27,14 @@ class Chef
super("data_bags", parent, "data")
end
- def child(name)
+ def make_child_entry(name, exists = false)
result = @children.select { |child| child.name == name }.first if @children
- result || DataBagDir.new(name, self)
+ result || DataBagDir.new(name, self, exists)
end
def children
begin
- @children ||= root.get_json(api_path).keys.sort.map do |entry|
- DataBagDir.new(entry, self, true)
- end
+ @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) }
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}"
rescue Net::HTTPServerException => e
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb
index 559dd6af86..3aee3ee5af 100644
--- a/lib/chef/chef_fs/file_system/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/environments_dir.rb
@@ -30,7 +30,7 @@ class Chef
super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new)
end
- def _make_child_entry(name, exists = nil)
+ def make_child_entry(name, exists = nil)
if name == '_default.json'
DefaultEnvironmentEntry.new(name, self, exists)
else
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
index 1af7e618de..8611aa2e0f 100644
--- a/lib/chef/chef_fs/file_system/file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/file_system_entry.rb
@@ -40,15 +40,18 @@ class Chef
end
def children
+ # Except cookbooks and data bag dirs, all things must be json files
begin
- Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) }
+ Dir.entries(file_path).sort.
+ map { |child_name| make_child_entry(child_name) }.
+ select { |child| child && can_have_child?(child.name, child.dir?) }
rescue Errno::ENOENT
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
end
def create_child(child_name, file_contents=nil)
- child = make_child(child_name)
+ child = make_child_entry(child_name)
if child.exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
end
@@ -80,7 +83,7 @@ class Chef
end
def exists?
- File.exists?(file_path)
+ File.exists?(file_path) && parent.can_have_child?(name, dir?)
end
def read
@@ -99,7 +102,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
FileSystemEntry.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
index a7eda3c654..260a91693c 100644
--- a/lib/chef/chef_fs/file_system/memory_dir.rb
+++ b/lib/chef/chef_fs/file_system/memory_dir.rb
@@ -1,5 +1,4 @@
require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
require 'chef/chef_fs/file_system/memory_file'
class Chef
@@ -13,8 +12,8 @@ class Chef
attr_reader :children
- def child(name)
- @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+ def make_child_entry(name)
+ @children.select { |child| child.name == name }.first
end
def add_child(child)
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
index 06d4af705d..70b827f85f 100644
--- a/lib/chef/chef_fs/file_system/multiplexed_dir.rb
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -35,6 +35,21 @@ class Chef
end
end
+ def make_child_entry(name)
+ result = nil
+ multiplexed_dirs.each do |dir|
+ child_entry = dir.child(name)
+ if child_entry.exists?
+ if result
+ Chef::Log.warn("Child with name '#{child_entry.name}' found in multiple directories: #{result.parent.path_for_printing} and #{child_entry.parent.path_for_printing}")
+ else
+ result = child_entry
+ end
+ end
+ end
+ result
+ end
+
def can_have_child?(name, is_dir)
write_dir.can_have_child?(name, is_dir)
end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
index c3c48377cd..2610b06a82 100644
--- a/lib/chef/chef_fs/file_system/nodes_dir.rb
+++ b/lib/chef/chef_fs/file_system/nodes_dir.rb
@@ -33,7 +33,7 @@ class Chef
def children
begin
@children ||= root.get_json(env_api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
+ make_child_entry("#{key}.json", true)
end
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
index 672fa444f1..0ac735a2c4 100644
--- a/lib/chef/chef_fs/file_system/rest_list_dir.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb
@@ -33,12 +33,6 @@ class Chef
attr_reader :api_path
attr_reader :data_handler
- def child(name)
- result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- _make_child_entry(name) : NonexistentFSObject.new(name, self)
- end
-
def can_have_child?(name, is_dir)
name =~ /\.json$/ && !is_dir
end
@@ -46,7 +40,7 @@ class Chef
def children
begin
@children ||= root.get_json(api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
+ make_child_entry("#{key}.json", true)
end
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
@@ -66,7 +60,7 @@ class Chef
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
end
- result = _make_child_entry(name, true)
+ result = make_child_entry(name, true)
if data_handler
object = data_handler.normalize_for_post(object, result)
@@ -106,7 +100,8 @@ class Chef
parent.rest
end
- def _make_child_entry(name, exists = nil)
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
RestListEntry.new(name, self, exists)
end
end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 86872dab71..9101e455f8 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -17,6 +17,7 @@
#
require 'chef/knife'
+require 'pathname'
class Chef
module ChefFS
@@ -63,7 +64,7 @@ class Chef
# --chef-repo-path forcibly overrides all other paths
if config[:chef_repo_path]
Chef::Config[:chef_repo_path] = config[:chef_repo_path]
- %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
+ Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
Chef::Config.delete("#{variable_name}_path".to_sym)
end
end
@@ -98,14 +99,41 @@ class Chef
end
def pattern_arg_from(arg)
- # TODO support absolute file paths and not just patterns? Too much?
- # Could be super useful in a world with multiple repo paths
- if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
- # Check if chef repo path is specified to give a better error message
- ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
+ inferred_path = nil
+ if Chef::ChefFS::PathUtils.is_absolute?(arg)
+ # We should be able to use this as-is - but the user might have incorrectly provided
+ # us with a path that is based off of the OS root path instead of the Chef-FS root.
+ # Do a quick and dirty sanity check.
+ if possible_server_path = @chef_fs_config.server_path(arg)
+ ui.warn("The absolute path provided is suspicious: #{arg}")
+ ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
+ ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
+ end
+ # Use the original path because we can't be sure.
+ inferred_path = arg
+ elsif arg[0,1] == '~'
+ # Let's be nice and fix it if possible - but warn the user.
+ ui.warn("A path relative to a user home directory has been provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif Pathname.new(arg).absolute?
+ # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
+ # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
+ ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif @chef_fs_config.base_path.nil?
+ # These are all relative paths. We can't resolve and root paths unless we are in the
+ # chef repo.
+ ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
+ ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
exit(1)
+ else
+ inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg)
end
- Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
+ Chef::ChefFS::FilePattern.new(inferred_path)
end
def format_path(entry)
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 9ef75ce2e5..595f966378 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -23,31 +23,31 @@ class Chef
module ChefFS
class PathUtils
- # If you are in 'source', this is what you would have to type to reach 'dest'
- # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
- # relative_to('/a/b', '/a/b') == '.'
- def self.relative_to(dest, source)
- # Skip past the common parts
- source_parts = Chef::ChefFS::PathUtils.split(source)
- dest_parts = Chef::ChefFS::PathUtils.split(dest)
- i = 0
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
- i+=1
- end
- # dot-dot up from 'source' to the common ancestor, then
- # descend to 'dest' from the common ancestor
- result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
- result == '' ? '.' : result
- end
+ # A Chef-FS path is a path in a chef-repository that can be used to address
+ # both files on a local file-system as well as objects on a chef server.
+ # These paths are stricter than file-system paths allowed on various OSes.
+ # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well).
+ # "/" is used as the path element separator (on windows, "\" is acceptable as well).
+ # No directory/path element may contain a literal "\" character. Any such characters
+ # encountered are either dealt with as separators (on windows) or as escape
+ # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but
+ # may never use these to back-out of the root of a Chef-FS path. Any such extraneous
+ # ".."s are ignored.
+ # Chef-FS paths are case sensitive (since the paths on the server are).
+ # On OSes with case insensitive paths, you may be unable to locally deal with two
+ # objects whose server paths only differ by case. OTOH, the case of path segments
+ # that are outside the Chef-FS root (such as when looking at a file-system absolute
+ # path to discover the Chef-FS root path) are handled in accordance to the rules
+ # of the local file-system and OS.
def self.join(*parts)
return "" if parts.length == 0
# Determine if it started with a slash
absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
# Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
- parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
+ parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') }
# Don't join empty bits
- result = parts.select { |part| part != "" }.join("/")
+ result = parts.select { |part| part != '' }.join('/')
# Put the / back on
absolute ? "/#{result}" : result
end
@@ -60,36 +60,67 @@ class Chef
Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
end
+ # Given a server path, determines if it is absolute.
+ def self.is_absolute?(path)
+ !!(path =~ /^#{regexp_path_separator}/)
+ end
# Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
# or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
- # part that actually exists.
+ # part that actually exists. The paths operated on here are not Chef-FS paths.
+ # These are OS paths that may contain symlinks but may not also fully exist.
#
# If /x is a symlink to /blarghle, and has no subdirectories, then:
# PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
# PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
# PathUtils.realest_path('/*/y/z') == '/*/y/z'
- def self.realest_path(path)
- path = Pathname.new(path)
- begin
- path.realpath.to_s
- rescue Errno::ENOENT
- dirname = path.dirname
- if dirname
- PathUtils.join(realest_path(dirname), path.basename.to_s)
- else
- path.to_s
+ #
+ # TODO: Move this to wherever util/path_helper is these days.
+ def self.realest_path(path, cwd = Dir.pwd)
+ path = File.expand_path(path, cwd)
+ parent_path = File.dirname(path)
+ suffix = []
+
+ # File.dirname happens to return the path as its own dirname if you're
+ # at the root (such as at \\foo\bar, C:\ or /)
+ until parent_path == path do
+ # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:."
+ # for reasons only it knows.
+ raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length
+ begin
+ path = File.realpath(path)
+ break
+ rescue Errno::ENOENT
+ suffix << File.basename(path)
+ path = parent_path
+ parent_path = File.dirname(path)
end
end
+ File.join(path, *suffix.reverse)
end
- def self.descendant_of?(path, ancestor)
- path[0,ancestor.length] == ancestor &&
- (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
+ # Compares two path fragments according to the case-sentitivity of the host platform.
+ def self.os_path_eq?(left, right)
+ Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right
end
- def self.is_absolute?(path)
- path =~ /^#{regexp_path_separator}/
+ # Given two general OS-dependent file paths, determines the relative path of the
+ # child with respect to the ancestor. Both child and ancestor must exist and be
+ # fully resolved - this is strictly a lexical comparison. No trailing slashes
+ # and other shenanigans are allowed.
+ #
+ # TODO: Move this to util/path_helper.
+ def self.descendant_path(path, ancestor)
+ candidate_fragment = path[0, ancestor.length]
+ return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor)
+ if ancestor.length == path.length
+ ''
+ elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/
+ path[ancestor.length+1..-1]
+ else
+ nil
+ end
end
+
end
end
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 86e92585e3..621ce3d489 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -3,7 +3,7 @@
# Author:: Christopher Walters (<cw@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Tim Hinderliter (<tim@opscode.com>)
-# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -302,19 +302,24 @@ class Chef
@run_status = nil
run_context = nil
runlock.release
- GC.start
end
# Raise audit, converge, and other errors here so that we exit
# with the proper exit status code and everything gets raised
# as a RunFailedWrappingError
if run_error || converge_error || audit_error
- error = if run_error == converge_error
- Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- else
- Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
- end
- error.fill_backtrace
+ error = if Chef::Config[:audit_mode] == :disabled
+ run_error || converge_error
+ else
+ e = if run_error == converge_error
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ else
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+ end
+ e.fill_backtrace
+ e
+ end
+
Chef::Application.debug_stacktrace(error)
raise error
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 9beb18b53e..6382af14c2 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -28,9 +28,21 @@ require 'chef-config/logger'
ChefConfig.logger = Chef::Log
require 'chef-config/config'
-
require 'chef/platform/query_helpers'
+# Ohai::Config defines its own log_level and log_location. When loaded, it will
+# override the default ChefConfig::Config values. We save them here before
+# loading ohai/config so that we can override them again inside Chef::Config.
+#
+# REMOVEME once these configurables are removed from the top level of Ohai.
+LOG_LEVEL = ChefConfig::Config[:log_level] unless defined? LOG_LEVEL
+LOG_LOCATION = ChefConfig::Config[:log_location] unless defined? LOG_LOCATION
+
+# Load the ohai config into the chef config. We can't have an empty ohai
+# configuration context because `ohai.plugins_path << some_path` won't work,
+# and providing default ohai config values here isn't DRY.
+require 'ohai/config'
+
class Chef
Config = ChefConfig::Config
@@ -49,5 +61,24 @@ class Chef
evt_loggers
end
+ # Override the default values that were set by Ohai.
+ #
+ # REMOVEME once these configurables are removed from the top level of Ohai.
+ default :log_level, LOG_LEVEL
+ default :log_location, LOG_LOCATION
+
+ # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately,
+ # there is no way to distinguish between setting log_level and setting
+ # Ohai::Config[:log_level]. Since log_level and log_location are used by
+ # chef-client and other tools (e.g., knife), we will mute the warnings here
+ # by redefining the config_attr_writer to not warn for these options.
+ #
+ # REMOVEME once the warnings for these configurables are removed from Ohai.
+ [ :log_level, :log_location ].each do |option|
+ config_attr_writer option do |value|
+ value
+ end
+ end
+
end
end
diff --git a/lib/chef/constants.rb b/lib/chef/constants.rb
new file mode 100644
index 0000000000..d39ce4c68d
--- /dev/null
+++ b/lib/chef/constants.rb
@@ -0,0 +1,27 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ NOT_PASSED = Object.new
+ def NOT_PASSED.to_s
+ "NOT_PASSED"
+ end
+ def NOT_PASSED.inspect
+ to_s
+ end
+ NOT_PASSED.freeze
+end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 01a98fda39..9822920a7d 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -54,12 +54,13 @@ class Chef
VERSION = 'version'.freeze
SOURCE_URL = 'source_url'.freeze
ISSUES_URL = 'issues_url'.freeze
+ PRIVACY = 'privacy'.freeze
COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
:maintainer_email, :license, :platforms, :dependencies,
:recommendations, :suggestions, :conflicting, :providing,
:replacing, :attributes, :groupings, :recipes, :version,
- :source_url, :issues_url ]
+ :source_url, :issues_url, :privacy ]
VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
:recommends => RECOMMENDATIONS,
@@ -116,6 +117,7 @@ class Chef
@version = Version.new("0.0.0")
@source_url = ''
@issues_url = ''
+ @privacy = false
@errors = []
end
@@ -454,7 +456,8 @@ class Chef
:recipes => { :kind_of => [ Array ], :default => [] },
:default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] },
:source_url => { :kind_of => String },
- :issues_url => { :kind_of => String }
+ :issues_url => { :kind_of => String },
+ :privacy => { :kind_of => [ TrueClass, FalseClass ] }
}
)
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
@@ -498,7 +501,8 @@ class Chef
RECIPES => self.recipes,
VERSION => self.version,
SOURCE_URL => self.source_url,
- ISSUES_URL => self.issues_url
+ ISSUES_URL => self.issues_url,
+ PRIVACY => self.privacy
}
end
@@ -532,6 +536,7 @@ class Chef
@version = o[VERSION] if o.has_key?(VERSION)
@source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
@issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
+ @privacy = o[PRIVACY] if o.has_key?(PRIVACY)
self
end
@@ -590,6 +595,23 @@ class Chef
)
end
+ #
+ # Sets the cookbook's privacy flag, or returns it.
+ #
+ # === Parameters
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ # === Returns
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ def privacy(arg=nil)
+ set_or_return(
+ :privacy,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
private
def run_validation
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 1b96d0510b..fc8e739d73 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -131,7 +131,7 @@ class Chef
files_remaining_by_cookbook[file.cookbook] -= 1
if files_remaining_by_cookbook[file.cookbook] == 0
- @events.synchronized_cookbook(file.cookbook.name)
+ @events.synchronized_cookbook(file.cookbook.name, file.cookbook)
end
end
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 8d302eeec2..bff3146572 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -51,12 +51,12 @@ class Chef
attr_accessor :metadata_filenames
def status=(new_status)
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1))
@status = new_status
end
def status
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1))
@status
end
@@ -480,7 +480,7 @@ class Chef
# @deprecated This method was used by the Ruby Chef Server and is no longer
# needed. There is no replacement.
def generate_manifest_with_urls(&url_generator)
- Chef::Log.deprecation("Deprecated method #generate_manifest_with_urls called from #{caller(1).first}")
+ Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1))
rendered_manifest = manifest.dup
COOKBOOK_SEGMENTS.each do |segment|
diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb
new file mode 100644
index 0000000000..9f18a53445
--- /dev/null
+++ b/lib/chef/delayed_evaluator.rb
@@ -0,0 +1,21 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ class DelayedEvaluator < Proc
+ end
+end
diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb
index 36d18ad90d..58a661c4bd 100644
--- a/lib/chef/deprecation/mixin/template.rb
+++ b/lib/chef/deprecation/mixin/template.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::Mixin::Template
# This module contains the deprecated functions of
# Chef::Mixin::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module Template
@@ -46,4 +46,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb
index dfbf4a39a4..92f5ce3623 100644
--- a/lib/chef/deprecation/provider/cookbook_file.rb
+++ b/lib/chef/deprecation/provider/cookbook_file.rb
@@ -24,7 +24,7 @@ class Chef
# == Deprecation::Provider::CookbookFile
# This module contains the deprecated functions of
# Chef::Provider::CookbookFile. These functions are refactored to
- # different components. They are frozen and will be removed in Chef 12.
+ # different components. They are frozen and will be removed in Chef 13.
#
module CookbookFile
diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb
index 125f31fe10..31038ab3d8 100644
--- a/lib/chef/deprecation/provider/file.rb
+++ b/lib/chef/deprecation/provider/file.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::File
# This module contains the deprecated functions of
# Chef::Provider::File. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module File
diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb
index 4452de67cd..c06a5cc695 100644
--- a/lib/chef/deprecation/provider/remote_file.rb
+++ b/lib/chef/deprecation/provider/remote_file.rb
@@ -23,7 +23,7 @@ class Chef
# == Deprecation::Provider::RemoteFile
# This module contains the deprecated functions of
# Chef::Provider::RemoteFile. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module RemoteFile
@@ -83,4 +83,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb
index d7a228e97a..34e5f54b7e 100644
--- a/lib/chef/deprecation/provider/template.rb
+++ b/lib/chef/deprecation/provider/template.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::Template
# This module contains the deprecated functions of
# Chef::Provider::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module Template
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb
index 34f468ff53..376629710e 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/deprecation/warnings.rb
@@ -25,10 +25,9 @@ class Chef
m = instance_method(name)
define_method(name) do |*args|
message = []
- message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
- message << "Please update your cookbooks accordingly. Accessed from:"
- caller[0..3].each {|l| message << l}
- Chef::Log.deprecation message
+ message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13."
+ message << "Please update your cookbooks accordingly."
+ Chef.log_deprecation(message, caller(0..3))
super(*args)
end
end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index 7af67e94a5..c577118dd4 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -45,7 +45,7 @@ class Chef
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
# Vista + Server 2008 and newer may have reboots pending from CBS
- registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') ||
# The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
# http://support.microsoft.com/kb/832475
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index d69f0a8f11..26c0ec6768 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -19,12 +19,10 @@
require 'chef/mixin/convert_to_class_name'
require 'chef/exceptions'
-require 'chef/resource_builder'
require 'chef/mixin/shell_out'
require 'chef/mixin/powershell_out'
require 'chef/dsl/resources'
require 'chef/dsl/definitions'
-require 'chef/resource'
class Chef
module DSL
@@ -122,9 +120,9 @@ class Chef
def describe_self_for_error
if respond_to?(:name)
- %Q[`#{self.class.name} "#{name}"']
+ %Q[`#{self.class} "#{name}"']
elsif respond_to?(:recipe_name)
- %Q[`#{self.class.name} "#{recipe_name}"']
+ %Q[`#{self.class} "#{recipe_name}"']
else
to_s
end
@@ -142,8 +140,7 @@ class Chef
# method_missing manually. Not a fan. Not. A. Fan.
#
if respond_to?(method_symbol)
- Chef::Log.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.")
- Chef::Log.deprecation("Use public_send() or send() instead.")
+ Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.")
return send(method_symbol, *args, &block)
end
@@ -152,7 +149,7 @@ class Chef
# never called. DEPRECATED.
#
if run_context.definitions.has_key?(method_symbol.to_sym)
- Chef::Log.deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.")
+ Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.")
Chef::DSL::Definitions.add_definition(method_symbol)
return send(method_symbol, *args, &block)
end
@@ -176,10 +173,32 @@ class Chef
raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
end
end
+
+ module FullDSL
+ require 'chef/dsl/data_query'
+ require 'chef/dsl/platform_introspection'
+ require 'chef/dsl/include_recipe'
+ require 'chef/dsl/registry_helper'
+ require 'chef/dsl/reboot_pending'
+ require 'chef/dsl/audit'
+ require 'chef/dsl/powershell'
+ include Chef::DSL::DataQuery
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::IncludeRecipe
+ include Chef::DSL::Recipe
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
+ include Chef::DSL::Powershell
+ end
end
end
end
+# Avoid circular references for things that are only used in instance methods
+require 'chef/resource_builder'
+require 'chef/resource'
+
# **DEPRECATED**
# This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code.
require 'chef/mixin/recipe_definition_dsl_core'
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
index 1ce12ed0a0..49588ed516 100644
--- a/lib/chef/dsl/resources.rb
+++ b/lib/chef/dsl/resources.rb
@@ -10,14 +10,16 @@ class Chef
def self.add_resource_dsl(dsl_name)
begin
module_eval(<<-EOM, __FILE__, __LINE__+1)
- def #{dsl_name}(name=nil, created_at=nil, &block)
- declare_resource(#{dsl_name.inspect}, name, created_at || caller[0], &block)
+ def #{dsl_name}(*args, &block)
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1
+ declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block)
end
EOM
rescue SyntaxError
# Handle the case where dsl_name has spaces, etc.
- define_method(dsl_name.to_sym) do |name=nil, created_at=nil, &block|
- declare_resource(dsl_name, name, created_at || caller[0], &block)
+ define_method(dsl_name.to_sym) do |*args, &block|
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1
+ declare_resource(dsl_name, args[0], caller[0], &block)
end
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 73fe25ec13..1c9a58be23 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -47,14 +47,19 @@ class Chef
def ohai_completed(node)
end
- # Already have a client key, assuming this node has registered.
+ # Announce that we're not going to register the client. Generally because
+ # we already have the private key, or because we're deliberately not using
+ # a key.
def skipping_registration(node_name, config)
end
- # About to attempt to register as +node_name+
+ # About to attempt to create a private key registered to the server with
+ # client +node_name+.
def registration_start(node_name, config)
end
+ # Successfully created the private key and registered this client with the
+ # server.
def registration_completed
end
@@ -118,8 +123,8 @@ class Chef
def cookbook_sync_start(cookbook_count)
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
end
# Called when an individual file in a cookbook has been updated
@@ -269,26 +274,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change (many per action)
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -302,21 +318,33 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
# A stream has opened.
def stream_opened(stream, options = {})
end
@@ -352,8 +380,9 @@ class Chef
def whyrun_assumption(action, resource, message)
end
- ## TODO: deprecation warning. this way we can queue them up and present
- # them all at once.
+ # Emit a message about something being deprecated.
+ def deprecation(message, location=caller(2..2)[0])
+ end
# An uncategorized message. This supports the case that a user needs to
# pass output that doesn't fit into one of the callbacks above. Note that
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 370f8c51b4..966a3f32ec 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -25,18 +25,30 @@ class Chef
# define the forwarding in one go:
#
- # Define a method that will be forwarded to all
- def self.def_forwarding_method(method_name)
- define_method(method_name) do |*args|
- @subscribers.each { |s| s.send(method_name, *args) }
+ def call_subscribers(method_name, *args)
+ @subscribers.each do |s|
+ # Skip new/unsupported event names.
+ next if !s.respond_to?(method_name)
+ mth = s.method(method_name)
+ # Trim arguments to match what the subscriber expects to allow
+ # adding new arguments without breaking compat.
+ args = args.take(mth.arity) if mth.arity < args.size && mth.arity >= 0
+ mth.call(*args)
end
end
(Base.instance_methods - Object.instance_methods).each do |method_name|
- def_forwarding_method(method_name)
+ class_eval <<-EOM
+ def #{method_name}(*args)
+ call_subscribers(#{method_name.inspect}, *args)
+ end
+ EOM
end
+ # Special case deprecation, since it needs to know its caller
+ def deprecation(message, location=caller(2..2)[0])
+ call_subscribers(:deprecation, message, location)
+ end
end
end
end
-
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
new file mode 100644
index 0000000000..c6f21c9b45
--- /dev/null
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Ranjib Dey (<ranjib@linux.com>)
+# Copyright:: Copyright (c) 2015 Ranjib Dey
+# 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/event_dispatch/base'
+require 'chef/exceptions'
+require 'chef/config'
+
+class Chef
+ module EventDispatch
+ class DSL
+ attr_reader :handler
+
+ def initialize(name)
+ klass = Class.new(Chef::EventDispatch::Base) do
+ attr_reader :name
+ end
+ @handler = klass.new
+ @handler.instance_variable_set(:@name, name)
+
+ # Use event.register API to add anonymous handler if Chef.run_context
+ # and associated event dispatcher is set, else fallback to
+ # Chef::Config[:hanlder]
+ if Chef.run_context && Chef.run_context.events
+ Chef::Log.debug("Registering handler '#{name}' using events api")
+ Chef.run_context.events.register(handler)
+ else
+ Chef::Log.debug("Registering handler '#{name}' using global config")
+ Chef::Config[:event_handlers] << handler
+ end
+ end
+
+ # Adds a new event handler derived from base handler
+ # with user defined block against a chef event
+ #
+ # @return [Chef::EventDispatch::Base] a base handler object
+ def on(event_type, &block)
+ validate!(event_type)
+ handler.define_singleton_method(event_type) do |*args|
+ instance_exec(*args, &block)
+ end
+ end
+
+ private
+ def validate!(event_type)
+ all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods)
+ raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type)
+ end
+ end
+ end
+end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index dd0bac3cf9..e3649c068b 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -17,12 +17,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef-config/exceptions'
+
class Chef
# == Chef::Exceptions
# Chef's custom exceptions are all contained within the Chef::Exceptions
# namespace.
class Exceptions
+ ConfigurationError = ChefConfig::ConfigurationError
+
# Backcompat with Chef::ShellOut code:
require 'mixlib/shellout/exceptions'
@@ -68,7 +72,6 @@ class Chef
class DuplicateRole < RuntimeError; end
class ValidationFailed < ArgumentError; end
class InvalidPrivateKey < ArgumentError; end
- class ConfigurationError < ArgumentError; end
class MissingKeyAttribute < ArgumentError; end
class KeyCommandInputError < ArgumentError; end
class InvalidKeyArgument < ArgumentError; end
@@ -97,7 +100,14 @@ class Chef
class ConflictingMembersInGroup < ArgumentError; end
class InvalidResourceReference < RuntimeError; end
class ResourceNotFound < RuntimeError; end
+ class ProviderNotFound < RuntimeError; end
+ NoProviderAvailable = ProviderNotFound
class VerificationNotFound < RuntimeError; end
+ class InvalidEventType < ArgumentError; end
+ class MultipleIdentityError < RuntimeError; end
+ # Used in Resource::ActionProvider#load_current_resource to denote that
+ # the resource doesn't actually exist (for example, the file does not exist)
+ class CurrentValueDoesNotExist < RuntimeError; end
# Can't find a Resource of this type that is valid on this platform.
class NoSuchResourceType < NameError
@@ -119,6 +129,23 @@ class Chef
class EnclosingDirectoryDoesNotExist < ArgumentError; end
# Errors originating from calls to the Win32 API
class Win32APIError < RuntimeError; end
+
+ class Win32NetAPIError < Win32APIError
+ attr_reader :msg, :error_code
+ def initialize(msg, error_code)
+ @msg = msg
+ @error_code = error_code
+
+ formatted_message = ""
+ formatted_message << "---- Begin Win32 API output ----\n"
+ formatted_message << "Net Api Error Code: #{error_code}\n"
+ formatted_message << "Net Api Error Message: #{msg}\n"
+ formatted_message << "---- End Win32 API output ----\n"
+
+ super(formatted_message)
+ end
+ end
+
# Thrown when Win32 API layer binds to non-existent Win32 function. Occurs
# when older versions of Windows don't support newer Win32 API functions.
class Win32APIFunctionNotImplemented < NotImplementedError; end
@@ -218,8 +245,6 @@ class Chef
class ChildConvergeError < RuntimeError; end
- class NoProviderAvailable < RuntimeError; end
-
class DeprecatedFeatureError < RuntimeError;
def initalize(message)
super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)")
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
index c901068aa0..d3756ef00c 100644
--- a/lib/chef/formatters/base.rb
+++ b/lib/chef/formatters/base.rb
@@ -212,6 +212,9 @@ class Chef
file_load_failed(path, exception)
end
+ def deprecation(message, location=caller(2..2)[0])
+ Chef::Log.deprecation("#{message} at #{location}")
+ end
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index e76a940c38..614cc44e6d 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -22,6 +22,7 @@ class Chef
@failed_audits = 0
@start_time = Time.now
@end_time = @start_time
+ @skipped_resources = 0
end
def elapsed_time
@@ -33,7 +34,7 @@ class Chef
end
def total_resources
- @up_to_date_resources + @updated_resources
+ @up_to_date_resources + @updated_resources + @skipped_resources
end
def total_audits
@@ -42,6 +43,26 @@ class Chef
def run_completed(node)
@end_time = Time.now
+ # Print out deprecations.
+ if !deprecations.empty?
+ puts_line ""
+ puts_line "Deprecated features used!"
+ deprecations.each do |message, locations|
+ if locations.size == 1
+ puts_line " #{message} at #{locations.size} location:"
+ else
+ puts_line " #{message} at #{locations.size} locations:"
+ end
+ locations.each do |location|
+ prefix = " - "
+ Array(location).each do |line|
+ puts_line "#{prefix}#{line}"
+ prefix = " "
+ end
+ end
+ end
+ puts_line ""
+ end
if Chef::Config[:why_run]
puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
else
@@ -132,9 +153,9 @@ class Chef
indent
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
- puts_line "- #{cookbook_name}"
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
+ puts_line "- #{cookbook.name} (#{cookbook.version})"
end
# Called when an individual file in a cookbook has been updated
@@ -236,6 +257,7 @@ class Chef
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
+ @skipped_resources += 1
# TODO: more info about conditional
puts " (skipped due to #{conditional.short_description})", :stream => resource
unindent
@@ -334,6 +356,16 @@ class Chef
end
end
+ def deprecation(message, location=caller(2..2)[0])
+ if Chef::Config[:treat_deprecation_warnings_as_errors]
+ super
+ end
+
+ # Save deprecations to the screen until the end
+ deprecations[message] ||= Set.new
+ deprecations[message] << location
+ end
+
def indent
indent_by(2)
end
@@ -341,6 +373,12 @@ class Chef
def unindent
indent_by(-2)
end
+
+ protected
+
+ def deprecations
+ @deprecations ||= {}
+ end
end
end
end
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index d64d5e7b01..fe418ed485 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -44,6 +44,18 @@ class Chef
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
end
+
+ if exception_message_modifying_frozen?
+ msg = <<-MESSAGE
+ Chef calls the freeze method on certain ruby objects to prevent
+ pollution across multiple instances. Specifically, resource
+ properties have frozen default values to avoid modifying the
+ property for all instances of a resource. Try modifying the
+ particular instance variable or using an instance accessor instead.
+ MESSAGE
+
+ error_description.section("Additional information:", msg.gsub(/^ {6}/, ''))
+ end
end
def context
@@ -111,6 +123,10 @@ class Chef
end
end
+ def exception_message_modifying_frozen?
+ exception.message.include?("can't modify frozen")
+ end
+
end
end
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
index a189cc67eb..3862951f76 100644
--- a/lib/chef/formatters/minimal.rb
+++ b/lib/chef/formatters/minimal.rb
@@ -109,8 +109,8 @@ class Chef
puts "Synchronizing cookbooks"
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
print "."
end
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index d4b386a15a..8cff3bc032 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -68,7 +68,10 @@ class Chef
run_action = action || @resource.action
begin
- @resource.run_action(run_action)
+ # Coerce to an array to be safe. This could happen with a legacy
+ # resource or something overriding the default_action code in a
+ # subclass.
+ Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) }
resource_updated = @resource.updated
rescue Mixlib::ShellOut::ShellCommandFailed
resource_updated = nil
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
index 7582f4458f..1baf5724ae 100644
--- a/lib/chef/http/http_request.rb
+++ b/lib/chef/http/http_request.rb
@@ -40,7 +40,7 @@ class Chef
engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
- UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
+ UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)"
DEFAULT_UA = "Chef Client" << UA_COMMON
USER_AGENT = "User-Agent".freeze
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 4a93697a1b..46e968827e 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -87,6 +87,7 @@ class Chef
def self.inherited(subclass)
unless subclass.unnamed?
subcommands[subclass.snake_case_name] = subclass
+ subcommand_files[subclass.snake_case_name] += [caller[0].split(/:\d+/).first]
end
end
@@ -121,17 +122,29 @@ class Chef
end
def self.subcommand_loader
- @subcommand_loader ||= Knife::SubcommandLoader.new(chef_config_dir)
+ @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir)
end
def self.load_commands
@commands_loaded ||= subcommand_loader.load_commands
end
+ def self.guess_category(args)
+ subcommand_loader.guess_category(args)
+ end
+
+ def self.subcommand_class_from(args)
+ subcommand_loader.command_class_from(args) || subcommand_not_found!(args)
+ end
+
def self.subcommands
@@subcommands ||= {}
end
+ def self.subcommand_files
+ @@subcommand_files ||= Hash.new([])
+ end
+
def self.subcommands_by_category
unless @subcommands_by_category
@subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
@@ -142,30 +155,6 @@ class Chef
@subcommands_by_category
end
- # Print the list of subcommands knife knows about. If +preferred_category+
- # is given, only subcommands in that category are shown
- def self.list_commands(preferred_category=nil)
- load_commands
-
- category_desc = preferred_category ? preferred_category + " " : ''
- msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
-
- if preferred_category && subcommands_by_category.key?(preferred_category)
- commands_to_show = {preferred_category => subcommands_by_category[preferred_category]}
- else
- commands_to_show = subcommands_by_category
- end
-
- commands_to_show.sort.each do |category, commands|
- next if category =~ /deprecated/i
- msg "** #{category.upcase} COMMANDS **"
- commands.sort.each do |command|
- msg subcommands[command].banner if subcommands[command]
- end
- msg
- end
- end
-
# Shared with subclasses
@@chef_config_dir = nil
@@ -206,7 +195,6 @@ class Chef
Chef::Log.level(:debug)
end
- load_commands
subcommand_class = subcommand_class_from(args)
subcommand_class.options = options.merge!(subcommand_class.options)
subcommand_class.load_deps
@@ -215,34 +203,6 @@ class Chef
instance.run_with_pretty_exceptions
end
- def self.guess_category(args)
- category_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
- category_words.map! {|w| w.split('-')}.flatten!
- matching_category = nil
- while (!matching_category) && (!category_words.empty?)
- candidate_category = category_words.join(' ')
- matching_category = candidate_category if subcommands_by_category.key?(candidate_category)
- matching_category || category_words.pop
- end
- matching_category
- end
-
- def self.subcommand_class_from(args)
- command_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
-
- subcommand_class = nil
-
- while ( !subcommand_class ) && ( !command_words.empty? )
- snake_case_class_name = command_words.join("_")
- unless subcommand_class = subcommands[snake_case_class_name]
- command_words.pop
- end
- end
- # see if we got the command as e.g., knife node-list
- subcommand_class ||= subcommands[args.first.gsub('-', '_')]
- subcommand_class || subcommand_not_found!(args)
- end
-
def self.dependency_loaders
@dependency_loaders ||= []
end
@@ -265,7 +225,13 @@ class Chef
# Error out and print usage. probably because the arguments given by the
# user could not be resolved to a subcommand.
def self.subcommand_not_found!(args)
- ui.fatal("Cannot find sub command for: '#{args.join(' ')}'")
+ ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'")
+
+ # Mention rehash when the subcommands cache(plugin_manifest.json) is used
+ if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) ||
+ subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader)
+ ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
+ end
if category_commands = guess_category(args)
list_commands(category_commands)
@@ -280,6 +246,20 @@ class Chef
exit 10
end
+ def self.list_commands(preferred_category=nil)
+ category_desc = preferred_category ? preferred_category + " " : ''
+ msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
+ subcommand_loader.list_commands(preferred_category).sort.each do |category, commands|
+ next if category =~ /deprecated/i
+ msg "** #{category.upcase} COMMANDS **"
+ commands.sort.each do |command|
+ subcommand_loader.load_command(command)
+ msg subcommands[command].banner if subcommands[command]
+ end
+ msg
+ end
+ end
+
def self.reset_config_path!
@@chef_config_dir = nil
end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 5b29591fcc..f173b6b909 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -143,6 +143,12 @@ class Chef
:proc => lambda { |o| o.split(/[\s,]+/) },
:default => []
+ option :tags,
+ :long => "--tags TAGS",
+ :description => "Comma separated list of tags to apply to the node",
+ :proc => lambda { |o| o.split(/[\s,]+/) },
+ :default => []
+
option :first_boot_attributes,
:short => "-j JSON_ATTRIBS",
:long => "--json-attributes",
diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb
index 749f61e6da..f658957499 100644
--- a/lib/chef/knife/bootstrap/chef_vault_handler.rb
+++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb
@@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+require 'chef/knife/bootstrap'
class Chef
class Knife
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
index b9c1d98bec..59b0cabd49 100644
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ b/lib/chef/knife/bootstrap/client_builder.rb
@@ -20,6 +20,7 @@ require 'chef/node'
require 'chef/rest'
require 'chef/api_client/registration'
require 'chef/api_client'
+require 'chef/knife/bootstrap'
require 'tmpdir'
class Chef
@@ -140,6 +141,9 @@ class Chef
node.run_list(normalized_run_list)
node.normal_attrs = first_boot_attributes if first_boot_attributes
node.environment(environment) if environment
+ (knife_config[:tags] || []).each do |tag|
+ node.tags << tag
+ end
node
end
end
diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
deleted file mode 100644
index 55d2c0cc12..0000000000
--- a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
+++ /dev/null
@@ -1,76 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- pacman -Syy
- 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 <%= Chef::VERSION %>
-fi
-
-mkdir -p /etc/chef
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% 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 -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% 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'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-log_level :info
-log_location STDOUT
-chef_server_url "<%= @chef_config[:chef_server_url] %>"
-validation_client_name "<%= @chef_config[:validation_client_name] %>"
-<% if @config[:chef_node_name] -%>
-node_name "<%= @config[:chef_node_name] %>"
-<% else -%>
-# Using default node name (fqdn)
-<% end -%>
-# ArchLinux follows the Filesystem Hierarchy Standard
-file_cache_path "/var/cache/chef"
-file_backup_path "/var/lib/chef/backup"
-pid_file "/var/run/chef/client.pid"
-cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true})
-<% if knife_config[:bootstrap_proxy] %>
-http_proxy "<%= knife_config[:bootstrap_proxy] %>"
-https_proxy "<%= knife_config[:bootstrap_proxy] %>"
-<% end -%>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-aix.erb b/lib/chef/knife/bootstrap/templates/chef-aix.erb
deleted file mode 100644
index 45fbba7b48..0000000000
--- a/lib/chef/knife/bootstrap/templates/chef-aix.erb
+++ /dev/null
@@ -1,72 +0,0 @@
-ksh -c '
-
-function exists {
- if type $1 >/dev/null 2>&1
- then
- return 0
- else
- return 1
- fi
-}
-
-if ! exists /usr/bin/chef-client; then
- <% if @chef_config[:aix_package] -%>
- # Read the download URL/location from knife.rb with option aix_package
- rm -rf /tmp/chef_installer # ensure there no older pkg
- echo "<%= @chef_config[:aix_package] %>"
- perl -e '\''use LWP::Simple; getprint($ARGV[0]);'\'' <%= @chef_config[:aix_package] %> > /tmp/chef_installer
- installp -aYF -d /tmp/chef_installer chef
- <% else -%>
- echo ":aix_package location is not set in knife.rb"
- exit
- <% end -%>
-fi
-
-mkdir -p /etc/chef
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% 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 -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% 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'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index 335b1f181c..575aec0f50 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -12,7 +12,7 @@ tmp_dir="$tmp/install.sh.$$"
(umask 077 && mkdir $tmp_dir) || exit 1
exists() {
- if command -v $1 &>/dev/null
+ if command -v $1 >/dev/null 2>&1
then
return 0
else
@@ -166,12 +166,12 @@ do_download() {
<%= knife_config[:bootstrap_install_command] %>
<% else %>
install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
- if ! exists /usr/bin/chef-client; then
+ if test -f /usr/bin/chef-client; then
+ echo "-----> Existing Chef installation detected"
+ else
echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
do_download ${install_sh} $tmp_dir/install.sh
sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
- else
- echo "-----> Existing Chef installation detected"
fi
<% end %>
@@ -226,6 +226,6 @@ cat > /etc/chef/first-boot.json <<EOP
<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
-echo "Starting first Chef Client run..."
+echo "Starting the first Chef Client run..."
<%= start_chef %>'
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index f2be772759..b439e6f995 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -23,7 +23,7 @@ class Chef
class ClientBulkDelete < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -39,7 +39,7 @@ class Chef
ui.fatal("You must supply a regular expression to match the results against")
exit 42
end
- all_clients = Chef::ApiClient.list(true)
+ all_clients = Chef::ApiClientV1.list(true)
matcher = /#{name_args[0]}/
clients_to_delete = {}
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 570c1ee950..fa9a1a7e32 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -23,7 +23,7 @@ class Chef
class ClientCreate < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -57,12 +57,12 @@ class Chef
banner "knife client create CLIENTNAME (options)"
def client
- @client_field ||= Chef::ApiClient.new
+ @client_field ||= Chef::ApiClientV1.new
end
def create_client(client)
# should not be using save :( bad behavior
- client.save
+ Chef::ApiClientV1.from_hash(client).save
end
def run
@@ -93,7 +93,7 @@ class Chef
output = edit_data(client)
final_client = create_client(output)
- ui.info("Created #{output}")
+ ui.info("Created #{final_client}")
# output private_key if one
if final_client.private_key
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index d7d302ee1d..a49c0867a8 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -23,7 +23,7 @@ class Chef
class ClientDelete < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -43,8 +43,8 @@ class Chef
exit 1
end
- delete_object(Chef::ApiClient, @client_name, 'client') {
- object = Chef::ApiClient.load(@client_name)
+ delete_object(Chef::ApiClientV1, @client_name, 'client') {
+ object = Chef::ApiClientV1.load(@client_name)
if object.validator
unless config[:delete_validators]
ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}")
diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb
index c81bce902a..5dcd8f212b 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/knife/client_edit.rb
@@ -23,7 +23,7 @@ class Chef
class ClientEdit < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -38,7 +38,15 @@ class Chef
exit 1
end
- edit_object(Chef::ApiClient, @client_name)
+ original_data = Chef::ApiClientV1.load(@client_name).to_hash
+ edited_client = edit_data(original_data)
+ if original_data != edited_client
+ client = Chef::ApiClientV1.from_hash(edited_client)
+ client.save
+ ui.msg("Saved #{client}.")
+ else
+ ui.msg("Client unchanged, not saving.")
+ end
end
end
end
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
index da0bf12dc3..d8a3698b6a 100644
--- a/lib/chef/knife/client_list.rb
+++ b/lib/chef/knife/client_list.rb
@@ -23,7 +23,7 @@ class Chef
class ClientList < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -35,7 +35,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::ApiClient.list))
+ output(format_list_for_display(Chef::ApiClientV1.list))
end
end
end
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
index 666fd09fd2..b94761e718 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -23,7 +23,7 @@ class Chef
class ClientReregister < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -43,7 +43,7 @@ class Chef
exit 1
end
- client = Chef::ApiClient.reregister(@client_name)
+ client = Chef::ApiClientV1.reregister(@client_name)
Chef::Log.debug("Updated client data: #{client.inspect}")
key = client.private_key
if config[:file]
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
index 822848fdc2..bdac3f9758 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -25,7 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -40,7 +40,7 @@ class Chef
exit 1
end
- client = Chef::ApiClient.load(@client_name)
+ client = Chef::ApiClientV1.load(@client_name)
output(format_for_display(client))
end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 7197653489..867b6fe366 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -163,11 +163,14 @@ CONFIG
end
def first_boot
- (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list)
+ (@config[:first_boot_attributes] || {}).tap do |attributes|
+ attributes.merge!(:run_list => @run_list)
+ attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty?
+ end
end
private
-
+
# Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
# This string should contain both the commands necessary to both create the files, as well as their content
def trusted_certs_content
diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb
new file mode 100644
index 0000000000..c19e749f32
--- /dev/null
+++ b/lib/chef/knife/core/custom_manifest_loader.rb
@@ -0,0 +1,69 @@
+# Copyright:: Copyright (c) 2015 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/version'
+class Chef
+ class Knife
+ class SubcommandLoader
+
+ #
+ # Load a subcommand from a user-supplied
+ # manifest file
+ #
+ class CustomManifestLoader < Chef::Knife::SubcommandLoader
+ attr_accessor :manifest
+ def initialize(chef_config_dir, plugin_manifest)
+ super(chef_config_dir)
+ @manifest = plugin_manifest
+ end
+
+ # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
+ # that instead of inspecting the on-system gems to find the plugins. The
+ # file format is expected to look like:
+ #
+ # { "plugins": {
+ # "knife-ec2": {
+ # "paths": [
+ # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
+ # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
+ # ]
+ # }
+ # }
+ # }
+ #
+ # Extraneous content in this file is ignored. This is intentional so that we
+ # can adapt the file format for potential behavior changes to knife in
+ # the future.
+ def find_subcommands_via_manifest
+ # Format of subcommand_files is "relative_path" (something you can
+ # Kernel.require()) => full_path. The relative path isn't used
+ # currently, so we just map full_path => full_path.
+ subcommand_files = {}
+ manifest["plugins"].each do |plugin_name, plugin_manifest|
+ plugin_manifest["paths"].each do |cmd_path|
+ subcommand_files[cmd_path] = cmd_path
+ end
+ end
+ subcommand_files.merge(find_subcommands_via_dirglob)
+ end
+
+ def subcommand_files
+ subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/gem_glob_loader.rb b/lib/chef/knife/core/gem_glob_loader.rb
new file mode 100644
index 0000000000..d09131aacb
--- /dev/null
+++ b/lib/chef/knife/core/gem_glob_loader.rb
@@ -0,0 +1,138 @@
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2009-2015 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/version'
+require 'chef/util/path_helper'
+class Chef
+ class Knife
+ class SubcommandLoader
+ class GemGlobLoader < Chef::Knife::SubcommandLoader
+ MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
+ MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
+
+ def subcommand_files
+ @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ end
+
+ # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
+ # If rubygems is not installed, falls back to globbing the knife directory.
+ # The Hash is of the form {"relative/path" => "/absolute/path"}
+ #--
+ # Note: the "right" way to load the plugins is to require the relative path, i.e.,
+ # require 'chef/knife/command'
+ # but we're getting frustrated by bugs at every turn, and it's slow besides. So
+ # subcommand loader has been modified to load the plugins by using Kernel.load
+ # with the absolute path.
+ def gem_and_builtin_subcommands
+ require 'rubygems'
+ find_subcommands_via_rubygems
+ rescue LoadError
+ find_subcommands_via_dirglob
+ end
+
+ def find_subcommands_via_dirglob
+ # The "require paths" of the core knife subcommands bundled with chef
+ files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
+ subcommand_files = {}
+ files.each do |knife_file|
+ rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/,1]
+ subcommand_files[rel_path] = knife_file
+ end
+ subcommand_files
+ end
+
+ def find_subcommands_via_rubygems
+ files = find_files_latest_gems 'chef/knife/*.rb'
+ subcommand_files = {}
+ files.each do |file|
+ rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
+
+ # When not installed as a gem (ChefDK/appbundler in particular), AND
+ # a different version of Chef is installed via gems, `files` will
+ # include some files from the 'other' Chef install. If this contains
+ # a knife command that doesn't exist in this version of Chef, we will
+ # get a LoadError later when we try to require it.
+ next if from_different_chef_version?(file)
+
+ subcommand_files[rel_path] = file
+ end
+
+ subcommand_files.merge(find_subcommands_via_dirglob)
+ end
+
+ private
+
+ def find_files_latest_gems(glob, check_load_path=true)
+ files = []
+
+ if check_load_path
+ files = $LOAD_PATH.map { |load_path|
+ Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
+ }.flatten.select { |file| File.file? file.untaint }
+ end
+
+ gem_files = latest_gem_specs.map do |spec|
+ # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
+ if spec.respond_to? :matches_for_glob
+ spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ else
+ check_spec_for_glob(spec, glob)
+ end
+ end.flatten
+
+ files.concat gem_files
+ files.uniq! if check_load_path
+
+ return files
+ end
+
+ def latest_gem_specs
+ @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
+ Gem::Specification.latest_specs(true) # find prerelease gems
+ else
+ Gem.source_index.latest_specs(true)
+ end
+ end
+
+ def check_spec_for_glob(spec, glob)
+ dirs = if spec.require_paths.size > 1 then
+ "{#{spec.require_paths.join(',')}}"
+ else
+ spec.require_paths.first
+ end
+
+ glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
+
+ Dir[glob].map { |f| f.untaint }
+ end
+
+ def from_different_chef_version?(path)
+ matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
+ end
+
+ def matches_any_chef_gem?(path)
+ path =~ MATCHES_CHEF_GEM
+ end
+
+ def matches_this_chef_gem?(path)
+ path =~ MATCHES_THIS_CHEF_GEM
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
new file mode 100644
index 0000000000..6eb3635726
--- /dev/null
+++ b/lib/chef/knife/core/hashed_command_loader.rb
@@ -0,0 +1,80 @@
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 2015 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/version'
+class Chef
+ class Knife
+ class SubcommandLoader
+ #
+ # Load a subcommand from a pre-computed path
+ # for the given command.
+ #
+ class HashedCommandLoader < Chef::Knife::SubcommandLoader
+ KEY = '_autogenerated_command_paths'
+
+ attr_accessor :manifest
+ def initialize(chef_config_dir, plugin_manifest)
+ super(chef_config_dir)
+ @manifest = plugin_manifest
+ end
+
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split('-') }.flatten!
+ find_longest_key(manifest[KEY]["plugins_by_category"], category_words, ' ')
+ end
+
+ def list_commands(pref_category=nil)
+ if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category)
+ { pref_category => manifest[KEY]["plugins_by_category"][pref_category] }
+ else
+ manifest[KEY]["plugins_by_category"]
+ end
+ end
+
+ def subcommand_files
+ manifest[KEY]["plugins_paths"].values.flatten
+ end
+
+ def load_command(args)
+ paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)]
+ if paths.nil? || paths.empty? || (! paths.is_a? Array)
+ false
+ else
+ paths.each do |sc|
+ if File.exists?(sc)
+ Kernel.load sc
+ else
+ Chef::Log.error "The file #{sc} is missing for subcommand '#{subcommand_for_args(args)}'. Please rehash to update the subcommands cache."
+ return false
+ end
+ end
+ true
+ end
+ end
+
+ def subcommand_for_args(args)
+ if manifest[KEY]["plugins_paths"].key?(args)
+ args
+ else
+ find_longest_key(manifest[KEY]["plugins_paths"], args, "_")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
index 698b09ac84..97ca381471 100644
--- a/lib/chef/knife/core/object_loader.rb
+++ b/lib/chef/knife/core/object_loader.rb
@@ -18,6 +18,7 @@
require 'ffi_yajl'
require 'chef/util/path_helper'
+require 'chef/data_bag_item'
class Chef
class Knife
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index a8705c724f..808e053c40 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -18,105 +18,120 @@
require 'chef/version'
require 'chef/util/path_helper'
+require 'chef/knife/core/gem_glob_loader'
+require 'chef/knife/core/hashed_command_loader'
+require 'chef/knife/core/custom_manifest_loader'
+
class Chef
class Knife
+ #
+ # Public Methods of a Subcommand Loader
+ #
+ # load_commands - loads all available subcommands
+ # load_command(args) - loads subcommands for the given args
+ # list_commands(args) - lists all available subcommands,
+ # optionally filtering by category
+ # subcommand_files - returns an array of all subcommand files
+ # that could be loaded
+ # commnad_class_from(args) - returns the subcommand class for the
+ # user-requested command
+ #
class SubcommandLoader
-
- MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
-
attr_reader :chef_config_dir
attr_reader :env
- def initialize(chef_config_dir, env=nil)
+ # A small factory method. Eventually, this is the only place
+ # where SubcommandLoader should know about its subclasses, but
+ # to maintain backwards compatibility many of the instance
+ # methods in this base class contain default implementations
+ # of the functions sub classes should otherwise provide
+ # or directly instantiate the appropriate subclass
+ def self.for_config(chef_config_dir)
+ if autogenerated_manifest?
+ Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}")
+ Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
+ elsif custom_manifest?
+ Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.")
+ Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest)
+ else
+ Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+ end
+ end
+
+ def self.plugin_manifest?
+ plugin_manifest_path && File.exist?(plugin_manifest_path)
+ end
+
+ def self.autogenerated_manifest?
+ plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+ end
+
+ def self.custom_manifest?
+ plugin_manifest? && plugin_manifest.key?("plugins")
+ end
+
+ def self.plugin_manifest
+ Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
+ end
+
+ def self.plugin_manifest_path
+ Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+ end
+
+ def initialize(chef_config_dir, env = nil)
@chef_config_dir = chef_config_dir
- @forced_activate = {}
# Deprecated and un-used instance variable.
@env = env
unless env.nil?
- Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
+ Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
end
end
# Load all the sub-commands
def load_commands
+ return true if @loaded
subcommand_files.each { |subcommand| Kernel.load subcommand }
- true
+ @loaded = true
end
- # Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
- # and ~/.chef/plugins/knife/
- def site_subcommands
- user_specific_files = []
-
- if chef_config_dir
- user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
- end
-
- # finally search ~/.chef/plugins/knife/*.rb
- Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
- user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
- end
+ def force_load
+ @loaded=false
+ load_commands
+ end
- user_specific_files
+ def load_command(_command_args)
+ load_commands
end
- # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
- # If rubygems is not installed, falls back to globbing the knife directory.
- # The Hash is of the form {"relative/path" => "/absolute/path"}
- #--
- # Note: the "right" way to load the plugins is to require the relative path, i.e.,
- # require 'chef/knife/command'
- # but we're getting frustrated by bugs at every turn, and it's slow besides. So
- # subcommand loader has been modified to load the plugins by using Kernel.load
- # with the absolute path.
- def gem_and_builtin_subcommands
- if have_plugin_manifest?
- find_subcommands_via_manifest
+ def list_commands(pref_cat = nil)
+ load_commands
+ if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
+ { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
else
- # search all gems for chef/knife/*.rb
- require 'rubygems'
- find_subcommands_via_rubygems
+ Chef::Knife.subcommands_by_category
end
- rescue LoadError
- find_subcommands_via_dirglob
end
- def subcommand_files
- @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ def command_class_from(args)
+ cmd_words = positional_arguments(args)
+ load_command(cmd_words)
+ result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
+ cmd_words, '_')]
+ result || Chef::Knife.subcommands[args.first.gsub('-', '_')]
end
- # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
- # that instead of inspecting the on-system gems to find the plugins. The
- # file format is expected to look like:
- #
- # { "plugins": {
- # "knife-ec2": {
- # "paths": [
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
- # ]
- # }
- # }
- # }
- #
- # Extraneous content in this file is ignored. This intentional so that we
- # can adapt the file format for potential behavior changes to knife in
- # the future.
- def find_subcommands_via_manifest
- # Format of subcommand_files is "relative_path" (something you can
- # Kernel.require()) => full_path. The relative path isn't used
- # currently, so we just map full_path => full_path.
- subcommand_files = {}
- plugin_manifest["plugins"].each do |plugin_name, plugin_manifest|
- plugin_manifest["paths"].each do |cmd_path|
- subcommand_files[cmd_path] = cmd_path
- end
- end
- subcommand_files.merge(find_subcommands_via_dirglob)
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split('-') }.flatten!
+ find_longest_key(Chef::Knife.subcommands_by_category,
+ category_words, ' ')
end
+
+ #
+ # This is shared between the custom_manifest_loader and the gem_glob_loader
+ #
def find_subcommands_via_dirglob
# The "require paths" of the core knife subcommands bundled with chef
files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
@@ -128,95 +143,65 @@ class Chef
subcommand_files
end
- def find_subcommands_via_rubygems
- files = find_files_latest_gems 'chef/knife/*.rb'
- subcommand_files = {}
- files.each do |file|
- rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
-
- # When not installed as a gem (ChefDK/appbundler in particular), AND
- # a different version of Chef is installed via gems, `files` will
- # include some files from the 'other' Chef install. If this contains
- # a knife command that doesn't exist in this version of Chef, we will
- # get a LoadError later when we try to require it.
- next if from_different_chef_version?(file)
-
- subcommand_files[rel_path] = file
- end
-
- subcommand_files.merge(find_subcommands_via_dirglob)
- end
-
- def have_plugin_manifest?
- plugin_manifest_path && File.exist?(plugin_manifest_path)
- end
-
- def plugin_manifest
- Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
- end
-
- def plugin_manifest_path
- Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+ #
+ # Subclassses should define this themselves. Eventually, this will raise a
+ # NotImplemented error, but for now, we mimic the behavior the user was likely
+ # to get in the past.
+ #
+ def subcommand_files
+ Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated.
+Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)"
+ @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest?
+ Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files
+ else
+ Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files
+ end
end
- private
-
- def find_files_latest_gems(glob, check_load_path=true)
- files = []
-
- if check_load_path
- files = $LOAD_PATH.map { |load_path|
- Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
- }.flatten.select { |file| File.file? file.untaint }
- end
-
- gem_files = latest_gem_specs.map do |spec|
- # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
- if spec.respond_to? :matches_for_glob
- spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ #
+ # Utility function for finding an element in a hash given an array
+ # of words and a separator. We find the the longest key in the
+ # hash composed of the given words joined by the separator.
+ #
+ def find_longest_key(hash, words, sep = '_')
+ match = nil
+ until match || words.empty?
+ candidate = words.join(sep)
+ if hash.key?(candidate)
+ match = candidate
else
- check_spec_for_glob(spec, glob)
+ words.pop
end
- end.flatten
-
- files.concat gem_files
- files.uniq! if check_load_path
-
- return files
- end
-
- def latest_gem_specs
- @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
- Gem::Specification.latest_specs(true) # find prerelease gems
- else
- Gem.source_index.latest_specs(true)
end
+ match
end
- def check_spec_for_glob(spec, glob)
- dirs = if spec.require_paths.size > 1 then
- "{#{spec.require_paths.join(',')}}"
- else
- spec.require_paths.first
- end
-
- glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
-
- Dir[glob].map { |f| f.untaint }
+ #
+ # The positional arguments from the argument list provided by the
+ # users. Used to search for subcommands and categories.
+ #
+ # @return [Array<String>]
+ #
+ def positional_arguments(args)
+ args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
end
- def from_different_chef_version?(path)
- matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
- end
+ # Returns an Array of paths to knife commands located in
+ # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
+ def site_subcommands
+ user_specific_files = []
- def matches_any_chef_gem?(path)
- path =~ MATCHES_CHEF_GEM
- end
+ if chef_config_dir
+ user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
+ end
- def matches_this_chef_gem?(path)
- path =~ MATCHES_THIS_CHEF_GEM
- end
+ # finally search ~/.chef/plugins/knife/*.rb
+ Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
+ user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
+ end
+ user_specific_files
+ end
end
end
end
diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb
index 4b8953a264..ef03c176b8 100644
--- a/lib/chef/knife/node_run_list_remove.rb
+++ b/lib/chef/knife/node_run_list_remove.rb
@@ -42,7 +42,18 @@ class Chef
entries = @name_args[1].split(',').map { |e| e.strip }
end
- entries.each { |e| node.run_list.remove(e) }
+ # iterate over the list of things to remove,
+ # warning if one of them was not found
+ entries.each do |e|
+ if node.run_list.find { |rli| e == rli.to_s }
+ node.run_list.remove(e)
+ else
+ ui.warn "#{e} is not in the run list"
+ unless e =~ /^(recipe|role)\[/
+ ui.warn '(did you forget recipe[] or role[] around it?)'
+ end
+ end
+ end
node.save
diff --git a/lib/chef/knife/null.rb b/lib/chef/knife/null.rb
new file mode 100644
index 0000000000..0b5058e8ea
--- /dev/null
+++ b/lib/chef/knife/null.rb
@@ -0,0 +1,10 @@
+class Chef
+ class Knife
+ class Null < Chef::Knife
+ banner "knife null"
+
+ def run
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_create.rb b/lib/chef/knife/osc_user_create.rb
index c368296040..6c3415473f 100644
--- a/lib/chef/knife/osc_user_create.rb
+++ b/lib/chef/knife/osc_user_create.rb
@@ -27,7 +27,7 @@ class Chef
class OscUserCreate < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -69,7 +69,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.new
+ user = Chef::User.new
user.name(@user_name)
user.admin(config[:admin])
user.password config[:user_password]
@@ -79,7 +79,7 @@ class Chef
end
output = edit_data(user)
- user = Chef::OscUser.from_hash(output).create
+ user = Chef::User.from_hash(output).create
ui.info("Created #{user}")
if user.private_key
diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/osc_user_delete.rb
index d6fbd4a6a9..5cd4f10413 100644
--- a/lib/chef/knife/osc_user_delete.rb
+++ b/lib/chef/knife/osc_user_delete.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserDelete < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -43,7 +43,7 @@ class Chef
exit 1
end
- delete_object(Chef::OscUser, @user_name)
+ delete_object(Chef::User, @user_name)
end
end
diff --git a/lib/chef/knife/osc_user_edit.rb b/lib/chef/knife/osc_user_edit.rb
index 4c38674d08..526475db05 100644
--- a/lib/chef/knife/osc_user_edit.rb
+++ b/lib/chef/knife/osc_user_edit.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserEdit < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -43,10 +43,10 @@ class Chef
exit 1
end
- original_user = Chef::OscUser.load(@user_name).to_hash
+ original_user = Chef::User.load(@user_name).to_hash
edited_user = edit_data(original_user)
if original_user != edited_user
- user = Chef::OscUser.from_hash(edited_user)
+ user = Chef::User.from_hash(edited_user)
user.update
ui.msg("Saved #{user}.")
else
diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/osc_user_list.rb
index 92f049cd19..84fca31899 100644
--- a/lib/chef/knife/osc_user_list.rb
+++ b/lib/chef/knife/osc_user_list.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserList < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -40,7 +40,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::OscUser.list))
+ output(format_list_for_display(Chef::User.list))
end
end
end
diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
index a71e0aa677..163b286fe0 100644
--- a/lib/chef/knife/osc_user_reregister.rb
+++ b/lib/chef/knife/osc_user_reregister.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserReregister < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -48,7 +48,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.load(@user_name).reregister
+ user = Chef::User.load(@user_name).reregister
Chef::Log.debug("Updated user data: #{user.inspect}")
key = user.private_key
if config[:file]
diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb
index 6a41ddae88..cb3a77585a 100644
--- a/lib/chef/knife/osc_user_show.rb
+++ b/lib/chef/knife/osc_user_show.rb
@@ -30,7 +30,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -45,7 +45,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.load(@user_name)
+ user = Chef::User.load(@user_name)
output(format_for_display(user))
end
diff --git a/lib/chef/knife/rehash.rb b/lib/chef/knife/rehash.rb
new file mode 100644
index 0000000000..6f1fd91911
--- /dev/null
+++ b/lib/chef/knife/rehash.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Steven Danna <steve@chef.io>
+# Copyright:: Copyright (c) 2015 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/knife'
+require 'chef/knife/core/subcommand_loader'
+
+class Chef
+ class Knife
+ class Rehash < Chef::Knife
+ banner "knife rehash"
+
+ def run
+ if ! Chef::Knife::SubcommandLoader.autogenerated_manifest?
+ ui.msg "Using knife-rehash will speed up knife's load time by caching the location of subcommands on disk."
+ ui.msg "However, you will need to update the cache by running `knife rehash` anytime you install a new knife plugin."
+ else
+ reload_plugins
+ end
+ write_hash(generate_hash)
+ end
+
+ def reload_plugins
+ Chef::Knife::SubcommandLoader::GemGlobLoader.new(@@chef_config_dir).load_commands
+ end
+
+ def generate_hash
+ output = if Chef::Knife::SubcommandLoader.plugin_manifest?
+ Chef::Knife::SubcommandLoader.plugin_manifest
+ else
+ { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {}}
+ end
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_paths'] = Chef::Knife.subcommand_files
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_by_category'] = Chef::Knife.subcommands_by_category
+ output
+ end
+
+ def write_hash(data)
+ plugin_manifest_dir = File.expand_path('..', Chef::Knife::SubcommandLoader.plugin_manifest_path)
+ FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
+ File.open(Chef::Knife::SubcommandLoader.plugin_manifest_path, 'w') do |f|
+ f.write(Chef::JSONCompat.to_json_pretty(data))
+ ui.msg "Knife subcommands are cached in #{Chef::Knife::SubcommandLoader.plugin_manifest_path}. Delete this file to disable the caching."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
index c5fe4fc1aa..d71eacfc7e 100644
--- a/lib/chef/knife/ssl_check.rb
+++ b/lib/chef/knife/ssl_check.rb
@@ -73,11 +73,12 @@ class Chef
exit 1
end
-
def verify_peer_socket
@verify_peer_socket ||= begin
tcp_connection = TCPSocket.new(host, port)
- OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client.hostname = host
+ ssl_client
end
end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index e73f6be8b6..995573cd03 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -27,7 +27,7 @@ class Chef
attr_accessor :user_field
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -61,11 +61,11 @@ class Chef
banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
def user
- @user_field ||= Chef::User.new
+ @user_field ||= Chef::UserV1.new
end
def create_user_from_hash(hash)
- Chef::User.from_hash(hash).create
+ Chef::UserV1.from_hash(hash).create
end
def osc_11_warning
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index 803be6b90c..828cd51588 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -23,7 +23,7 @@ class Chef
class UserDelete < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -55,7 +55,7 @@ EOF
if Kernel.block_given?
object = block.call
else
- object = Chef::User.load(user_name)
+ object = Chef::UserV1.load(user_name)
object.destroy
end
@@ -77,10 +77,10 @@ EOF
# Below is modification of Chef::Knife.delete_object to detect OSC 11 server.
# When OSC 11 is deprecated, simply delete all this and go back to:
#
- # delete_object(Chef::User, @user_name)
+ # delete_object(Chef::UserV1, @user_name)
#
# Also delete our override of delete_object above
- object = Chef::User.load(@user_name)
+ object = Chef::UserV1.load(@user_name)
# OSC 11 case
if object.username.nil?
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index dd2fc02743..c3a4326ee8 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -23,7 +23,7 @@ class Chef
class UserEdit < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -56,8 +56,7 @@ EOF
exit 1
end
- original_user = Chef::User.load(@user_name).to_hash
-
+ original_user = Chef::UserV1.load(@user_name).to_hash
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
#
@@ -69,11 +68,11 @@ EOF
else # EC / CS 12 user create
edited_user = edit_data(original_user)
if original_user != edited_user
- user = Chef::User.from_hash(edited_user)
+ user = Chef::UserV1.from_hash(edited_user)
user.update
ui.msg("Saved #{user}.")
else
- ui.msg("User unchaged, not saving.")
+ ui.msg("User unchanged, not saving.")
end
end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 7ae43dadc9..6a130392b9 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -25,7 +25,7 @@ class Chef
class UserList < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -37,7 +37,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::User.list))
+ output(format_list_for_display(Chef::UserV1.list))
end
end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index eab2245025..09fd1cd2d6 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -23,7 +23,7 @@ class Chef
class UserReregister < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -61,7 +61,7 @@ EOF
exit 1
end
- user = Chef::User.load(@user_name)
+ user = Chef::UserV1.load(@user_name)
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index f5e81e9972..3a2443471a 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -25,7 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -58,7 +58,7 @@ EOF
exit 1
end
- user = Chef::User.load(@user_name)
+ user = Chef::UserV1.load(@user_name)
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 9b27778a40..bf846c2072 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -37,7 +37,11 @@ class Chef
end
end
- def self.deprecation(msg=nil, &block)
+ def self.deprecation(msg=nil, location=caller(2..2)[0], &block)
+ if msg
+ msg << " at #{Array(location).join("\n")}"
+ msg = msg.join("") if msg.respond_to?(:join)
+ end
if Chef::Config[:treat_deprecation_warnings_as_errors]
error(msg, &block)
raise Chef::Exceptions::DeprecatedFeatureError.new(msg)
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index a3eacf75cb..562af541bd 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -102,20 +102,20 @@ class Chef
def deprecated_attr_reader(name, alternative, level=:warn)
define_method(name) do
- Chef::Log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
- Chef::Log.deprecation(alternative)
- Chef::Log.deprecation("Called from:")
- caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
instance_variable_get("@#{name}")
end
end
def deprecated_attr_writer(name, alternative, level=:warn)
define_method("#{name}=") do |value|
- Chef::Log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
- Chef::Log.deprecation(alternative)
- Chef::Log.deprecation("Called from:")
- caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
instance_variable_set("@#{name}", value)
end
end
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..e3c7657b1b 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -15,9 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef/constants'
+require 'chef/property'
+require 'chef/delayed_evaluator'
+
class Chef
- class DelayedEvaluator < Proc
- end
module Mixin
module ParamsValidate
@@ -32,20 +34,55 @@ class Chef
# Would raise an exception if the value of :one above is not a kind_of? string. Valid
# map options are:
#
- # :default:: Sets the default value for this parameter.
- # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
- # The key will be inserted into the error message if the Proc does not return true:
- # "Option #{key}'s value #{value} #{message}!"
- # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
- # that the value is one of those types.
- # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
- # method names.
- # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
- # by default, options are not required.
- # :regex:: Match the value of the parameter against a regular expression.
- # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any
- # of the values.
+ # @param opts [Hash<Symbol,Object>] Validation opts.
+ # @option opts [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
+ # @option opts [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.)
+ # @option opts [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # (See #_pv_regex)
+ # @option opts [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of. (See
+ # #_pv_kind_of.)
+ # @option opts [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches). (See
+ # #_pv_callbacks.)
+ # @option opts [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to. (See
+ # #_pv_respond_to.)
+ # @option opts [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty). (See #_pv_cannot_be.)
+ # @option opts [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # (See #_pv_coerce.) (See #_pv_coerce.)
+ # @option opts [Boolean] :required `true` if this property
+ # must be present and not `nil`; `false` otherwise. This is checked
+ # after the resource is fully initialized. (See #_pv_required.)
+ # @option opts [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set. (See
+ # #_pv_name_property.)
+ # @option opts [Boolean] :name_attribute Same as `name_property`.
+ # @option opts [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties). (See #_pv_default.)
+ #
def validate(opts, map)
+ map = map.validation_options if map.is_a?(Property)
+
#--
# validate works by taking the keys in the validation map, assuming it's a hash, and
# looking for _pv_:symbol as methods. Assuming it find them, it calls the right
@@ -65,7 +102,7 @@ class Chef
true
when Hash
validation.each do |check, carg|
- check_method = "_pv_#{check.to_s}"
+ check_method = "_pv_#{check}"
if self.respond_to?(check_method, true)
self.send(check_method, opts, key, carg)
else
@@ -81,162 +118,352 @@ class Chef
DelayedEvaluator.new(&block)
end
- def set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
- end
- else
- if(arg.is_a?(DelayedEvaluator))
- val = arg
- else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
+ def set_or_return(symbol, value, validation)
+ property = SetOrReturnProperty.new(name: symbol, **validation)
+ property.call(self, value)
+ end
- # Handle the case where the "default" was a DelayedEvaluator. In
- # this case, the block yields an optional parameter of +self+,
- # which is the equivalent of "new_resource"
- if val.is_a?(DelayedEvaluator)
- val = val.call(self)
- end
- end
- self.instance_variable_set(iv_symbol, val)
+ private
+
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ end
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
end
end
- private
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+ if is_required
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+ raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
+ end
+ true
+ end
- # Return the value of a parameter, or nil if it doesn't exist.
- def _pv_opts_lookup(opts, key)
- if opts.has_key?(key.to_s)
- opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
- opts[key.to_sym]
- else
- nil
+ #
+ # List of things values must be equal to.
+ #
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
+ # match for the value to be valid.
+ #
+ # `nil` passes this validation automatically.
+ #
+ # @return [Array,nil] List of things values must be equal to, or nil if
+ # equal_to is unspecified.
+ #
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value == tb
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
end
+ end
- # Raise an exception if the parameter is not found.
- def _pv_required(opts, key, is_required=true)
- if is_required
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
- true
- else
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
- end
+ #
+ # List of things values must be instances of.
+ #
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+ # the value to be valid.
+ #
+ # `nil` automatically passes this validation.
+ #
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value.kind_of?(tb)
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
end
+ end
- def _pv_equal_to(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value == tb
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ #
+ # List of method names values must respond to.
+ #
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+ # for the value to be valid.
+ #
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
end
end
end
+ end
- # Raise an exception if the parameter is not a kind_of?(to_be)
- def _pv_kind_of(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value.kind_of?(tb)
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ #
+ # List of things that must not be true about the value.
+ #
+ # Calls `value.<thing>?` All responses must be false for the value to be
+ # valid.
+ # Values which do not respond to <thing>? are considered valid (because if
+ # a value doesn't respond to `:readable?`, then it probably isn't
+ # readable.)
+ #
+ # @example
+ # ```ruby
+ # property :x, cannot_be: [ :nil, :empty ]
+ # x [ 1, 2 ] #=> valid
+ # x 1 #=> valid
+ # x [] #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ end
end
end
end
+ end
- # Raise an exception if the parameter does not respond to a given set of methods.
- def _pv_respond_to(opts, key, method_name_list)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- Array(method_name_list).each do |method_name|
- unless value.respond_to?(method_name)
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
- end
- end
+ #
+ # The default value for a property.
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # If this is a lazy value, it will either be passed the resource as a value,
+ # or if the lazy proc does not take parameters, it will be run in the
+ # context of the instance with instance_eval.
+ #
+ # @example
+ # ```ruby
+ # property :x, default: 10
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { |r| r.x+2 }
+ # ```
+ #
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value.nil?
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+ opts[key] = default_value
+ end
+ end
+
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).flatten.each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ unless zeproc.call(value)
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get(:"@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example Property
+ # ```ruby
+ # type = Property.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ case tb
+ when Proc
+ return true if instance_exec(value, &tb)
+ when Property
+ validate(opts, { key => tb.validation_options })
+ return true
+ else
+ return true if tb === value
end
end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
+
+ # Used by #set_or_return to avoid emitting a deprecation warning for
+ # "value nil" and to keep default stickiness working exactly the same
+ # @api private
+ class SetOrReturnProperty < Chef::Property
+ def get(resource)
+ value = super
+ # All values are sticky, frozen or not
+ if !is_set?(resource)
+ set_value(resource, value)
end
+ value
end
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ def call(resource, value=NOT_PASSED)
+ # setting to nil does a get
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ get(resource)
+ else
+ super
end
end
+ end
end
end
end
-
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index 9b35bbcc33..db9a2f6d91 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -44,6 +44,52 @@ class Chef
attr_reader :_extension_modules
+ #
+ # Helpers for adding context of which resource is rendering the template (CHEF-5012)
+ #
+
+ # name of the cookbook containing the template resource, e.g.:
+ # test
+ #
+ # @return [String] cookbook name
+ attr_reader :cookbook_name
+
+ # name of the recipe containing the template resource, e.g.:
+ # default
+ #
+ # @return [String] recipe name
+ attr_reader :recipe_name
+
+ # string representation of the line in the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb:2:in `from_file'
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line_string
+
+ # path to the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb
+ #
+ # @return [String] recipe path
+ attr_reader :recipe_path
+
+ # line in the recipe containing the template reosurce, e.g.:
+ # 2
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line
+
+ # name of the template source itself, e.g.:
+ # foo.erb
+ #
+ # @return [String] template name
+ attr_reader :template_name
+
+ # path to the template source itself, e.g.:
+ # /Users/lamont/solo/cookbooks/test/templates/default/foo.erb
+ #
+ # @return [String] template path
+ attr_reader :template_path
+
def initialize(variables)
super
@_extension_modules = []
@@ -62,6 +108,7 @@ class Chef
"include a node variable if you plan to use it."
end
+
#
# Takes the name of the partial, plus a hash of options. Returns a
# string that contains the result of the evaluation of the partial.
diff --git a/lib/chef/mixin/wide_string.rb b/lib/chef/mixin/wide_string.rb
new file mode 100644
index 0000000000..0c32b76365
--- /dev/null
+++ b/lib/chef/mixin/wide_string.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Jay Mundrawala(<jdm@chef.io>)
+# Copyright:: Copyright 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module WideString
+
+ def wstring(str)
+ if str.nil? || str.encoding == Encoding::UTF_16LE
+ str
+ else
+ utf8_to_wide(str)
+ end
+ end
+
+ def utf8_to_wide(ustring)
+ # ensure it is actually UTF-8
+ # Ruby likes to mark binary data as ASCII-8BIT
+ ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
+
+ # ensure we have the double-null termination Windows Wide likes
+ ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000"
+
+ # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
+ ustring = begin
+ if ustring.respond_to?(:encode)
+ ustring.encode('UTF-16LE')
+ else
+ require 'iconv'
+ Iconv.conv("UTF-16LE", "UTF-8", ustring)
+ end
+ end
+ ustring
+ end
+
+ def wide_to_utf8(wstring)
+ # ensure it is actually UTF-16LE
+ # Ruby likes to mark binary data as ASCII-8BIT
+ wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
+
+ # encode it all as UTF-8
+ wstring = begin
+ if wstring.respond_to?(:encode)
+ wstring.encode('UTF-8')
+ else
+ require 'iconv'
+ Iconv.conv("UTF-8", "UTF-16LE", wstring)
+ end
+ end
+ # remove trailing CRLF and NULL characters
+ wstring.strip!
+ wstring
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index c5f3e1bd79..744001f8a2 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -19,19 +19,13 @@
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?
+require 'chef/win32/process' if Chef::Platform.windows?
+require 'chef/win32/system' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsArchitectureHelper
- if Chef::Platform.windows?
- include Chef::ReservedNames::Win32::API::Process
- include Chef::ReservedNames::Win32::API::Error
- end
-
def node_windows_architecture(node)
node[:kernel][:machine].to_sym
end
@@ -42,6 +36,16 @@ class Chef
is_i386_process_on_x86_64_windows?
end
+ def forced_32bit_override_required?(node, desired_architecture)
+ desired_architecture == :i386 &&
+ node_windows_architecture(node) == :x86_64 &&
+ !is_i386_process_on_x86_64_windows?
+ end
+
+ def wow64_directory
+ Chef::ReservedNames::Win32::System.get_system_wow64_directory
+ end
+
def with_os_architecture(node, architecture: nil)
node ||= begin
os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
@@ -88,49 +92,21 @@ class Chef
def is_i386_process_on_x86_64_windows?
if Chef::Platform.windows?
- is_64_bit_process_result = FFI::MemoryPointer.new(:int)
-
- # The return value of IsWow64Process is nonzero value if the API call succeeds.
- # The result data are returned in the last parameter, not the return value.
- call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
-
- # The result is nonzero if IsWow64Process's calling process, in the case here
- # this process, is running under WOW64, i.e. the result is nonzero if this
- # process is 32-bit (aka :i386).
- result = (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+ Chef::ReservedNames::Win32::Process.is_wow64_process
else
false
end
end
def disable_wow64_file_redirection( node )
- original_redirection_state = ['0'].pack('P')
-
if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_disable_wow_64_fs_redirection =
- ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to disable Wow64 file redirection"
- end
-
+ Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection
end
-
- original_redirection_state
end
def restore_wow64_file_redirection( node, original_redirection_state )
if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_revert_wow_64_fs_redirection =
- ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to revert Wow64 file redirection"
- end
+ Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state)
end
end
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index a126801a28..cd12b4254a 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
+require 'chef/mixin/wide_string'
require 'chef/platform/query_helpers'
require 'chef/win32/error' if Chef::Platform.windows?
require 'chef/win32/api/system' if Chef::Platform.windows?
@@ -26,6 +27,8 @@ require 'chef/win32/api/unicode' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsEnvHelper
+ include Chef::Mixin::WideString
+
if Chef::Platform.windows?
include Chef::ReservedNames::Win32::API::System
end
@@ -45,7 +48,7 @@ class Chef
Chef::ReservedNames::Win32::Error.raise!
end
if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string(
- Chef::ReservedNames::Win32::Unicode.utf8_to_wide('Environment')
+ utf8_to_wide('Environment')
).address, flags, 5000, nil) == 0 )
Chef::ReservedNames::Win32::Error.raise!
end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index d5078371c5..22c7d5bd8e 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -315,6 +315,7 @@ class Chef
# Consumes the combined run_list and other attributes in +attrs+
def consume_attributes(attrs)
normal_attrs_to_merge = consume_run_list(attrs)
+ normal_attrs_to_merge = consume_chef_environment(normal_attrs_to_merge)
Chef::Log.debug("Applying attributes from json file")
self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs,normal_attrs_to_merge)
self.tags # make sure they're defined
@@ -347,6 +348,24 @@ class Chef
attrs
end
+ # chef_environment when set in -j JSON will take precedence over
+ # -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to
+ # highest):
+ # config_file
+ # -j JSON
+ # -E ENVIRONMENT
+ # so that users could reuse their JSON and override the chef_environment
+ # configured within it with -E ENVIRONMENT. Because command line options are
+ # merged with Chef::Config there is currently no way to distinguish between
+ # an environment set via config from an environment set via command line.
+ def consume_chef_environment(attrs)
+ attrs = attrs ? attrs.dup : {}
+ if env = attrs.delete("chef_environment")
+ chef_environment(env)
+ end
+ attrs
+ end
+
# Clear defaults and overrides, so that any deleted attributes
# between runs are still gone.
def reset_defaults_and_overrides
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index f547018a38..751f9576f6 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -20,13 +20,6 @@ class Chef
class NodeMap
#
- # Create a new NodeMap
- #
- def initialize
- @map = {}
- end
-
- #
# Set a key/value pair on the map with a filter. The filter must be true
# when applied to the node in order to retrieve the value.
#
@@ -38,30 +31,34 @@ class Chef
#
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, &block)
- Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform
- Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
+ Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform
+ Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms
platform ||= on_platform || on_platforms
- filters = { platform: platform, platform_version: platform_version, platform_family: platform_family, os: os }
- new_matcher = { filters: filters, block: block, value: value, canonical: canonical }
- @map[key] ||= []
- # Decide where to insert the matcher; the new value is preferred over
- # anything more specific (see `priority_of`) and is preferred over older
- # values of the same specificity. (So all other things being equal,
- # newest wins.)
+ filters = {}
+ filters[:platform] = platform if platform
+ filters[:platform_version] = platform_version if platform_version
+ filters[:platform_family] = platform_family if platform_family
+ filters[:os] = os if os
+ new_matcher = { value: value, filters: filters }
+ new_matcher[:block] = block if block
+ new_matcher[:canonical] = canonical if canonical
+ new_matcher[:override] = override if override
+
+ # The map is sorted in order of preference already; we just need to find
+ # our place in it (just before the first value with the same preference level).
insert_at = nil
- @map[key].each_with_index do |matcher, index|
- if specificity(new_matcher) >= specificity(matcher)
- insert_at = index
- break
- end
+ map[key] ||= []
+ map[key].each_with_index do |matcher,index|
+ cmp = compare_matchers(key, new_matcher, matcher)
+ insert_at ||= index if cmp && cmp <= 0
end
if insert_at
- @map[key].insert(insert_at, new_matcher)
+ map[key].insert(insert_at, new_matcher)
else
- @map[key] << new_matcher
+ map[key] << new_matcher
end
- self
+ map
end
#
@@ -95,8 +92,8 @@ class Chef
#
def list(node, key, canonical: nil)
raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
- return [] unless @map.has_key?(key)
- @map[key].select do |matcher|
+ return [] unless map.has_key?(key)
+ map[key].select do |matcher|
node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
end.map { |matcher| matcher[:value] }
end
@@ -105,41 +102,18 @@ class Chef
# @return remaining
# @api private
def delete_canonical(key, value)
- remaining = @map[key]
+ remaining = map[key]
if remaining
remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
if remaining.empty?
- @map.delete(key)
+ map.delete(key)
remaining = nil
end
end
remaining
end
- private
-
- #
- # Gives a value for "how specific" the matcher is.
- # Things which specify more specific filters get a higher number
- # (platform_version > platform > platform_family > os); things
- # with a block have higher specificity than similar things without
- # a block.
- #
- def specificity(matcher)
- if matcher[:filters][:platform_version]
- specificity = 8
- elsif matcher[:filters][:platform]
- specificity = 6
- elsif matcher[:filters][:platform_family]
- specificity = 4
- elsif matcher[:filters][:os]
- specificity = 2
- else
- specificity = 0
- end
- specificity += 1 if matcher[:block]
- specificity
- end
+ protected
#
# Succeeds if:
@@ -197,5 +171,52 @@ class Chef
return true if canonical.nil?
!!canonical == !!matcher[:canonical]
end
+
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+ return cmp if cmp != 0
+ # If all things are identical, return 0
+ 0
+ end
+
+ def compare_matcher_properties(new_matcher, matcher)
+ a = yield(new_matcher)
+ b = yield(matcher)
+
+ # Check for blcacklists ('!windows'). Those always come *after* positive
+ # whitelists.
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ if a_negated != b_negated
+ return 1 if a_negated
+ return -1 if b_negated
+ end
+
+ # We treat false / true and nil / not-nil with the same comparison
+ a = nil if a == false
+ b = nil if b == false
+ cmp = a <=> b
+ # This is the case where one is non-nil, and one is nil. The one that is
+ # nil is "greater" (i.e. it should come last).
+ if cmp.nil?
+ return 1 if a.nil?
+ return -1 if b.nil?
+ end
+ cmp
+ end
+
+ def map
+ @map ||= {}
+ end
end
end
diff --git a/lib/chef/osc_user.rb b/lib/chef/osc_user.rb
deleted file mode 100644
index 52bfd11108..0000000000
--- a/lib/chef/osc_user.rb
+++ /dev/null
@@ -1,194 +0,0 @@
-#
-# Author:: Steven Danna (steve@opscode.com)
-# Copyright:: Copyright 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/mash'
-require 'chef/json_compat'
-require 'chef/search/query'
-
-# TODO
-# DEPRECATION NOTE
-# This class was previously Chef::User. It is the code to support the User object
-# corrosponding to the Open Source Chef Server 11 and only still exists to support
-# users still on OSC 11.
-#
-# Chef::User now supports Chef Server 12.
-#
-# New development should occur in Chef::User.
-# This file and corrosponding osc_user knife files
-# should be removed once client support for Open Source Chef Server 11 expires.
-class Chef
- class OscUser
-
- include Chef::Mixin::FromFile
- include Chef::Mixin::ParamsValidate
-
- def initialize
- @name = ''
- @public_key = nil
- @private_key = nil
- @password = nil
- @admin = false
- end
-
- def name(arg=nil)
- set_or_return(:name, arg,
- :regex => /^[a-z0-9\-_]+$/)
- end
-
- def admin(arg=nil)
- set_or_return(:admin,
- arg, :kind_of => [TrueClass, FalseClass])
- end
-
- def public_key(arg=nil)
- set_or_return(:public_key,
- arg, :kind_of => String)
- end
-
- def private_key(arg=nil)
- set_or_return(:private_key,
- arg, :kind_of => String)
- end
-
- def password(arg=nil)
- set_or_return(:password,
- arg, :kind_of => String)
- end
-
- def to_hash
- result = {
- "name" => @name,
- "public_key" => @public_key,
- "admin" => @admin
- }
- result["private_key"] = @private_key if @private_key
- result["password"] = @password if @password
- result
- end
-
- def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
- end
-
- def destroy
- Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}")
- end
-
- def create
- payload = {:name => self.name, :admin => self.admin, :password => self.password }
- payload[:public_key] = public_key if public_key
- new_user =Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("users", payload)
- Chef::OscUser.from_hash(self.to_hash.merge(new_user))
- end
-
- def update(new_key=false)
- payload = {:name => name, :admin => admin}
- payload[:private_key] = new_key if new_key
- payload[:password] = password if password
- updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put_rest("users/#{name}", payload)
- Chef::OscUser.from_hash(self.to_hash.merge(updated_user))
- end
-
- def save(new_key=false)
- begin
- create
- rescue Net::HTTPServerException => e
- if e.response.code == "409"
- update(new_key)
- else
- raise e
- end
- end
- end
-
- def reregister
- r = Chef::REST.new(Chef::Config[:chef_server_url])
- reregistered_self = r.put_rest("users/#{name}", { :name => name, :admin => admin, :private_key => true })
- private_key(reregistered_self["private_key"])
- self
- end
-
- def to_s
- "user[#{@name}]"
- end
-
- def inspect
- "Chef::OscUser name:'#{name}' admin:'#{admin.inspect}'" +
- "public_key:'#{public_key}' private_key:#{private_key}"
- end
-
- # Class Methods
-
- def self.from_hash(user_hash)
- user = Chef::OscUser.new
- user.name user_hash['name']
- user.private_key user_hash['private_key'] if user_hash.key?('private_key')
- user.password user_hash['password'] if user_hash.key?('password')
- user.public_key user_hash['public_key']
- user.admin user_hash['admin']
- user
- end
-
- def self.from_json(json)
- Chef::OscUser.from_hash(Chef::JSONCompat.from_json(json))
- end
-
- class << self
- alias_method :json_create, :from_json
- end
-
- def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
- users = if response.is_a?(Array)
- transform_ohc_list_response(response) # OHC/OPC
- else
- response # OSC
- end
- if inflate
- users.inject({}) do |user_map, (name, _url)|
- user_map[name] = Chef::OscUser.load(name)
- user_map
- end
- else
- users
- end
- end
-
- def self.load(name)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
- Chef::OscUser.from_hash(response)
- end
-
- # Gross. Transforms an API response in the form of:
- # [ { "user" => { "username" => USERNAME }}, ...]
- # into the form
- # { "USERNAME" => "URI" }
- def self.transform_ohc_list_response(response)
- new_response = Hash.new
- response.each do |u|
- name = u['user']['username']
- new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
- end
- new_response
- end
-
- private_class_method :transform_ohc_list_response
- end
-end
diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
new file mode 100644
index 0000000000..a9551a344b
--- /dev/null
+++ b/lib/chef/platform/handler_map.rb
@@ -0,0 +1,40 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class HandlerMap < Chef::NodeMap
+ #
+ # "provides" lines with identical filters sort by class name (ascending).
+ #
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = super
+ if cmp == 0
+ # Sort by class name (ascending) as well, if all other properties
+ # are exactly equal
+ if new_matcher[:value].is_a?(Class) && !new_matcher[:override]
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name }
+ end
+ end
+ cmp
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb
new file mode 100644
index 0000000000..0b050deb59
--- /dev/null
+++ b/lib/chef/platform/priority_map.rb
@@ -0,0 +1,41 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class PriorityMap < Chef::NodeMap
+ def priority(resource_name, priority_array, *filter)
+ set_priority_array(resource_name.to_sym, priority_array, *filter)
+ end
+
+ # @api private
+ def get_priority_array(node, key)
+ get(node, key)
+ end
+
+ # @api private
+ def set_priority_array(key, priority_array, *filter, &block)
+ priority_array = Array(priority_array)
+ set(key, priority_array, *filter, &block)
+ priority_array
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/wstring.rb b/lib/chef/platform/provider_handler_map.rb
index bb6fdf4884..4549d7994e 100644
--- a/lib/chef/mixin/wstring.rb
+++ b/lib/chef/platform/provider_handler_map.rb
@@ -1,6 +1,6 @@
#
-# Author:: Jay Mundrawala(<jdm@chef.io>)
-# Copyright:: Copyright 2015 Chef Software
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,14 @@
# limitations under the License.
#
+require 'singleton'
+require 'chef/platform/handler_map'
+
class Chef
- module Mixin
- module WideString
- def wstring(str)
- if str.nil? || str.encoding == Encoding::UTF_16LE
- str
- else
- str.to_wstring
- end
- end
+ class Platform
+ # @api private
+ class ProviderHandlerMap < Chef::Platform::HandlerMap
+ include Singleton
end
end
end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index e3a894c8ac..9b511f0237 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -176,7 +176,7 @@ class Chef
platform_provider(platform, version, resource_type) ||
resource_matching_provider(platform, version, resource_type)
- raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
+ raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
provider_klass
end
@@ -197,16 +197,16 @@ class Chef
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
- class_name = resource_type.class.to_s.split('::').last
+ class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+ convert_to_class_name(resource_type.resource_name.to_s)
- begin
- result = Chef::Provider.const_get(class_name)
+ if Chef::Provider.const_defined?(class_name)
Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
- Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to provide DSL.")
- rescue NameError
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.")
+ return Chef::Provider.const_get(class_name)
end
end
- result
+ nil
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 9d703c9178..5599c74c2d 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,29 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ProviderPriorityMap
+ # @api private
+ class ProviderPriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name)
- priority_map.get(node, resource_name.to_sym)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def list_handlers(node, resource_name)
- priority_map.list(node, resource_name.to_sym).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index b3948eac21..e64189fbd6 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -25,13 +25,10 @@ class Chef
end
def windows_server_2003?
+ # WMI startup shouldn't be performed unless we're on Windows.
return false unless windows?
require 'wmi-lite/wmi'
- # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
- # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
- # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
wmi = WmiLite::Wmi.new
host = wmi.first_of('Win32_OperatingSystem')
is_server_2003 = (host['version'] && host['version'].start_with?("5.2"))
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index b46f0e394c..b78ac38f0c 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -32,7 +32,7 @@ class Chef
cmd = if Chef::Platform.windows?
# should this do /f as well? do we then need a minimum delay to let apps quit?
- "shutdown /r /t #{reboot_info[:delay_mins]} /c \"#{reboot_info[:reason]}\""
+ "shutdown /r /t #{reboot_info[:delay_mins]*60} /c \"#{reboot_info[:reason]}\""
else
# probably Linux-only.
"shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb
new file mode 100644
index 0000000000..27a7bb1342
--- /dev/null
+++ b/lib/chef/platform/resource_handler_map.rb
@@ -0,0 +1,29 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 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 'singleton'
+require 'chef/platform/handler_map'
+
+class Chef
+ class Platform
+ # @api private
+ class ResourceHandlerMap < Chef::Platform::HandlerMap
+ include Singleton
+ end
+ end
+end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fb08debc53..5cc86fd2e7 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,34 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ResourcePriorityMap
+ # @api private
+ class ResourcePriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name, canonical: nil)
- priority_map.get(node, resource_name.to_sym, canonical: canonical)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def delete_canonical(resource_name, resource_class)
- priority_map.delete_canonical(resource_name, resource_class)
- end
-
- # @api private
- def list_handlers(*args)
- priority_map.list(*args).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
new file mode 100644
index 0000000000..09198d90f1
--- /dev/null
+++ b/lib/chef/property.rb
@@ -0,0 +1,536 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 John Keiser.
+# 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/exceptions'
+require 'chef/delayed_evaluator'
+
+class Chef
+ #
+ # Type and validation information for a property on a resource.
+ #
+ # A property named "x" manipulates the "@x" instance variable on a
+ # resource. The *presence* of the variable (`instance_variable_defined?(@x)`)
+ # tells whether the variable is defined; it may have any actual value,
+ # constrained only by validation.
+ #
+ # Properties may have validation, defaults, and coercion, and have full
+ # support for lazy values.
+ #
+ # @see Chef::Resource.property
+ # @see Chef::DelayedEvaluator
+ #
+ class Property
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
+ # the list of options.
+ #
+ # @example
+ # Property.derive(default: 'hi')
+ #
+ def self.derive(**options)
+ new(**options)
+ end
+
+ #
+ # Create a new property.
+ #
+ # @param options [Hash<Symbol,Object>] Property options, including
+ # control options here, as well as validation options (see
+ # Chef::Mixin::ParamsValidate#validate for a description of validation
+ # options).
+ # @option options [Symbol] :name The name of this property.
+ # @option options [Class] :declared_in The class this property comes from.
+ # @option options [Symbol] :instance_variable_name The instance variable
+ # tied to this property. Must include a leading `@`. Defaults to `@<name>`.
+ # `nil` means the property is opaque and not tied to a specific instance
+ # variable.
+ # @option options [Boolean] :desired_state `true` if this property is part of desired
+ # state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property is part of object
+ # identity. Defaults to `false`.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties) and cached. If not, the value will be frozen with Object#freeze
+ # to prevent users from modifying it in an instance.
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ #
+ def initialize(**options)
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+ options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property)
+ @options = options
+
+ options[:name] = options[:name].to_sym if options[:name]
+ options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
+ end
+
+ #
+ # The name of this property.
+ #
+ # @return [String]
+ #
+ def name
+ options[:name]
+ end
+
+ #
+ # The class this property was defined in.
+ #
+ # @return [Class]
+ #
+ def declared_in
+ options[:declared_in]
+ end
+
+ #
+ # The instance variable associated with this property.
+ #
+ # Defaults to `@<name>`
+ #
+ # @return [Symbol]
+ #
+ def instance_variable_name
+ if options.has_key?(:instance_variable_name)
+ options[:instance_variable_name]
+ elsif name
+ :"@#{name}"
+ end
+ end
+
+ #
+ # The raw default value for this resource.
+ #
+ # Does not coerce or validate the default. Does not evaluate lazy values.
+ #
+ # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
+ # `nil`
+ #
+ def default
+ return options[:default] if options.has_key?(:default)
+ return Chef::DelayedEvaluator.new { name } if name_property?
+ nil
+ end
+
+ #
+ # Whether this is part of the resource's natural identity or not.
+ #
+ # @return [Boolean]
+ #
+ def identity?
+ options[:identity]
+ end
+
+ #
+ # Whether this is part of desired state or not.
+ #
+ # Defaults to true.
+ #
+ # @return [Boolean]
+ #
+ def desired_state?
+ return true if !options.has_key?(:desired_state)
+ options[:desired_state]
+ end
+
+ #
+ # Whether this is name_property or not.
+ #
+ # @return [Boolean]
+ #
+ def name_property?
+ options[:name_property]
+ end
+
+ #
+ # Whether this property has a default value.
+ #
+ # @return [Boolean]
+ #
+ def has_default?
+ options.has_key?(:default) || name_property?
+ end
+
+ #
+ # Whether this property is required or not.
+ #
+ # @return [Boolean]
+ #
+ def required?
+ options[:required]
+ end
+
+ #
+ # Validation options. (See Chef::Mixin::ParamsValidate#validate.)
+ #
+ # @return [Hash<Symbol,Object>]
+ #
+ def validation_options
+ @validation_options ||= options.reject { |k,v|
+ [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
+ }
+ end
+
+ #
+ # Handle the property being called.
+ #
+ # The base implementation does the property get-or-set:
+ #
+ # ```ruby
+ # resource.myprop # get
+ # resource.myprop value # set
+ # ```
+ #
+ # Subclasses may implement this with any arguments they want, as long as
+ # the corresponding DSL calls it correctly.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ # @param value The value to set (or NOT_PASSED if it is a get).
+ #
+ # @return The current value of the property. If it is a `set`, lazy values
+ # will be returned without running, validating or coercing. If it is a
+ # `get`, the non-lazy, coerced, validated value will always be returned.
+ #
+ def call(resource, value=NOT_PASSED)
+ if value == NOT_PASSED
+ return get(resource)
+ end
+
+ # myprop nil is sometimes a get (backcompat)
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ # If you say "my_property nil" and the property explicitly accepts
+ # nil values, we consider this a get.
+ Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
+ return get(resource)
+ end
+
+ # Anything else (myprop value) is a set
+ set(resource, value)
+ end
+
+ #
+ # Get the property value from the resource, handling lazy values,
+ # defaults, and validation.
+ #
+ # - If the property's value is lazy, it is evaluated, coerced and validated.
+ # - If the property has no value, and is required, raises ValidationFailed.
+ # - If the property has no value, but has a lazy default, it is evaluated,
+ # coerced and validated. If the evaluated value is frozen, the resulting
+ # - If the property has no value, but has a default, the default value
+ # will be returned and frozen. If the default value is lazy, it will be
+ # evaluated, coerced and validated, and the result stored in the property.
+ # - If the property has no value, but is name_property, `resource.name`
+ # is retrieved, coerced, validated and stored in the property.
+ # - Otherwise, `nil` is returned.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return The value of the property.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property, or if the value is required and not set.
+ #
+ def get(resource)
+ if is_set?(resource)
+ value = get_value(resource)
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+
+ else
+ if has_default?
+ value = default
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ end
+
+ value = coerce(resource, value)
+
+ # We don't validate defaults
+
+ # If the value is mutable (non-frozen), we set it on the instance
+ # so that people can mutate it. (All constant default values are
+ # frozen.)
+ if !value.frozen? && !value.nil?
+ set_value(resource, value)
+ end
+
+ value
+
+ elsif required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is required"
+ end
+ end
+ end
+
+ #
+ # Set the value of this property in the given resource.
+ #
+ # Non-lazy values are coerced and validated before being set. Coercion
+ # and validation of lazy values is delayed until they are first retrieved.
+ #
+ # @param resource [Chef::Resource] The resource to set this property in.
+ # @param value The value to set.
+ #
+ # @return The value that was set, after coercion (if lazy, still returns
+ # the lazy value)
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def set(resource, value)
+ unless value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ set_value(resource, value)
+ end
+
+ #
+ # Find out whether this property has been set.
+ #
+ # This will be true if:
+ # - The user explicitly set the value
+ # - The property has a default, and the value was retrieved.
+ #
+ # From this point of view, it is worth looking at this as "what does the
+ # user think this value should be." In order words, if the user grabbed
+ # the value, even if it was a default, they probably based calculations on
+ # it. If they based calculations on it and the value changes, the rest of
+ # the world gets inconsistent.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return [Boolean]
+ #
+ def is_set?(resource)
+ value_is_set?(resource)
+ end
+
+ #
+ # Reset the value of this property so that is_set? will return false and the
+ # default will be returned in the future.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ def reset(resource)
+ reset_value(resource)
+ end
+
+ #
+ # Coerce an input value into canonical form for the property.
+ #
+ # After coercion, the value is suitable for storage in the resource.
+ # You must validate values after coercion, however.
+ #
+ # Does no special handling for lazy values.
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ # @param value The value to coerce.
+ #
+ # @return The coerced value.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def coerce(resource, value)
+ if options.has_key?(:coerce)
+ value = exec_in_resource(resource, options[:coerce], value)
+ end
+ value
+ end
+
+ #
+ # Validate a value.
+ #
+ # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
+ # options.
+ #
+ # @param resource [Chef::Resource] The resource we're validating against
+ # (to provide context for the validate).
+ # @param value The value to validate.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def validate(resource, value)
+ resource.validate({ name => value }, { name => validation_options })
+ end
+
+ #
+ # Derive a new Property that is just like this one, except with some added or
+ # changed options.
+ #
+ # @param options [Hash<Symbol,Object>] List of options that would be passed
+ # to #initialize.
+ #
+ # @return [Property] The new property type.
+ #
+ def derive(**modified_options)
+ Property.new(**options.merge(**modified_options))
+ end
+
+ #
+ # Emit the DSL for this property into the resource class (`declared_in`).
+ #
+ # Creates a getter and setter for the property.
+ #
+ def emit_dsl
+ # We don't create the getter/setter if it's a custom property; we will
+ # be using the existing getter/setter to manipulate it instead.
+ return if !instance_variable_name
+
+ # We prefer this form because the property name won't show up in the
+ # stack trace if you use `define_method`.
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
+ def #{name}(value=NOT_PASSED)
+ self.class.properties[#{name.inspect}].call(self, value)
+ end
+ def #{name}=(value)
+ self.class.properties[#{name.inspect}].set(self, value)
+ end
+ EOM
+ rescue SyntaxError
+ # If the name is not a valid ruby name, we use define_method.
+ resource_class.define_method(name) do |value=NOT_PASSED|
+ self.class.properties[name].call(self, value)
+ end
+ resource_class.define_method("#{name}=") do |value|
+ self.class.properties[name].set(self, value)
+ end
+ end
+
+ protected
+
+ #
+ # The options this Property will use for get/set behavior and validation.
+ #
+ # @see #initialize for a list of valid options.
+ #
+ attr_reader :options
+
+ #
+ # Find out whether this type accepts nil explicitly.
+ #
+ # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
+ # an empty type.
+ #
+ # These examples accept nil explicitly:
+ # ```ruby
+ # property :a, [ String, nil ]
+ # property :a, [ String, NilClass ]
+ # property :a, [ String, proc { |v| v.nil? } ]
+ # ```
+ #
+ # This does not (because the "is" doesn't exist or doesn't have nil):
+ #
+ # ```ruby
+ # property :x, String
+ # ```
+ #
+ # These do not, even though nil would validate fine (because they do not
+ # have "is"):
+ #
+ # ```ruby
+ # property :a
+ # property :a, equal_to: [ 1, 2, 3, nil ]
+ # property :a, kind_of: [ String, NilClass ]
+ # property :a, respond_to: [ ]
+ # property :a, callbacks: { "a" => proc { |v| v.nil? } }
+ # ```
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ #
+ # @return [Boolean] Whether this value explicitly accepts nil.
+ #
+ # @api private
+ def explicitly_accepts_nil?(resource)
+ options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)
+ end
+
+ def get_value(resource)
+ if instance_variable_name
+ resource.instance_variable_get(instance_variable_name)
+ else
+ resource.send(name)
+ end
+ end
+
+ def set_value(resource, value)
+ if instance_variable_name
+ resource.instance_variable_set(instance_variable_name, value)
+ else
+ resource.send(name, value)
+ end
+ end
+
+ def value_is_set?(resource)
+ if instance_variable_name
+ resource.instance_variable_defined?(instance_variable_name)
+ else
+ true
+ end
+ end
+
+ def reset_value(resource)
+ if instance_variable_name
+ if value_is_set?(resource)
+ resource.remove_instance_variable(instance_variable_name)
+ end
+ else
+ raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
+ end
+ end
+
+ def exec_in_resource(resource, proc, *args)
+ if resource
+ if proc.arity > args.size
+ value = proc.call(resource, *args)
+ else
+ value = resource.instance_exec(*args, &proc)
+ end
+ else
+ value = proc.call
+ end
+
+ if value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+ end
+ end
+end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index e50e374804..3138704a55 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -26,6 +26,7 @@ require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
+require 'forwardable'
class Chef
class Provider
@@ -65,6 +66,7 @@ class Chef
@recipe_name = nil
@cookbook_name = nil
+ self.class.include_resource_dsl_module(new_resource)
end
def whyrun_mode?
@@ -119,11 +121,11 @@ class Chef
check_resource_semantics!
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
- if !whyrun_mode? || whyrun_supported?
+ if whyrun_mode? && !whyrun_supported?
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+ else
load_current_resource
events.resource_current_state_loaded(@new_resource, @action, @current_resource)
- elsif whyrun_mode? && !whyrun_supported?
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
end
define_resource_requirements
@@ -136,9 +138,7 @@ class Chef
# we can't execute the action.
# in non-whyrun mode, this will still cause the action to be
# executed normally.
- if whyrun_supported? && !requirements.action_blocked?(@action)
- send("action_#{@action}")
- elsif whyrun_mode?
+ if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
events.resource_bypassed(@new_resource, @action, self)
else
send("action_#{@action}")
@@ -175,14 +175,221 @@ class Chef
converge_actions.add_action(descriptions, &block)
end
+ #
+ # Handle patchy convergence safely.
+ #
+ # - Does *not* call the block if the current_resource's properties match
+ # the properties the user specified on the resource.
+ # - Calls the block if current_resource does not exist
+ # - Calls the block if the user has specified any properties in the resource
+ # whose values are *different* from current_resource.
+ # - Does *not* call the block if why-run is enabled (just prints out text).
+ # - Prints out automatic green text saying what properties have changed.
+ #
+ # @param properties An optional list of property names (symbols). If not
+ # specified, `new_resource.class.state_properties` will be used.
+ # @param converge_block The block to do the converging in.
+ #
+ # @return [Boolean] whether the block was executed.
+ #
+ def converge_if_changed(*properties, &converge_block)
+ if !converge_block
+ raise ArgumentError, "converge_if_changed must be passed a block!"
+ end
+
+ properties = new_resource.class.state_properties.map { |p| p.name } if properties.empty?
+ properties = properties.map { |p| p.to_sym }
+ if current_resource
+ # Collect the list of modified properties
+ specified_properties = properties.select { |property| new_resource.property_is_set?(property) }
+ modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) }
+ if modified.empty?
+ Chef::Log.debug("Skipping update of #{new_resource.to_s}: has not changed any of the specified properties #{specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ")}.")
+ return false
+ end
+
+ # Print the pretty green text and run the block
+ property_size = modified.map { |p| p.size }.max
+ modified = modified.map { |p| " set #{p.to_s.ljust(property_size)} to #{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})" }
+ converge_by([ "update #{current_resource.identity}" ] + modified, &converge_block)
+
+ else
+ # The resource doesn't exist. Mark that we are *creating* this, and
+ # write down any properties we are setting.
+ property_size = properties.map { |p| p.size }.max
+ created = []
+ properties.each do |property|
+ if new_resource.property_is_set?(property)
+ created << " set #{property.to_s.ljust(property_size)} to #{new_resource.send(property).inspect}"
+ else
+ created << " set #{property.to_s.ljust(property_size)} to #{new_resource.send(property).inspect} (default value)"
+ end
+ end
+
+ converge_by([ "create #{new_resource.identity}" ] + created, &converge_block)
+ end
+ true
+ end
+
def self.provides(short_name, opts={}, &block)
- Chef.set_provider_priority_array(short_name, self, opts, &block)
+ Chef.provider_handler_map.set(short_name, self, opts, &block)
end
def self.provides?(node, resource)
Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
end
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider.
+ #
+ # If this is set to true, delegate methods are included in the provider so
+ # that you can call (for example) `attrname` and it will call
+ # `new_resource.attrname`.
+ #
+ # The actual include does not happen until the first time the Provider
+ # is instantiated (so that we don't have to worry about load order issues).
+ #
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+ # not (defaults to `false`).
+ #
+ def self.include_resource_dsl(include_resource_dsl)
+ @include_resource_dsl = include_resource_dsl
+ end
+
+ # Create the resource DSL module that forwards resource methods to new_resource
+ #
+ # @api private
+ def self.include_resource_dsl_module(resource)
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+ provider_class = self
+ @included_resource_dsl_module = Module.new do
+ extend Forwardable
+ define_singleton_method(:to_s) { "#{resource_class} forwarder module" }
+ define_singleton_method(:inspect) { to_s }
+ # Add a delegator for each explicit property that will get the *current* value
+ # of the property by default instead of the *actual* value.
+ resource.class.properties.each do |name, property|
+ class_eval(<<-EOM, __FILE__, __LINE__)
+ def #{name}(*args, &block)
+ # If no arguments were passed, we process "get" by defaulting
+ # the value to current_resource, not new_resource. This helps
+ # avoid issues where resources accidentally overwrite perfectly
+ # valid stuff with default values.
+ if args.empty? && !block
+ if !new_resource.property_is_set?(__method__) && current_resource
+ return current_resource.public_send(__method__)
+ end
+ end
+ new_resource.public_send(__method__, *args, &block)
+ end
+ EOM
+ end
+ dsl_methods =
+ resource.class.public_instance_methods +
+ resource.class.protected_instance_methods -
+ provider_class.instance_methods -
+ resource.class.properties.keys
+ def_delegators(:new_resource, *dsl_methods)
+ end
+ include @included_resource_dsl_module
+ end
+ end
+
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the Provider are added
+ # to the resource collection after the current position at the time the
+ # action is executed. Because they are added to the primary resource
+ # collection for the chef run, they can notify other resources outside
+ # the Provider, and potentially be notified by resources outside the Provider
+ # (but this is complicated by the fact that they don't exist until the
+ # provider executes). In this mode, it is impossible to correctly set the
+ # updated_by_last_action flag on the parent Provider resource, since it
+ # executes and returns before its component resources are run.
+ #
+ # With this option enabled, each action creates a temporary run_context
+ # with its own resource collection, evaluates the action's code in that
+ # context, and then converges the resources created. If any resources
+ # were updated, then this provider's new_resource will be marked updated.
+ #
+ # In this mode, resources created within the Provider cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the Provider will work. Delayed notifications are executed
+ # at the conclusion of the provider's action, *not* at the end of the
+ # main chef run.
+ #
+ # This mode of evaluation is experimental, but is believed to be a better
+ # set of tradeoffs than the append-after mode, so it will likely become
+ # the default in a future major release of Chef.
+ #
+ def self.use_inline_resources
+ extend InlineResources::ClassMethods
+ include InlineResources
+ end
+
+ # Chef::Provider::InlineResources
+ # Implementation of inline resource convergence for providers. See
+ # Provider.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ #
+ # @api private
+ module InlineResources
+
+ # Our run context is a child of the main run context; that gives us a
+ # whole new resource collection and notification set.
+ def initialize(resource, run_context)
+ super(resource, run_context.create_child)
+ end
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ #
+ # @api private
+ module ClassMethods
+ # Defines an action method on the provider, running the block to
+ # compile the resources, converging them, and then checking if any
+ # were updated (and updating new-resource if so)
+ def action(name, &block)
+ # We first try to create the method using "def method_name", which is
+ # preferred because it actually shows up in stack traces. If that
+ # fails, we try define_method.
+ begin
+ class_eval <<-EOM, __FILE__, __LINE__+1
+ def action_#{name}
+ return_value = compile_action_#{name}
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ EOM
+ rescue SyntaxError
+ define_method("action_#{name}") do
+ begin
+ return_value = send("compile_action_#{name}")
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ end
+ end
+ # We put the action in its own method so that super() works.
+ define_method("compile_action_#{name}", &block)
+ end
+ end
+
+ require 'chef/dsl/recipe'
+ include Chef::DSL::Recipe::FullDSL
+ end
+
protected
def converge_actions
@@ -200,19 +407,21 @@ class Chef
# manipulating notifies.
converge_by ("evaluate block and run any associated actions") do
- saved_run_context = @run_context
- @run_context = @run_context.dup
- @run_context.resource_collection = Chef::ResourceCollection.new
- instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- @run_context = saved_run_context
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
module DeprecatedLWRPClass
def const_missing(class_name)
if deprecated_constants[class_name.to_sym]
- Chef::Log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
+ Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
deprecated_constants[class_name.to_sym]
else
raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index b6b386e5a8..5f0134443d 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -28,6 +28,14 @@ class Chef
super(new_resource, run_context, '.bat')
end
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
+ end
+
def flags
@new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c'
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..77a0410593 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -201,7 +201,7 @@ class Chef
converge_by("execute migration command #{@new_resource.migration_command}") do
Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
- run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+ shell_out!(@new_resource.migration_command,run_options(:cwd=>release_path, :log_level => :info))
end
end
end
@@ -221,7 +221,7 @@ class Chef
else
converge_by("restart app using command #{@new_resource.restart_command}") do
Chef::Log.info("#{@new_resource} restarting app")
- run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+ shell_out!(@new_resource.restart_command,run_options(:cwd=>@new_resource.current_path))
end
end
end
@@ -373,11 +373,9 @@ class Chef
end
def gem_resource_collection_runner
- gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
- gems_run_context = run_context.dup
- gems_run_context.resource_collection = gems_collection
- Chef::Runner.new(gems_run_context)
+ child_context = run_context.create_child
+ gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
+ Chef::Runner.new(child_context)
end
def gem_packages
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 4d5423d0e8..8892d3a73d 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -64,7 +64,13 @@ class Chef
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exists?(base_dir)
- Chef::FileAccessControl.writable?(base_dir)
+ if Chef::FileAccessControl.writable?(base_dir)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
+ Chef::Util::PathHelper.writable_sip_path?(base_dir)
+ else
+ false
+ end
else
is_parent_writable.call(base_dir)
end
@@ -74,7 +80,13 @@ class Chef
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
if !whyrun_mode? || ::File.exists?(parent_directory)
- Chef::FileAccessControl.writable?(parent_directory)
+ if Chef::FileAccessControl.writable?(parent_directory)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
+ Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+ else
+ false
+ end
else
true
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 5fa84a21e9..379369ba6e 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -53,7 +53,7 @@ class Chef
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
- a.failure_message Chef::Exceptions::NoProviderAvailable,
+ a.failure_message Chef::Exceptions::ProviderNotFound,
err
a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
a.block_action!
@@ -63,7 +63,7 @@ class Chef
meta_configuration['RefreshMode'] == 'Disabled'
}
err = ["The LCM must have its RefreshMode set to Disabled. "]
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index a75e68a475..b2432132b7 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -70,7 +70,7 @@ class Chef
"Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.",
]
a.assertion { supports_dsc? }
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
a.block_action!
end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index f877ed2424..5b5c8136f1 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -109,7 +109,7 @@ class Chef
else
# Append is not set so we're resetting the membership of
# the group to the given members.
- members_to_be_added = @new_resource.members
+ members_to_be_added = @new_resource.members.dup
@current_resource.members.each do |member|
# No need to re-add a member if it's present in the new
# list of members
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 468e1ec639..7869917307 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -194,7 +194,7 @@ class Chef
private
def add_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
@@ -202,7 +202,7 @@ class Chef
end
def enable_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index b5efbb284d..a96c382a01 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -28,52 +28,10 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
@@ -122,38 +80,6 @@ class Chef
provider_class
end
- # Enables inline evaluation of resources in provider actions.
- #
- # Without this option, any resources declared inside the LWRP are added
- # to the resource collection after the current position at the time the
- # action is executed. Because they are added to the primary resource
- # collection for the chef run, they can notify other resources outside
- # the LWRP, and potentially be notified by resources outside the LWRP
- # (but this is complicated by the fact that they don't exist until the
- # provider executes). In this mode, it is impossible to correctly set the
- # updated_by_last_action flag on the parent LWRP resource, since it
- # executes and returns before its component resources are run.
- #
- # With this option enabled, each action creates a temporary run_context
- # with its own resource collection, evaluates the action's code in that
- # context, and then converges the resources created. If any resources
- # were updated, then this provider's new_resource will be marked updated.
- #
- # In this mode, resources created within the LWRP cannot interact with
- # external resources via notifies, though notifications to other
- # resources within the LWRP will work. Delayed notifications are executed
- # at the conclusion of the provider's action, *not* at the end of the
- # main chef run.
- #
- # This mode of evaluation is experimental, but is believed to be a better
- # set of tradeoffs than the append-after mode, so it will likely become
- # the default in a future major release of Chef.
- #
- def use_inline_resources
- extend InlineResources::ClassMethods
- include InlineResources
- end
-
# DSL for defining a provider's actions.
def action(name, &block)
define_method("action_#{name}") do
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 2039e9ae51..6bdfd5b867 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -42,13 +42,17 @@ class Chef
end
def action_mount
- unless current_resource.mounted
+ if current_resource.mounted
+ if mount_options_unchanged?
+ Chef::Log.debug("#{new_resource} is already mounted")
+ else
+ action_remount
+ end
+ else
converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
mount_fs
Chef::Log.info("#{new_resource} mounted")
end
- else
- Chef::Log.debug("#{new_resource} is already mounted")
end
end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 4ad7b24c15..510dfde46d 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -32,7 +32,7 @@ class Chef
@new_resource.options.clear
end
if @new_resource.fstype == "auto"
- @new_resource.fstype = nil
+ @new_resource.send(:clear_fstype)
end
end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 9d534ec414..880104bff7 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -142,7 +142,7 @@ class Chef
def action_remove
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description} package #{@current_resource.package_name}") do
+ converge_by("remove #{description}package #{@current_resource.package_name}") do
remove_package(@current_resource.package_name, @new_resource.version)
Chef::Log.info("#{@new_resource} removed")
end
@@ -491,37 +491,6 @@ class Chef
end
end
- # Set provider priority
- require 'chef/chef_class'
- require 'chef/provider/package/dpkg'
- require 'chef/provider/package/homebrew'
- require 'chef/provider/package/macports'
- require 'chef/provider/package/apt'
- require 'chef/provider/package/yum'
- require 'chef/provider/package/zypper'
- require 'chef/provider/package/portage'
- require 'chef/provider/package/pacman'
- require 'chef/provider/package/ips'
- require 'chef/provider/package/solaris'
- require 'chef/provider/package/smartos'
- require 'chef/provider/package/aix'
- require 'chef/provider/package/paludis'
-
- Chef.set_provider_priority_array :package, [ Homebrew, Macports ], os: "darwin"
-
- Chef.set_provider_priority_array :package, Apt, platform_family: "debian"
- Chef.set_provider_priority_array :package, Yum, platform_family: %w(rhel fedora)
- Chef.set_provider_priority_array :package, Zypper, platform_family: "suse"
- Chef.set_provider_priority_array :package, Portage, platform: "gentoo"
- Chef.set_provider_priority_array :package, Pacman, platform: "arch"
- Chef.set_provider_priority_array :package, Ips, platform: %w(openindiana opensolaris omnios solaris2)
- Chef.set_provider_priority_array :package, Solaris, platform: "nexentacore"
- Chef.set_provider_priority_array :package, Solaris, platform: "solaris2", platform_version: '< 5.11'
-
- Chef.set_provider_priority_array :package, SmartOS, platform: "smartos"
- Chef.set_provider_priority_array :package, Aix, platform: "aix"
- Chef.set_provider_priority_array :package, Paludis, platform: "exherbo"
-
private
def shell_out_with_timeout(*command_args)
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index b97db9d061..5165f4b4ea 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -26,6 +26,7 @@ class Chef
class Package
class Aix < Chef::Provider::Package
+ provides :package, os: "aix"
provides :bff_package, os: "aix"
include Chef::Mixin::GetSourceFromPackage
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index bd6ed283bf..e109c9966a 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -25,6 +25,7 @@ class Chef
class Package
class Apt < Chef::Provider::Package
+ provides :package, platform_family: "debian"
provides :apt_package, os: "linux"
# return [Hash] mapping of package name to Boolean value
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index a262f1ab1a..67e9b903c6 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -25,8 +25,6 @@ class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
- DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~:-]+)/
DPKG_INSTALLED = /^Status: install ok installed/
DPKG_VERSION = /^Version: (.+)$/
@@ -54,26 +52,22 @@ class Chef
@source_exists = true
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
if @new_resource.source
@source_exists = ::File.exists?(@new_resource.source)
if @source_exists
# Get information from the package if supplied
Chef::Log.debug("#{@new_resource} checking dpkg status")
-
- shell_out_with_timeout("dpkg-deb -W #{@new_resource.source}").stdout.each_line do |line|
- if pkginfo = DPKG_INFO.match(line)
- @current_resource.package_name(pkginfo[1])
- @new_resource.version(pkginfo[2])
- @candidate_version = pkginfo[2]
- end
+ status = shell_out_with_timeout("dpkg-deb -W #{@new_resource.source}")
+ pkginfo = status.stdout.split("\t")
+ unless pkginfo.empty?
+ @current_resource.package_name(pkginfo[0])
+ @candidate_version = pkginfo[1].strip
end
else
# Source provided but not valid means we can't safely do further processing
return
end
-
end
# Check to see if it is installed
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index beede1c916..e5c45f0a62 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -26,6 +26,7 @@ class Chef
class Package
class Homebrew < Chef::Provider::Package
+ provides :package, os: "darwin", override: true
provides :homebrew_package
include Chef::Mixin::HomebrewUser
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 4d7f4a3583..96c2e711d4 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -27,6 +27,7 @@ class Chef
class Package
class Ips < Chef::Provider::Package
+ provides :package, platform: %w(openindiana opensolaris omnios solaris2)
provides :ips_package, os: "solaris2"
attr_accessor :virtual
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index e945211540..c7ea71ac8c 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -3,6 +3,7 @@ class Chef
class Package
class Macports < Chef::Provider::Package
+ provides :package, os: "darwin"
provides :macports_package
def load_current_resource
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index f231101390..83fc09c8ae 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -31,6 +31,7 @@ class Chef
class Openbsd < Chef::Provider::Package
provides :package, os: "openbsd"
+ provides :openbsd_package
include Chef::Mixin::ShellOut
include Chef::Mixin::GetSourceFromPackage
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index bf03e54656..01e3a9cc01 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -25,6 +25,7 @@ class Chef
class Package
class Pacman < Chef::Provider::Package
+ provides :package, platform: "arch"
provides :pacman_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 407e0d0110..2d6302515b 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -24,6 +24,7 @@ class Chef
class Package
class Paludis < Chef::Provider::Package
+ provides :package, platform: "exherbo"
provides :paludis_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 4ba0160bb0..95782a6774 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -25,6 +25,8 @@ class Chef
class Provider
class Package
class Portage < Chef::Provider::Package
+
+ provides :package, platform: "gentoo"
provides :portage_package
PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index 21c39752d1..c5d52a8384 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -61,7 +61,7 @@ class Chef
Chef::Log.debug("#{@new_resource} checking rpm status")
shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
@current_resource.package_name($1)
@new_resource.version($2)
@candidate_version = $2
@@ -78,7 +78,7 @@ class Chef
@rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
@rpm_status.stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
Chef::Log.debug("#{@new_resource} current version is #{$2}")
@current_resource.version($2)
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index b5f7dbdd80..729f755b2a 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -394,7 +394,7 @@ class Chef
end
def is_omnibus?
- if RbConfig::CONFIG['bindir'] =~ %r!/opt/(opscode|chef)/embedded/bin!
+ if RbConfig::CONFIG['bindir'] =~ %r!/(opscode|chef|chefdk)/embedded/bin!
Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
# Omnibus installs to a static path because of linking on unix, find it.
true
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 0d5b801c96..71b8a9b9e1 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -29,6 +29,7 @@ class Chef
class SmartOS < Chef::Provider::Package
attr_accessor :is_virtual_package
+ provides :package, platform: "smartos"
provides :smartos_package, os: "solaris2", platform_family: "smartos"
def load_current_resource
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index 9b10403344..e62f37d27b 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -27,6 +27,8 @@ class Chef
include Chef::Mixin::GetSourceFromPackage
+ provides :package, platform: "nexentacore"
+ provides :package, platform: "solaris2", platform_version: '< 5.11'
provides :solaris_package, os: "solaris2"
# def initialize(*args)
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 85c2ba683c..81454380a3 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,6 @@
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ class Chef
class Package
class Yum < Chef::Provider::Package
+ provides :package, platform_family: %w(rhel fedora)
provides :yum_package, os: "linux"
class RPMUtils
@@ -650,6 +651,8 @@ class Chef
include Chef::Mixin::ShellOut
include Singleton
+ attr_accessor :yum_binary
+
def initialize
@rpmdb = RPMDb.new
@@ -780,7 +783,7 @@ class Chef
end
def python_bin
- yum_executable = which("yum")
+ yum_executable = which(yum_binary)
if yum_executable && shabang?(yum_executable)
extract_interpreter(yum_executable)
else
@@ -979,6 +982,15 @@ class Chef
super
@yum = YumCache.instance
+ @yum.yum_binary = yum_binary
+ end
+
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
end
# Extra attributes
@@ -1025,6 +1037,7 @@ class Chef
end
def yum_command(command)
+ command = "#{yum_binary} #{command}"
Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
@@ -1232,7 +1245,7 @@ class Chef
end
pkg_string = pkg_string_bits.join(' ')
Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
else
raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
"and release? (version-release, e.g. 1.84-10.fc6)"
@@ -1241,7 +1254,7 @@ class Chef
def install_package(name, version)
if @new_resource.source
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
else
install_remote_package(name, version)
end
@@ -1289,7 +1302,7 @@ class Chef
"#{n}#{yum_arch(a)}"
end.join(' ')
end
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index c2a3ac4ba8..ac42304ffb 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -29,6 +29,7 @@ class Chef
class Package
class Zypper < Chef::Provider::Package
+ provides :package, platform_family: "suse"
provides :zypper_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index ed44dee6ae..b876b6d8ee 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -30,8 +30,17 @@ class Chef
end
def action_run
- valid_syntax = validate_script_syntax!
- super if valid_syntax
+ validate_script_syntax!
+ super
+ end
+
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+ interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
end
def flags
@@ -62,30 +71,46 @@ class Chef
def validate_script_syntax!
interpreter_arguments = default_interpreter_flags.join(' ')
Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
- user_script_file.puts("{#{@new_resource.code}}")
- user_script_file.close
+ # Wrap the user's code in a PowerShell script block so that
+ # it isn't executed. However, syntactically invalid script
+ # in that block will still trigger a syntax error which is
+ # exactly what we want here -- verify the syntax without
+ # actually running the script.
+ user_code_wrapped_in_powershell_script_block = <<-EOH
+{
+ #{@new_resource.code}
+}
+EOH
+ user_script_file.puts user_code_wrapped_in_powershell_script_block
+ # A .close or explicit .flush required to ensure the file is
+ # written to the file system at this point, which is required since
+ # the intent is to execute the code just written to it.
+ user_script_file.close
validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
- # For consistency with other script resources, allow even syntax errors
- # to be suppressed if the returns attribute would have suppressed it
- # at converge.
- valid_returns = [0]
- specified_returns = @new_resource.returns.is_a?(Integer) ?
- [@new_resource.returns] :
- @new_resource.returns
- valid_returns.concat([1]) if specified_returns.include?(1)
-
- result = shell_out!(validation_command, {returns: valid_returns})
- result.exitstatus == 0
+ # Note that other script providers like bash allow syntax errors
+ # to be suppressed by setting 'returns' to a value that the
+ # interpreter would return as a status code in the syntax
+ # error case. We explicitly don't do this here -- syntax
+ # errors will not be suppressed, since doing so could make
+ # it harder for users to detect / debug invalid scripts.
+
+ # Therefore, the only return value for a syntactically valid
+ # script is 0. If an exception is raised by shellout, this
+ # means a non-zero return and thus a syntactically invalid script.
+
+ with_os_architecture(node, architecture: new_resource.architecture) do
+ shell_out!(validation_command, {returns: [0]})
+ end
end
end
def default_interpreter_flags
- # 'Bypass' is preferable since it doesn't require user input confirmation
- # for files such as PowerShell modules downloaded from the
- # Internet. However, 'Bypass' is not supported prior to
- # PowerShell 3.0, so the fallback is 'Unrestricted'
+ # Execution policy 'Bypass' is preferable since it doesn't require
+ # user input confirmation for files such as PowerShell modules
+ # downloaded from the Internet. However, 'Bypass' is not supported
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
[
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index cd62f7c56f..948fa6c63f 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -64,7 +64,7 @@ class Chef
def values_to_hash(values)
if values
- @name_hash = Hash[values.map { |val| [val[:name], val] }]
+ @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
else
@name_hash = {}
end
@@ -100,8 +100,8 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
- current_value = @name_hash[value[:name]]
+ if @name_hash.has_key?(value[:name].downcase)
+ current_value = @name_hash[value[:name].downcase]
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
converge_by("set value #{value}") do
registry.set_value(@new_resource.key, value)
@@ -122,7 +122,7 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- unless @name_hash.has_key?(value[:name])
+ unless @name_hash.has_key?(value[:name].downcase)
converge_by("create value #{value}") do
registry.set_value(@new_resource.key, value)
end
@@ -133,7 +133,7 @@ class Chef
def action_delete
if registry.key_exists?(@new_resource.key)
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
+ if @name_hash.has_key?(value[:name].downcase)
converge_by("delete value #{value}") do
registry.delete_value(@new_resource.key, value)
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index eaccce46cf..85ceb5cdae 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -67,7 +67,7 @@ class Chef
::File::FNM_DOTMATCH)
# Remove current directory and previous directory
- files.reject! do |name|
+ files = files.reject do |name|
basename = Pathname.new(name).basename().to_s
['.', '..'].include?(basename)
end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 9c523b5e66..e7bb2a76d7 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,10 @@ class Chef
include Chef::Mixin::Command
+ def supports
+ @supports ||= new_resource.supports.dup
+ end
+
def initialize(new_resource, run_context)
super
@enabled = nil
@@ -34,6 +38,12 @@ class Chef
true
end
+ def load_current_resource
+ supports[:status] = false if supports[:status].nil?
+ supports[:reload] = false if supports[:reload].nil?
+ supports[:restart] = false if supports[:restart].nil?
+ end
+
def load_new_resource_state
# If the user didn't specify a change in enabled state,
# it will be the same as the old resource
@@ -50,7 +60,7 @@ class Chef
def define_resource_requirements
requirements.assert(:reload) do |a|
- a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
+ a.assertion { supports[:reload] || @new_resource.reload_command }
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
# if a service is not declared to support reload, that won't
# typically change during the course of a run - so no whyrun
@@ -188,29 +198,11 @@ class Chef
require 'chef/provider/service/upstart'
require 'chef/provider/service/debian'
require 'chef/provider/service/invokercd'
- require 'chef/provider/service/freebsd'
- require 'chef/provider/service/openbsd'
- require 'chef/provider/service/solaris'
- require 'chef/provider/service/macosx'
-
- def self.os(os, *providers)
- Chef.set_provider_priority_array(:service, providers, os: os)
- end
- def self.platform_family(platform_family, *providers)
- Chef.set_provider_priority_array(:service, providers, platform_family: platform_family)
- end
-
- os %w(freebsd netbsd), Freebsd
- os %w(openbsd), Openbsd
- os %w(solaris2), Solaris
- os %w(darwin), Macosx
- os %w(linux), Systemd, Insserv, Redhat
-
- platform_family %w(arch), Systemd, Arch
- platform_family %w(gentoo), Systemd, Gentoo
- platform_family %w(debian), Systemd, Upstart, Insserv, Debian, Invokercd
- platform_family %w(rhel fedora suse), Systemd, Insserv, Redhat
+ Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch'
+ Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo'
+ Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian'
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse)
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 09ed4bbf01..0c95ce2c8e 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -116,7 +116,7 @@ class Chef
end
def is_resource_group?
- so = shell_out!("lssrc -g #{@new_resource.service_name}")
+ so = shell_out("lssrc -g #{@new_resource.service_name}")
if so.exitstatus == 0
Chef::Log.debug("#{@new_resource.service_name} is a group")
@is_resource_group = true
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 01505924cb..7d23e4ac77 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -22,15 +22,13 @@ class Chef
class Provider
class Service
class Debian < Chef::Provider::Service::Init
+ provides :service, platform_family: 'debian' do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
+ end
+
UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
- provides :service, platform_family: "debian"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
- end
-
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 6c78f86fe0..78ca0be235 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -99,7 +99,7 @@ class Chef
def restart_service
if new_resource.restart_command
super
- elsif new_resource.supports[:restart]
+ elsif supports[:restart]
shell_out_with_systems_locale!("#{init_command} fastrestart")
else
stop_service
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 3dab920f06..903c55af7a 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -1,7 +1,7 @@
#
# Author:: Lee Jensen (<ljensen@engineyard.com>)
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,9 +26,9 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
provides :service, platform_family: "gentoo"
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+ supports[:restart] = true if supports[:restart].nil?
- @new_resource.supports[:status] = true
- @new_resource.supports[:restart] = true
@found_script = false
super
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index 355e98a0eb..8fe5b0281f 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -72,7 +72,7 @@ class Chef
def restart_service
if @new_resource.restart_command
super
- elsif @new_resource.supports[:restart]
+ elsif supports[:restart]
shell_out_with_systems_locale!("#{default_init_command} restart")
else
stop_service
@@ -84,7 +84,7 @@ class Chef
def reload_service
if @new_resource.reload_command
super
- elsif @new_resource.supports[:reload]
+ elsif supports[:reload]
shell_out_with_systems_locale!("#{default_init_command} reload")
end
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4bc6..dd01f9ab87 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,10 +24,8 @@ class Chef
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, os: "linux"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
+ provides :service, platform_family: %w(debian rhel fedora suse) do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
end
def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0dbb..2b045e0e60 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,10 +23,8 @@ class Chef
class Service
class Invokercd < Chef::Provider::Service::Init
- provides :service, platform_family: "debian"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
+ provides :service, platform_family: 'debian', override: true do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
end
def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 7324822eff..0a8fca4262 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -42,6 +42,10 @@ class Chef
PLIST_DIRS = gather_plist_dirs
+ def this_version_or_newer?(this_version)
+ Gem::Version.new(node['platform_version']) >= Gem::Version.new(this_version)
+ end
+
def load_current_resource
@current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@@ -56,7 +60,7 @@ class Chef
@console_user = Etc.getlogin
Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
cmd = "su "
- param = !node['platform_version'].include?('10.10') ? '-l ' : ''
+ param = this_version_or_newer?('10.10') ? '' : '-l '
@base_user_cmd = cmd + param + "#{@console_user} -c"
# Default LauchAgent session should be Aqua
@session_type = 'Aqua' if @session_type.nil?
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index d509ee10ff..36c9e8141e 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -26,7 +26,7 @@ class Chef
class Service
class Openbsd < Chef::Provider::Service::Init
- provides :service, os: [ "openbsd" ]
+ provides :service, os: "openbsd"
include Chef::Mixin::ShellOut
@@ -40,11 +40,12 @@ class Chef
@rc_conf = ::File.read(RC_CONF_PATH) rescue ''
@rc_conf_local = ::File.read(RC_CONF_LOCAL_PATH) rescue ''
@init_command = ::File.exist?(rcd_script_path) ? rcd_script_path : nil
- new_resource.supports[:status] = true
new_resource.status_command("#{default_init_command} check")
end
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+
@current_resource = Chef::Resource::Service.new(new_resource.name)
current_resource.service_name(new_resource.service_name)
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 850953125e..33a9778715 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,24 +23,32 @@ class Chef
class Service
class Redhat < Chef::Provider::Service::Init
- CHKCONFIG_ON = /\d:on/
- CHKCONFIG_MISSING = /No such/
-
- provides :service, platform_family: [ "rhel", "fedora", "suse" ]
+ # @api private
+ attr_accessor :service_missing
+ # @api private
+ attr_accessor :current_run_levels
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
+ provides :service, platform_family: %w(rhel fedora suse) do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
+ CHKCONFIG_ON = /\d:on/
+ CHKCONFIG_MISSING = /No such/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
end
def initialize(new_resource, run_context)
super
- @init_command = "/sbin/service #{@new_resource.service_name}"
- @new_resource.supports[:status] = true
+ @init_command = "/sbin/service #{new_resource.service_name}"
@service_missing = false
+ @current_run_levels = []
+ end
+
+ # @api private
+ def run_levels
+ new_resource.run_levels
end
def define_resource_requirements
@@ -49,34 +57,60 @@ class Chef
requirements.assert(:all_actions) do |a|
chkconfig_file = "/sbin/chkconfig"
a.assertion { ::File.exists? chkconfig_file }
- a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+ a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} dbleoes not exist!"
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
a.assertion { !@service_missing }
- a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+ a.failure_message Chef::Exceptions::Service, "#{new_resource}: unable to locate the init.d script!"
a.whyrun "Assuming service would be disabled. The init script is not presently installed."
end
end
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+
super
if ::File.exists?("/sbin/chkconfig")
- chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
- @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0,1])
+ unless run_levels.nil? or run_levels.empty?
+ all_levels_match = true
+ chkconfig.stdout.split(/\s+/)[1..-1].each do |level|
+ index = level.split(':').first
+ status = level.split(':').last
+ if level =~ CHKCONFIG_ON
+ @current_run_levels << index.to_i
+ all_levels_match = false unless run_levels.include?(index.to_i)
+ else
+ all_levels_match = false if run_levels.include?(index.to_i)
+ end
+ end
+ current_resource.enabled(all_levels_match)
+ else
+ current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ end
@service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
end
- @current_resource
+ current_resource
+ end
+
+ # @api private
+ def levels
+ (run_levels.nil? or run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
end
def enable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} on"
+ unless run_levels.nil? or run_levels.empty?
+ disable_levels = current_run_levels - run_levels
+ shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty?
+ end
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on"
end
def disable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
end
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index ee403ee163..d295513b42 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -76,7 +76,7 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+ a.assertion { @new_resource.status_command or supports[:status] or
(!ps_cmd.nil? and !ps_cmd.empty?) }
a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
end
@@ -127,7 +127,7 @@ class Chef
nil
end
- elsif @new_resource.supports[:status]
+ elsif supports[:status]
Chef::Log.debug("#{@new_resource} supports status, running")
begin
if shell_out("#{default_init_command} status").exitstatus == 0
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 9085ffde2e..d41f6248c2 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -24,14 +24,12 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
include Chef::Mixin::Which
- provides :service, os: "linux"
+ provides :service, os: "linux" do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
+ end
attr_accessor :status_check_success
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
- end
-
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd)
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41035..c08a5f8636 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -25,14 +25,13 @@ class Chef
class Provider
class Service
class Upstart < Chef::Provider::Service::Simple
- UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
-
- provides :service, os: "linux"
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
+ provides :service, platform_family: 'debian', override: true do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
+ UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
end
@@ -107,7 +106,7 @@ class Chef
Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
begin
- if shell_out!(@new_resource.status_command) == 0
+ if shell_out!(@new_resource.status_command).exitstatus == 0
@current_resource.running true
end
rescue
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index 7fc680ec85..a231bd509e 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -39,6 +39,16 @@ class Chef
context = TemplateContext.new(@new_resource.variables)
context[:node] = @run_context.node
context[:template_finder] = template_finder
+
+ # helper variables
+ context[:cookbook_name] = @new_resource.cookbook_name unless context.keys.include?(:coookbook_name)
+ context[:recipe_name] = @new_resource.recipe_name unless context.keys.include?(:recipe_name)
+ context[:recipe_line_string] = @new_resource.source_line unless context.keys.include?(:recipe_line_string)
+ context[:recipe_path] = @new_resource.source_line_file unless context.keys.include?(:recipe_path)
+ context[:recipe_line] = @new_resource.source_line_number unless context.keys.include?(:recipe_line)
+ context[:template_name] = @new_resource.source unless context.keys.include?(:template_name)
+ context[:template_path] = template_location unless context.keys.include?(:template_path)
+
context._extend_modules(@new_resource.helper_modules)
output = context.render_template(template_location)
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index ad92a72a0a..244b11db98 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,8 +23,6 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
- provides :user
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index e600bb2837..62b49bd833 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -23,6 +23,8 @@ class Chef
class Provider
class WindowsScript < Chef::Provider::Script
+ attr_reader :is_forced_32bit
+
protected
include Chef::Mixin::WindowsArchitectureHelper
@@ -36,11 +38,7 @@ class Chef
@is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
- # if the user wants to run the script 32 bit && we are on a 64bit windows system && we are running a 64bit ruby ==> fail
- if ( target_architecture == :i386 ) && node_windows_architecture(run_context.node) == :x86_64 && !is_i386_process_on_x86_64_windows?
- raise Chef::Exceptions::Win32ArchitectureIncorrect,
- "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented"
- end
+ @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture)
end
public
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 5bfee343d1..82a24fc078 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -17,7 +17,7 @@
#
require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require 'chef/platform/priority_map'
class Chef
#
@@ -62,12 +62,47 @@ class Chef
maybe_chef_platform_lookup(resource)
end
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
def provided_by?(provider_class)
- prioritized_handlers.include?(provider_class)
+ potential_handlers.include?(provider_class)
+ end
+
+ def enabled_handlers
+ @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) }
+ end
+
+ # TODO deprecate this and allow actions to be passed as a filter to
+ # `provides` so we don't have to have two separate things.
+ # @api private
+ def supported_handlers
+ enabled_handlers.select { |handler| handler.supports?(resource, action) }
end
private
+ def potential_handlers
+ handler_map.list(node, resource.resource_name).uniq
+ end
+
+ # The list of handlers, with any in the priority_map moved to the front
+ def prioritized_handlers
+ @prioritized_handlers ||= begin
+ supported_handlers = self.supported_handlers
+ if supported_handlers.empty?
+ # if none of the providers specifically support the resource, we still need to pick one of the providers that are
+ # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
+ Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
+ supported_handlers = enabled_handlers
+ end
+
+ prioritized = priority_map.list(node, resource.resource_name).flatten(1)
+ prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers
+ prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+ prioritized
+ end
+ end
+
# if resource.provider is set, just return one of those objects
def maybe_explicit_provider(resource)
return nil unless resource.provider
@@ -78,27 +113,7 @@ class Chef
def maybe_dynamic_provider_resolution(resource, action)
Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
- # Get all the handlers in the priority bucket
- handlers = prioritized_handlers
-
- # Narrow it down to handlers that return `true` to `provides?`
- # TODO deprecate this and don't bother calling--the fact that they said
- # `provides` should be enough. But we need to do it right now because
- # some classes implement additional handling.
- enabled_handlers = prioritized_handlers.select { |handler| handler.provides?(node, resource) }
-
- # Narrow it down to handlers that return `true` to `supports?`
- # TODO deprecate this and allow actions to be passed as a filter to
- # `provides` so we don't have to have two separate things.
- supported_handlers = enabled_handlers.select { |handler| handler.supports?(resource, action) }
- if supported_handlers.empty?
- # if none of the providers specifically support the resource, we still need to pick one of the providers that are
- # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
- Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
- handler = enabled_handlers.first
- else
- handler = supported_handlers.first
- end
+ handler = prioritized_handlers.first
if handler
Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
@@ -114,13 +129,16 @@ class Chef
Chef::Platform.find_provider_for_node(node, resource)
end
- def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance
+ def priority_map
+ Chef.provider_priority_map
end
- def prioritized_handlers
- @prioritized_handlers ||=
- provider_priority_map.list_handlers(node, resource.resource_name).flatten(1).uniq
+ def handler_map
+ Chef.provider_handler_map
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner
end
module Deprecated
@@ -129,33 +147,21 @@ class Chef
@providers ||= Chef::Provider.descendants
end
- # this cut looks at if the provider can handle the resource type on the node
def enabled_handlers
- @enabled_handlers ||=
- providers.select do |klass|
- # NB: this is different from resource_resolver which must pass a resource_name
- # FIXME: deprecate this and normalize on passing resource_name here
- klass.provides?(node, resource)
- end.sort {|a,b| a.to_s <=> b.to_s }
- end
-
- # this cut looks at if the provider can handle the specific resource and action
- def supported_handlers
- @supported_handlers ||=
- enabled_handlers.select do |klass|
- klass.supports?(resource, action)
- end
- end
-
- # If there are no providers for a DSL, we search through the
- def prioritized_handlers
- @prioritized_handlers ||= super || begin
- result = providers.select { |handler| handler.provides?(node, resource) }.sort_by(:name)
- if !result.empty?
- Chef::Log.deprecation("#{resource.resource_name.to_sym} is marked as providing DSL #{method_symbol}, but provides #{resource.resource_name.to_sym.inspect} was never called!")
- Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ @enabled_handlers ||= begin
+ handlers = super
+ if handlers.empty?
+ # Look through all providers, and find ones that return true to provides.
+ # Don't bother with ones that don't override provides?, since they
+ # would have been in enabled_handlers already if that were so. (It's a
+ # perf concern otherwise.)
+ handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ end
end
- result
+ handlers
end
end
end
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2d61..262560f754 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -36,14 +36,7 @@ class Chef
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
- include Chef::DSL::DataQuery
- include Chef::DSL::PlatformIntrospection
- include Chef::DSL::IncludeRecipe
- include Chef::DSL::Recipe
- include Chef::DSL::RegistryHelper
- include Chef::DSL::RebootPending
- include Chef::DSL::Audit
- include Chef::DSL::Powershell
+ include Chef::DSL::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 7fe8a52d95..ee75dec3b9 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -1,7 +1,8 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: John Keiser (<jkeiser@chef.io)
+# Copyright:: Copyright (c) 2008-2015 Chef, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +18,7 @@
# limitations under the License.
#
+require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
@@ -27,6 +29,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
require 'chef/resource/conditional_action_not_nothing'
+require 'chef/resource/action_provider'
require 'chef/resource_collection'
require 'chef/node_map'
require 'chef/node'
@@ -58,8 +61,6 @@ class Chef
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
- NULL_ARG = Object.new
-
#
# The node the current Chef run is using.
#
@@ -103,7 +104,7 @@ class Chef
# @param run_context The context of the Chef run. Corresponds to #run_context.
#
def initialize(name, run_context=nil)
- name(name)
+ name(name) unless name.nil?
@run_context = run_context
@noop = nil
@before = nil
@@ -132,37 +133,27 @@ class Chef
end
#
- # The name of this particular resource.
- #
- # This special resource attribute is set automatically from the declaration
- # of the resource, e.g.
+ # The list of properties defined on this resource.
#
- # execute 'Vitruvius' do
- # command 'ls'
- # end
+ # Everything defined with `property` is in this list.
#
- # Will set the name to "Vitruvius".
- #
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
- #
- # This is also used for resource notifications and subscribes in the same manner.
- #
- # This will coerce any object into a string via #to_s. Arrays are a special case
- # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
- # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ # @param include_superclass [Boolean] `true` to include properties defined
+ # on superclasses; `false` or `nil` to return the list of properties
+ # directly on this class.
#
- # @param name [Object] The name to set, typically a String or Array
- # @return [String] The name of this Resource.
+ # @return [Hash<Symbol,Property>] The list of property names and types.
#
- def name(name=nil)
- if !name.nil?
- if name.is_a?(Array)
- @name = name.join(', ')
+ def self.properties(include_superclass=true)
+ @properties ||= {}
+ if include_superclass
+ if superclass.respond_to?(:properties)
+ superclass.properties.merge(@properties)
else
- @name = name.to_s
+ @properties.dup
end
+ else
+ @properties
end
- @name
end
#
@@ -171,27 +162,24 @@ class Chef
# @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
# @return [Array[Symbol]] the list of actions.
#
- attr_accessor :action
def action(arg=nil)
if arg
- if arg.is_a?(Array)
- arg = arg.map { |a| a.to_sym }
- else
- arg = arg.to_sym
- end
- Array(arg).each do |action|
+ arg = Array(arg).map(&:to_sym)
+ arg.each do |action|
validate(
{ action: action },
{ action: { kind_of: Symbol, equal_to: allowed_actions } }
)
end
- self.action = arg
+ @action = arg
else
- # Pull the action from the class if it's not set
- @action || self.class.default_action
+ @action
end
end
+ # Alias for normal assigment syntax.
+ alias_method :action=, :action
+
#
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
@@ -480,13 +468,21 @@ class Chef
#
# Get the value of the state attributes in this resource as a hash.
#
+ # Does not include properties that are not set (unless they are identity
+ # properties).
+ #
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
+ #
def state_for_resource_reporter
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = send(attr_name)
- state_attrs
+ state = {}
+ state_properties = self.class.state_properties
+ state_properties.each do |property|
+ if property.identity? || property.is_set?(self)
+ state[property.name] = send(property.name)
+ end
end
+ state
end
#
@@ -499,17 +495,22 @@ class Chef
alias_method :state, :state_for_resource_reporter
#
- # The value of the identity attribute, if declared. Falls back to #name if
- # no identity attribute is declared.
+ # The value of the identity of this resource.
+ #
+ # - If there are no identity properties on the resource, `name` is returned.
+ # - If there is exactly one identity property on the resource, it is returned.
+ # - If there are more than one, they are returned in a hash.
#
- # @return The value of the identity attribute.
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
#
def identity
- if identity_attr = self.class.identity_attr
- send(identity_attr)
- else
- name
+ result = {}
+ identity_properties = self.class.identity_properties
+ identity_properties.each do |property|
+ result[property.name] = send(property.name)
end
+ return result.values.first if identity_properties.size == 1
+ result
end
#
@@ -531,9 +532,7 @@ class Chef
#
# Equivalent to #ignore_failure.
#
- def epic_fail(arg=nil)
- ignore_failure(arg)
- end
+ alias :epic_fail :ignore_failure
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -688,66 +687,393 @@ class Chef
#
# The provider class for this resource.
#
+ # If `action :x do ... end` has been declared on this resource or its
+ # superclasses, this will return the `action_provider_class`.
+ #
# If this is not set, `provider_for_action` will dynamically determine the
# provider.
#
# @param arg [String, Symbol, Class] Sets the provider class for this resource.
# If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
# provider based on the name.
+ #
# @return The provider class for this resource.
#
+ # @see Chef::Resource.action_provider_class
+ #
def provider(arg=nil)
klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
lookup_provider_constant(arg)
else
arg
end
- set_or_return(:provider, klass, kind_of: [ Class ])
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_provider_class
end
def provider=(arg)
provider(arg)
end
- # Set or return the list of "state attributes" implemented by the Resource
- # subclass. State attributes are attributes that describe the desired state
- # of the system, such as file permissions or ownership. In general, state
- # attributes are attributes that could be populated by examining the state
- # of the system (e.g., File.stat can tell you the permissions on an
- # existing file). Contrarily, attributes that are not "state attributes"
- # usually modify the way Chef itself behaves, for example by providing
- # additional options for a package manager to use when installing a
- # package.
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`options[:is].any? { |v| v === value }`).
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ # @option options [Boolean] :desired_state `true` if this property is
+ # part of desired state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property
+ # is part of object identity. Defaults to `false`.
+ #
+ # @example Bare property
+ # property :x
+ #
+ # @example With just a type
+ # property :x, String
+ #
+ # @example With just options
+ # property :x, default: 'hi'
+ #
+ # @example With type and options
+ # property :x, String, default: 'hi'
+ #
+ def self.property(name, type=NOT_PASSED, **options)
+ name = name.to_sym
+
+ options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
+ options.merge!(name: name, declared_in: self)
+
+ if type == NOT_PASSED
+ # If a type is not passed, the property derives from the
+ # superclass property (if any)
+ if properties.has_key?(name)
+ property = properties[name].derive(**options)
+ else
+ property = property_type(**options)
+ end
+
+ # If a Property is specified, derive a new one from that.
+ elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
+ property = type.derive(**options)
+
+ # If a primitive type was passed, combine it with "is"
+ else
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ property = property_type(**options)
+ end
+
+ if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash))
+ Chef.log_deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.")
+ end
+
+ local_properties = properties(false)
+ local_properties[name] = property
+
+ property.emit_dsl
+ end
+
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. see #property for
+ # the list of options.
+ #
+ # @example
+ # property_type(default: 'hi')
+ #
+ def self.property_type(**options)
+ Property.derive(**options)
+ end
+
+ #
+ # The name of this particular resource.
+ #
+ # This special resource attribute is set automatically from the declaration
+ # of the resource, e.g.
+ #
+ # execute 'Vitruvius' do
+ # command 'ls'
+ # end
+ #
+ # Will set the name to "Vitruvius".
+ #
+ # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ #
+ # This is also used for resource notifications and subscribes in the same manner.
+ #
+ # This will coerce any object into a string via #to_s. Arrays are a special case
+ # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
+ # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ #
+ # @param name [Object] The name to set, typically a String or Array
+ # @return [String] The name of this Resource.
+ #
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false
+
+ #
+ # Whether this property has been set (or whether it has a default that has
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ # @return [Boolean] `true` if the property has been set.
+ #
+ def property_is_set?(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.is_set?(self)
+ end
+
+ #
+ # Clear this property as if it had never been set. It will thereafter return
+ # the default.
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ #
+ def reset_property(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.reset(self)
+ end
+
+ #
+ # Create a lazy value for assignment to a default value.
+ #
+ # @param block The block to run when the value is retrieved.
+ #
+ # @return [Chef::DelayedEvaluator] The lazy value
+ #
+ def self.lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
+
+ #
+ # Get or set the list of desired state properties for this resource.
+ #
+ # State properties are properties that describe the desired state
+ # of the system, such as file permissions or ownership.
+ # In general, state properties are properties that could be populated by
+ # examining the state of the system (e.g., File.stat can tell you the
+ # permissions on an existing file). Contrarily, properties that are not
+ # "state properties" usually modify the way Chef itself behaves, for example
+ # by providing additional options for a package manager to use when
+ # installing a package.
#
# This list is used by the Chef client auditing system to extract
# information from resources to describe changes made to the system.
- def self.state_attrs(*attr_names)
- @state_attrs ||= []
- @state_attrs = attr_names unless attr_names.empty?
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties are added to state_properties by default, and can be turned off
+ # with `desired_state: false`.
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Property>] All properties in desired state.
+ #
+ def self.state_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }.uniq
- # Return *all* state_attrs that this class has, including inherited ones
- if superclass.respond_to?(:state_attrs)
- superclass.state_attrs + @state_attrs
- else
- @state_attrs
+ local_properties = properties(false)
+ # Add new properties to the list.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, desired_state: true
+ elsif !property.desired_state?
+ self.property name, desired_state: true
+ end
+ end
+
+ # If state_attrs *excludes* something which is currently desired state,
+ # mark it as desired_state: false.
+ local_properties.each do |name,property|
+ if property.desired_state? && !names.include?(name)
+ self.property name, desired_state: false
+ end
+ end
end
+
+ properties.values.select { |property| property.desired_state? }
+ end
+
+ #
+ # Set or return the list of "state properties" implemented by the Resource
+ # subclass.
+ #
+ # Equivalent to calling #state_properties and getting `state_properties.keys`.
+ #
+ # @deprecated Use state_properties.keys instead. Note that when you declare
+ # properties with `property`: properties are added to state_properties by
+ # default, and can be turned off with `desired_state: false`
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Symbol>] All property names with desired state.
+ #
+ def self.state_attrs(*names)
+ state_properties(*names).map { |property| property.name }
end
- # Set or return the "identity attribute" for this resource class. This is
- # generally going to be the "name attribute" for this resource. In other
- # words, the resource type plus this attribute uniquely identify a given
- # bit of state that chef manages. For a File resource, this would be the
- # path, for a package resource, it will be the package name. This will show
- # up in chef-client's audit records as a searchable field.
- def self.identity_attr(attr_name=nil)
- @identity_attr ||= nil
- @identity_attr = attr_name if attr_name
+ #
+ # Set the identity of this resource to a particular set of properties.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # If no properties are marked as identity, "name" is considered the identity.
+ #
+ # @param names [Array<Symbol>] A list of property names to set as the identity.
+ #
+ # @return [Array<Property>] All identity properties.
+ #
+ def self.identity_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }
- # If this class doesn't have an identity attr, we'll defer to the superclass:
- if @identity_attr || !superclass.respond_to?(:identity_attr)
- @identity_attr
- else
- superclass.identity_attr
+ # Add or change properties that are not part of the identity.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, identity: true
+ elsif !property.identity?
+ self.property name, identity: true
+ end
+ end
+
+ # If identity_properties *excludes* something which is currently part of
+ # the identity, mark it as identity: false.
+ properties.each do |name,property|
+ if property.identity? && !names.include?(name)
+ self.property name, identity: false
+ end
+ end
end
+
+ result = properties.values.select { |property| property.identity? }
+ result = [ properties[:name] ] if result.empty?
+ result
+ end
+
+ #
+ # Set the identity of this resource to a particular property.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # @param name [Symbol] A list of property names to set as the identity.
+ #
+ # @return [Symbol] The identity property if there is only one; or `nil` if
+ # there are more than one.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_property(name=nil)
+ result = identity_properties(*Array(name))
+ if result.size > 1
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
+ end
+ result.first
+ end
+
+ #
+ # Set a property as the "identity attribute" for this resource.
+ #
+ # Identical to calling #identity_property.first.key.
+ #
+ # @param name [Symbol] The name of the property to set.
+ #
+ # @return [Symbol]
+ #
+ # @deprecated `identity_property` should be used instead.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_attr(name=nil)
+ property = identity_property(name)
+ return nil if !property
+ property.name
end
#
@@ -773,8 +1099,8 @@ class Chef
# have.
#
attr_accessor :allowed_actions
- def allowed_actions(value=NULL_ARG)
- if value != NULL_ARG
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
self.allowed_actions = value
end
@allowed_actions
@@ -885,7 +1211,7 @@ class Chef
# @deprecated Use resource_name instead.
#
def self.dsl_name
- Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
+ Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
if name
name = self.name.split('::')[-1]
convert_to_snake_case(name)
@@ -908,29 +1234,23 @@ class Chef
#
# @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- def self.resource_name(name=NULL_ARG)
+ def self.resource_name(name=NOT_PASSED)
# Setter
- if name != NULL_ARG
+ if name != NOT_PASSED
remove_canonical_dsl
# Set the resource_name and call provides
if name
name = name.to_sym
# If our class is not already providing this name, provide it.
- if !Chef::ResourceResolver.list(name).include?(self)
+ if !Chef::ResourceResolver.includes_handler?(name, self)
provides name, canonical: true
end
@resource_name = name
else
@resource_name = nil
end
- else
- # set resource_name automatically if it's not set
- if !instance_variable_defined?(:@resource_name) && self.name
- resource_name convert_to_snake_case(self.name.split('::')[-1])
- end
end
-
@resource_name
end
def self.resource_name=(name)
@@ -938,6 +1258,19 @@ class Chef
end
#
+ # Use the class name as the resource name.
+ #
+ # Munges the last part of the class name from camel case to snake case,
+ # and sets the resource_name to that:
+ #
+ # A::B::BlahDBlah -> blah_d_blah
+ #
+ def self.use_automatic_resource_name
+ automatic_name = convert_to_snake_case(self.name.split('::')[-1])
+ resource_name automatic_name
+ end
+
+ #
# The module where Chef should look for providers for this resource.
# The provider for `MyResource` will be looked up using
# `provider_base::MyResource`. Defaults to `Chef::Provider`.
@@ -955,7 +1288,7 @@ class Chef
#
def self.provider_base(arg=nil)
if arg
- Chef::Log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
+ Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
end
@provider_base ||= arg || Chef::Provider
end
@@ -965,7 +1298,7 @@ class Chef
#
# @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
#
- # @return [Arrau<Symbol>] The list of actions, as symbols.
+ # @return [Array<Symbol>] The list of actions, as symbols.
#
def self.allowed_actions(*actions)
@allowed_actions ||=
@@ -974,10 +1307,10 @@ class Chef
else
[ :nothing ]
end
- @allowed_actions |= actions
+ @allowed_actions |= actions.flatten
end
def self.allowed_actions=(value)
- @allowed_actions = value
+ @allowed_actions = value.uniq
end
#
@@ -986,22 +1319,17 @@ class Chef
# Setting default_action will automatially add the action to
# allowed_actions, if it isn't already there.
#
- # Defaults to :nothing.
+ # Defaults to [:nothing].
#
# @param action_name [Symbol,Array<Symbol>] The default action (or series
# of actions) to use.
#
- # @return [Symbol,Array<Symbol>] The default actions for the resource.
+ # @return [Array<Symbol>] The default actions for the resource.
#
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
- if action_name.is_a?(Array)
- @default_action = action_name.map { |arg| arg.to_sym }
- else
- @default_action = action_name.to_sym
- end
-
- self.allowed_actions |= Array(@default_action)
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
+ @default_action = Array(action_name).map(&:to_sym)
+ self.allowed_actions |= @default_action
end
if @default_action
@@ -1009,11 +1337,132 @@ class Chef
elsif superclass.respond_to?(:default_action)
superclass.default_action
else
- :nothing
+ [:nothing]
end
end
def self.default_action=(action_name)
- default_action(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def self.action(action, &recipe_block)
+ action = action.to_sym
+ new_action_provider_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if Array(default_action) == [:nothing]
+ end
+
+ #
+ # Define a method to load up this resource's properties with the current
+ # actual values.
+ #
+ # @param load_block The block to load. Will be run in the context of a newly
+ # created resource with its identity values filled in.
+ #
+ def self.load_current_value(&load_block)
+ define_method(:load_current_value!, &load_block)
+ end
+
+ #
+ # Call this in `load_current_value` to indicate that the value does not
+ # exist and that `current_resource` should therefore be `nil`.
+ #
+ # @raise Chef::Exceptions::CurrentValueDoesNotExist
+ #
+ def current_value_does_not_exist!
+ raise Chef::Exceptions::CurrentValueDoesNotExist
+ end
+
+ #
+ # Get the current actual value of this resource.
+ #
+ # This does not cache--a new value will be returned each time.
+ #
+ # @return A new copy of the resource, with values filled in from the actual
+ # current value.
+ #
+ def current_resource
+ provider = provider_for_action(Array(action).first)
+ if provider.whyrun_mode? && !provider.whyrun_supported?
+ raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
+ end
+ provider.load_current_resource
+ provider.current_resource
+ end
+
+ #
+ # The action provider class is an automatic `Provider` created to handle
+ # actions declared by `action :x do ... end`.
+ #
+ # This class will be returned by `resource.provider` if `resource.provider`
+ # is not set. `provider_for_action` will also use this instead of calling
+ # out to `Chef::ProviderResolver`.
+ #
+ # If the user has not declared actions on this class or its superclasses
+ # using `action :x do ... end`, then there is no need for this class and
+ # `action_provider_class` will be `nil`.
+ #
+ # @api private
+ #
+ def self.action_provider_class
+ @action_provider_class ||
+ # If the superclass needed one, then we need one as well.
+ if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class
+ new_action_provider_class
+ end
+ end
+
+ #
+ # Ensure the action provider class actually gets created. This is called
+ # when the user does `action :x do ... end`.
+ #
+ # @api private
+ def self.new_action_provider_class
+ return @action_provider_class if @action_provider_class
+
+ if superclass.respond_to?(:action_provider_class)
+ base_provider = superclass.action_provider_class
+ end
+ base_provider ||= Chef::Provider
+
+ resource_class = self
+ @action_provider_class = Class.new(base_provider) do
+ include ActionProvider
+ define_singleton_method(:to_s) { "#{resource_class} action provider" }
+ def self.inspect
+ to_s
+ end
+ end
+ @action_provider_class
end
#
@@ -1087,7 +1536,7 @@ class Chef
class << self
# back-compat
- # NOTE: that we do not support unregistering classes as descendents like
+ # NOTE: that we do not support unregistering classes as descendants like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
# @deprecated
@@ -1110,8 +1559,13 @@ class Chef
end
def self.inherited(child)
super
- @sorted_descendants = nil
- child.resource_name
+ @@sorted_descendants = nil
+ # set resource_name automatically if it's not set
+ if child.name && !child.resource_name
+ if child.name =~ /^Chef::Resource::(\w+)$/
+ child.resource_name(convert_to_snake_case($1))
+ end
+ end
end
@@ -1143,13 +1597,13 @@ class Chef
remove_canonical_dsl
end
- result = Chef.set_resource_priority_array(name, self, options, &block)
+ result = Chef.resource_handler_map.set(name, self, options, &block)
Chef::DSL::Resources.add_resource_dsl(name)
result
end
- def self.provides?(node, resource)
- Chef::ResourceResolver.resolve(resource, node: node).provided_by?(self)
+ def self.provides?(node, resource_name)
+ Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
end
# Helper for #notifies
@@ -1173,16 +1627,31 @@ class Chef
run_context.delayed_notifications(self)
end
+ def source_line_file
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[1]
+ else
+ nil
+ end
+ end
+
+ def source_line_number
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[2]
+ else
+ nil
+ end
+ end
+
def defined_at
# The following regexp should match these two sourceline formats:
# /some/path/to/file.rb:80:in `wombat_tears'
# C:/some/path/to/file.rb:80 in 1`wombat_tears'
# extracting the path to the source file and the line number.
- (file, line_no) = source_line.match(/(.*):(\d+):?.*$/).to_a[1,2] if source_line
if cookbook_name && recipe_name && source_line
- "#{cookbook_name}::#{recipe_name} line #{line_no}"
+ "#{cookbook_name}::#{recipe_name} line #{source_line_number}"
elsif source_line
- "#{file} line #{line_no}"
+ "#{source_line_file} line #{source_line_number}"
else
"dynamically defined"
end
@@ -1208,7 +1677,8 @@ class Chef
end
def provider_for_action(action)
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+ provider_class = Chef::ProviderResolver.new(node, self, action).resolve
+ provider = provider_class.new(self, run_context)
provider.action = action
provider
end
@@ -1306,57 +1776,11 @@ class Chef
Chef::Resource.send(:remove_const, class_name)
end
- # In order to generate deprecation warnings when you use Chef::Resource::MyLwrp,
- # we make a special subclass (identical in nearly all respects) of the
- # actual LWRP. When you say any of these, a deprecation warning will be
- # generated:
- #
- # - Chef::Resource::MyLwrp.new(...)
- # - resource.is_a?(Chef::Resource::MyLwrp)
- # - resource.kind_of?(Chef::Resource::MyLwrp)
- # - case resource
- # when Chef::Resource::MyLwrp
- # end
- #
- resource_subclass = class_eval <<-EOM, __FILE__, __LINE__+1
- class Chef::Resource::#{class_name} < resource_class
- resource_name nil # we do not actually provide anything
- def initialize(*args, &block)
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- super
- end
- def self.resource_name(*args)
- if args.empty?
- @resource_name ||= superclass.resource_name
- else
- super
- end
- end
- self
- end
- EOM
- # Make case, is_a and kind_of work with the new subclass, for backcompat.
- # Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class
- # Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass
- resource_class.class_eval do
- define_method(:is_a?) do |other|
- other.is_a?(Module) && other === self
- end
- define_method(:kind_of?) do |other|
- other.is_a?(Module) && other === self
- end
+ if !Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Resource.const_set(class_name, resource_class)
+ deprecated_constants[class_name.to_sym] = resource_class
end
- resource_subclass.class_eval do
- define_singleton_method(:===) do |other|
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- # resource_subclass is a superclass of all resource_class descendants.
- if self == resource_subclass && other.class <= resource_class
- return true
- end
- super(other)
- end
- end
- deprecated_constants[class_name.to_sym] = resource_subclass
+
end
def self.deprecated_constants
@@ -1380,7 +1804,7 @@ class Chef
def self.remove_canonical_dsl
if @resource_name
- remaining = Chef.resource_priority_map.delete_canonical(@resource_name, self)
+ remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self)
if !remaining
Chef::DSL::Resources.remove_resource_dsl(@resource_name)
end
diff --git a/lib/chef/resource/action_provider.rb b/lib/chef/resource/action_provider.rb
new file mode 100644
index 0000000000..d71b54ef4d
--- /dev/null
+++ b/lib/chef/resource/action_provider.rb
@@ -0,0 +1,69 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/exceptions'
+
+class Chef
+ class Resource
+ module ActionProvider
+ #
+ # If load_current_value! is defined on the resource, use that.
+ #
+ def load_current_resource
+ if new_resource.respond_to?(:load_current_value!)
+ # dup the resource and then reset desired-state properties.
+ current_resource = new_resource.dup
+
+ # We clear desired state in the copy, because it is supposed to be actual state.
+ # We keep identity properties and non-desired-state, which are assumed to be
+ # "control" values like `recurse: true`
+ current_resource.class.properties.each do |name,property|
+ if property.desired_state? && !property.identity? && !property.name_property?
+ property.reset(current_resource)
+ end
+ end
+
+ # Call the actual load_current_value! method. If it raises
+ # CurrentValueDoesNotExist, set current_resource to `nil`.
+ begin
+ # If the user specifies load_current_value do |desired_resource|, we
+ # pass in the desired resource as well as the current one.
+ if current_resource.method(:load_current_value!).arity > 0
+ current_resource.load_current_value!(new_resource)
+ else
+ current_resource.load_current_value!
+ end
+ rescue Chef::Exceptions::CurrentValueDoesNotExist
+ current_resource = nil
+ end
+ end
+
+ @current_resource = current_resource
+ end
+
+ def self.included(other)
+ other.extend(ClassMethods)
+ other.use_inline_resources
+ other.include_resource_dsl true
+ end
+
+ module ClassMethods
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 0c2fdfa819..7e9d21ebd2 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -50,9 +50,9 @@ class Chef
# Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled
# Action could be an array of symbols, but probably won't (think install + enable for a package)
if compile_time.nil?
- Chef::Log.deprecation "#{self} chef_gem compile_time installation is deprecated"
- Chef::Log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
- Chef::Log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
+ Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated"
+ Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
+ Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
end
if compile_time || compile_time.nil?
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index 3e5255bced..5df46fff60 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -27,6 +27,7 @@
# migration_command "rake db:migrate"
# environment "RAILS_ENV" => "production", "OTHER_ENV" => "foo"
# shallow_clone true
+# depth 1
# action :deploy # or :rollback
# restart_command "touch tmp/restart.txt"
# git_ssh_wrapper "wrap-ssh4git.sh"
@@ -74,6 +75,7 @@ class Chef
@remote = "origin"
@enable_submodules = false
@shallow_clone = false
+ @depth = nil
@scm_provider = Chef::Provider::Git
@svn_force_export = false
@additional_remotes = Hash[]
@@ -97,8 +99,12 @@ class Chef
@current_path ||= @deploy_to + "/current"
end
- def depth
- @shallow_clone ? "5" : nil
+ def depth(arg=@shallow_clone ? 5 : nil)
+ set_or_return(
+ :depth,
+ arg,
+ :kind_of => [ Integer ]
+ )
end
# note: deploy_to is your application "meta-root."
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index 2fcf183375..c3602fa60e 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -17,12 +17,14 @@
#
require 'chef/exceptions'
+require 'chef/dsl/powershell'
class Chef
class Resource
class DscScript < Chef::Resource
+ include Chef::DSL::Powershell
- provides :dsc_script, platform: "windows"
+ provides :dsc_script, os: "windows"
default_action :run
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index f1ca0f1883..9b0788fad3 100644
--- a/lib/chef/resource/file/verification.rb
+++ b/lib/chef/resource/file/verification.rb
@@ -106,7 +106,14 @@ class Chef
# We reuse Chef::GuardInterpreter in order to support
# the same set of options that the not_if/only_if blocks do
def verify_command(path, opts)
- command = @command % {:file => path}
+ # First implementation interpolated `file`; docs & RFC claim `path`
+ # is interpolated. Until `file` can be deprecated, interpolate both.
+ Chef.log_deprecation(
+ '%{file} is deprecated in verify command and will not be '\
+ 'supported in Chef 13. Please use %{path} instead.',
+ caller(2..2)[0]
+ ) if @command.include?('%{file}')
+ command = @command % {:file => path, :path => path}
interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts)
interpreter.evaluate
end
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index 8d720dd411..2bf8e1dba8 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -23,6 +23,7 @@ class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
+ provides :package, os: "solaris2"
provides :ips_package, os: "solaris2"
allowed_actions :install, :remove, :upgrade
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index c486233020..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -74,19 +74,14 @@ class Chef
resource_class
end
- # Define an attribute on this resource, including optional validation
- # parameters.
- def attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
- end
- end
+ alias :attribute :property
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
def actions(*action_names)
+ action_names = action_names.flatten
if !action_names.empty? && !@allowed_actions
- self.allowed_actions = action_names
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
allowed_actions(*action_names)
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 937839b6e1..5843016897 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,10 +16,11 @@
# limitations under the License.
#
+require 'chef/resource/package'
+
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
- provides :package, os: "darwin"
end
end
end
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 79986d127f..a5da0ba329 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -174,6 +174,14 @@ class Chef
)
end
+ private
+
+ # Used by the AIX provider to set fstype to nil.
+ # TODO use property to make nil a valid value for fstype
+ def clear_fstype
+ @fstype = nil
+ end
+
end
end
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index f91fdb37e0..9ae8813d69 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -29,17 +29,6 @@ class Chef
include Chef::Mixin::ShellOut
provides :package, os: "openbsd"
-
- def after_created
- assign_provider
- end
-
- private
-
- def assign_provider
- @provider = Chef::Provider::Package::Openbsd
- end
-
end
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 1c6da75678..5be1c34b89 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -100,8 +100,3 @@ class Chef
end
end
end
-
-require 'chef/chef_class'
-require 'chef/resource/homebrew_package'
-
-Chef.set_resource_priority_array :package, Chef::Resource::HomebrewPackage, os: "darwin"
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index 4ed0d4a4e0..d2e5c4b94c 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -93,7 +93,7 @@ class Chef
raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name,:type,:data].include?(key)
end
raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
- raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol)
+ raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
end
@unscrubbed_values = @values
elsif self.instance_variable_defined?(:@values)
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index aa59b543be..6d1b81f9cb 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
# Author:: Tyler Cloke (<tyler@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,7 +44,8 @@ class Chef
@init_command = nil
@priority = nil
@timeout = nil
- @supports = { :restart => false, :reload => false, :status => false }
+ @run_levels = nil
+ @supports = { :restart => nil, :reload => nil, :status => nil }
end
def service_name(arg=nil)
@@ -174,6 +175,13 @@ class Chef
)
end
+ def run_levels(arg=nil)
+ set_or_return(
+ :run_levels,
+ arg,
+ :kind_of => [ Array ] )
+ end
+
def supports(args={})
if args.is_a? Array
args.each { |arg| @supports[arg] = true }
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 2dc72d5c47..a98fb8b4fa 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -24,10 +24,7 @@ class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
provides :package, os: "solaris2", platform_family: "nexentacore"
- provides :package, os: "solaris2", platform_family: "solaris2" do |node|
- # on >= Solaris 11 we default to IPS packages instead
- node[:platform_version].to_f <= 5.10
- end
+ provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
end
end
end
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 4d54f6051f..50ba13ce65 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ class Chef
super
@flush_cache = { :before => false, :after => false }
@allow_downgrade = false
+ @yum_binary = nil
end
# Install a specific arch
@@ -57,6 +58,14 @@ class Chef
)
end
+ def yum_binary(arg=nil)
+ set_or_return(
+ :yum_binary,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
end
end
end
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 31b39f7e24..67cf134c62 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
class Chef
class ResourceResolver
@@ -55,7 +56,7 @@ class Chef
attr_reader :resource_name
# @api private
def resource
- Chef::Log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
+ Chef.log_deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
resource_name
end
# @api private
@@ -104,52 +105,80 @@ class Chef
#
# Whether this DSL is provided by the given resource_class.
#
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
+ #
# @api private
def provided_by?(resource_class)
- !prioritized_handlers.include?(resource_class)
+ potential_handlers.include?(resource_class)
+ end
+
+ #
+ # Whether the given handler attempts to provide the resource class at all.
+ #
+ # @api private
+ def self.includes_handler?(resource_name, resource_class)
+ handler_map.list(nil, resource_name).include?(resource_class)
end
protected
+ def self.priority_map
+ Chef.resource_priority_map
+ end
+
+ def self.handler_map
+ Chef.resource_handler_map
+ end
+
def priority_map
- Chef::Platform::ResourcePriorityMap.instance
+ Chef.resource_priority_map
+ end
+
+ def handler_map
+ Chef.resource_handler_map
+ end
+
+ # @api private
+ def potential_handlers
+ handler_map.list(node, resource_name, canonical: canonical).uniq
+ end
+
+ def enabled_handlers
+ potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) }
end
def prioritized_handlers
- @prioritized_handlers ||=
- priority_map.list_handlers(node, resource_name, canonical: canonical)
+ @prioritized_handlers ||= begin
+ enabled_handlers = self.enabled_handlers
+
+ prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1)
+ prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers
+ prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+ prioritized
+ end
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner
end
module Deprecated
# return a deterministically sorted list of Chef::Resource subclasses
- # @deprecated Now prioritized_handlers does its own work (more efficiently)
def resources
Chef::Resource.sorted_descendants
end
- # A list of all handlers
- # @deprecated Now prioritized_handlers does its own work
def enabled_handlers
- Chef::Log.deprecation("enabled_handlers is deprecated. If you are implementing a ResourceResolver, use provided_handlers. If you are not, use Chef::ResourceResolver.list(#{resource_name.inspect}, node: <node>)")
- resources.select { |klass| klass.provides?(node, resource_name) }
- end
-
- protected
-
- # A list of all handlers for the given DSL. If there are no handlers in
- # the map, we still check all descendants of Chef::Resource for backwards
- # compatibility purposes.
- def prioritized_handlers
- @prioritized_handlers ||= super ||
- resources.select do |klass|
- # Don't bother calling provides? unless it's overridden. We already
- # know prioritized_handlers
- if klass.method(:provides?).owner != Chef::Resource && klass.provides?(node, resource_name)
- Chef::Log.deprecation("Resources #{provided.join(", ")} are marked as providing DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
- Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
- true
- end
+ handlers = super
+ if handlers.empty?
+ handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
end
+ end
+ handlers
end
end
prepend Deprecated
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 44b05f0cc0..0c8d3d1a48 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,118 +25,223 @@ require 'chef/log'
require 'chef/recipe'
require 'chef/run_context/cookbook_compiler'
require 'chef/event_dispatch/events_output_stream'
+require 'forwardable'
class Chef
# == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ #
+ # Global state
+ #
- # Chef::Node object for this run
+ #
+ # The node for this run
+ #
+ # @return [Chef::Node]
+ #
attr_reader :node
- # Chef::CookbookCollection for this run
+ #
+ # The set of cookbooks involved in this run
+ #
+ # @return [Chef::CookbookCollection]
+ #
attr_reader :cookbook_collection
+ #
# Resource Definitions for this run. Populated when the files in
# +definitions/+ are evaluated (this is triggered by #load).
+ #
+ # @return [Array[Chef::ResourceDefinition]]
+ #
attr_reader :definitions
- ###
- # These need to be settable so deploy can run a resource_collection
- # independent of any cookbooks via +recipe_eval+
+ #
+ # Event dispatcher for this run.
+ #
+ # @return [Chef::EventDispatch::Dispatcher]
+ #
+ attr_reader :events
+
+ #
+ # Hash of factoids for a reboot request.
+ #
+ # @return [Hash]
+ #
+ attr_accessor :reboot_info
+
+ #
+ # Scoped state
+ #
- # The Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # The parent run context.
+ #
+ # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+ # root context.
+ #
+ attr_reader :parent_run_context
+ #
+ # The collection of resources intended to be converged (and able to be
+ # notified).
+ #
+ # @return [Chef::ResourceCollection]
+ #
+ # @see CookbookCompiler
+ #
+ attr_reader :resource_collection
+
+ #
# The list of control groups to execute during the audit phase
- attr_accessor :audits
+ #
+ attr_reader :audits
+
+ #
+ # Notification handling
+ #
+ #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
- attr_accessor :immediate_notification_collection
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :immediate_notification_collection
+ #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
- attr_accessor :delayed_notification_collection
-
- # Event dispatcher for this run.
- attr_reader :events
-
- # Hash of factoids for a reboot request.
- attr_reader :reboot_info
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :delayed_notification_collection
# Creates a new Chef::RunContext object and populates its fields. This object gets
# used by the Chef Server to generate a fully compiled recipe list for a node.
#
- # === Returns
- # object<Chef::RunContext>:: Duh. :)
+ # @param node [Chef::Node] The node to run against.
+ # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+ # involved in this run.
+ # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+ # run.
+ #
def initialize(node, cookbook_collection, events)
@node = node
@cookbook_collection = cookbook_collection
- @resource_collection = Chef::ResourceCollection.new
- @audits = {}
- @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
- @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
- @definitions = Hash.new
- @loaded_recipes = {}
- @loaded_attributes = {}
@events = events
- @reboot_info = {}
- @node.run_context = self
- @node.set_cookbook_attribute
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
@cookbook_compiler = nil
+
+ initialize_child_state
end
- # Triggers the compile phase of the chef run. Implemented by
- # Chef::RunContext::CookbookCompiler
+ #
+ # Triggers the compile phase of the chef run.
+ #
+ # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+ # @see Chef::RunContext::CookbookCompiler
+ #
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
- @cookbook_compiler.compile
+ cookbook_compiler.compile
end
- # Adds an immediate notification to the
- # +immediate_notification_collection+. The notification should be a
- # Chef::Resource::Notification or duck type.
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_child_state
+ @audits = {}
+ @resource_collection = Chef::ResourceCollection.new
+ @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+ @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
+ end
+
+ #
+ # Adds an immediate notification to the +immediate_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_immediately(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @immediate_notification_collection[nr.name] << notification
+ immediate_notification_collection[nr.name] << notification
else
- @immediate_notification_collection[nr.declared_key] << notification
+ immediate_notification_collection[nr.declared_key] << notification
end
end
- # Adds a delayed notification to the +delayed_notification_collection+. The
- # notification should be a Chef::Resource::Notification or duck type.
+ #
+ # Adds a delayed notification to the +delayed_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_delayed(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @delayed_notification_collection[nr.name] << notification
+ delayed_notification_collection[nr.name] << notification
else
- @delayed_notification_collection[nr.declared_key] << notification
+ delayed_notification_collection[nr.declared_key] << notification
end
end
+ #
+ # Get the list of immediate notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def immediate_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @immediate_notification_collection[resource.name]
+ return immediate_notification_collection[resource.name]
else
- return @immediate_notification_collection[resource.declared_key]
+ return immediate_notification_collection[resource.declared_key]
end
end
+ #
+ # Get the list of delayed (end of run) notifications sent by the given
+ # resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def delayed_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @delayed_notification_collection[resource.name]
+ return delayed_notification_collection[resource.name]
else
- return @delayed_notification_collection[resource.declared_key]
+ return delayed_notification_collection[resource.declared_key]
end
end
+ #
+ # Cookbook and recipe loading
+ #
+
+ #
# Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+ #
+ # @param recipe_names [Array[String]] The list of recipe names (e.g.
+ # 'my_cookbook' or 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @see DSL::IncludeRecipe#include_recipe
+ #
def include_recipe(*recipe_names, current_cookbook: nil)
result_recipes = Array.new
recipe_names.flatten.each do |recipe_name|
@@ -147,7 +252,21 @@ class Chef
result_recipes
end
+ #
# Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+ #
+ # TODO I am sort of confused why we have both this and include_recipe ...
+ # I don't see anything different beyond accepting and returning an
+ # array of recipes.
+ #
+ # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+ # 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @return A truthy value if the load occurred; `false` if already loaded.
+ #
+ # @see DSL::IncludeRecipe#load_recipe
+ #
def load_recipe(recipe_name, current_cookbook: nil)
Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
@@ -175,6 +294,15 @@ ERROR_MESSAGE
end
end
+ #
+ # Load the given recipe from a filename.
+ #
+ # @param recipe_file [String] The recipe filename.
+ #
+ # @return [Chef::Recipe] The loaded recipe.
+ #
+ # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+ #
def load_recipe_file(recipe_file)
if !File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
@@ -186,8 +314,19 @@ ERROR_MESSAGE
recipe
end
- # Looks up an attribute file given the +cookbook_name+ and
- # +attr_file_name+. Used by DSL::IncludeAttribute
+ #
+ # Look up an attribute filename.
+ #
+ # @param cookbook_name [String] The cookbook name of the attribute file.
+ # @param attr_file_name [String] The attribute file's name (not path).
+ #
+ # @return [String] The filename.
+ #
+ # @see DSL::IncludeAttribute#include_attribute
+ #
+ # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+ # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+ #
def resolve_attribute(cookbook_name, attr_file_name)
cookbook = cookbook_collection[cookbook_name]
raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -198,76 +337,152 @@ ERROR_MESSAGE
attribute_filename
end
- # An Array of all recipes that have been loaded. This is stored internally
- # as a Hash, so ordering is predictable.
#
- # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
- # will be given as "nginx::default"
+ # A list of all recipes that have been loaded.
+ #
+ # This is stored internally as a Hash, so ordering is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+ # the recipe "nginx" will be given as "nginx::default".
+ #
+ # @see #loaded_recipe? To determine if a particular recipe has been loaded.
#
- # To determine if a particular recipe has been loaded, use #loaded_recipe?
def loaded_recipes
- @loaded_recipes.keys
+ loaded_recipes_hash.keys
end
- # An Array of all attributes files that have been loaded. Stored internally
- # using a Hash, so order is predictable.
#
- # Attribute file names are given in fully qualified form, e.g.,
- # "nginx::default" instead of "nginx".
+ # A list of all attributes files that have been loaded.
+ #
+ # Stored internally using a Hash, so order is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of attribute file names in fully qualified
+ # form, e.g. the "nginx" will be given as "nginx::default".
+ #
def loaded_attributes
- @loaded_attributes.keys
+ loaded_attributes_hash.keys
end
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_recipe?(cookbook, recipe)
- @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end
- # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
- # names are expanded, so `loaded_recipe?("nginx")` and
- # `loaded_recipe?("nginx::default")` are valid and give identical results.
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield
+ # the same results.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_recipe?(recipe)
cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
loaded_fully_qualified_recipe?(cookbook, recipe_name)
end
+ #
+ # Mark a given recipe as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ def loaded_recipe(cookbook, recipe)
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+ end
+
+ #
+ # Find out if a given attribute file has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end
+ #
+ # Mark a given attribute file as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
def loaded_attribute(cookbook, attribute_file)
- @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+ loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end
##
# Cookbook File Introspection
+ #
+ # Find out if the cookbook has the given template.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param template_name [String] Template name.
+ #
+ # @return [Boolean] `true` if the template is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_template_for_node?
+ #
def has_template_in_cookbook?(cookbook, template_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_template_for_node?(node, template_name)
end
+ #
+ # Find out if the cookbook has the given file.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param cb_file_name [String] File name.
+ #
+ # @return [Boolean] `true` if the file is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+ #
def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
- # Delegates to CookbookCompiler#unreachable_cookbook?
- # Used to raise an error when attempting to load a recipe belonging to a
- # cookbook that is not in the dependency graph. See also: CHEF-4367
+ #
+ # Find out whether the given cookbook is in the cookbook dependency graph.
+ #
+ # @param cookbook_name [String] Cookbook name.
+ #
+ # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+ #
+ # @see Chef::CookbookCompiler#unreachable_cookbook?
def unreachable_cookbook?(cookbook_name)
- @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ cookbook_compiler.unreachable_cookbook?(cookbook_name)
end
+ #
# Open a stream object that can be printed into and will dispatch to events
#
- # == Arguments
- # options is a hash with these possible options:
- # - name: a string that identifies the stream to the user. Preferably short.
+ # @param name [String] The name of the stream.
+ # @param options [Hash] Other options for the stream.
+ #
+ # @return [EventDispatch::EventsOutputStream] The created stream.
+ #
+ # @yield If a block is passed, it will be run and the stream will be closed
+ # afterwards.
+ # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
#
- # Pass a block and the stream will be yielded to it, and close on its own
- # at the end of the block.
- def open_stream(options = {})
- stream = EventDispatch::EventsOutputStream.new(events, options)
+ def open_stream(name: nil, **options)
+ stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
if block_given?
begin
yield stream
@@ -280,31 +495,137 @@ ERROR_MESSAGE
end
# there are options for how to handle multiple calls to these functions:
- # 1. first call always wins (never change @reboot_info once set).
- # 2. last call always wins (happily change @reboot_info whenever).
+ # 1. first call always wins (never change reboot_info once set).
+ # 2. last call always wins (happily change reboot_info whenever).
# 3. raise an exception on the first conflict.
# 4. disable reboot after this run if anyone ever calls :cancel.
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+ Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
def cancel_reboot
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+ Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
def reboot_requested?
- @reboot_info.size > 0
+ reboot_info.size > 0
end
- private
+ #
+ # Create a child RunContext.
+ #
+ def create_child
+ ChildRunContext.new(self)
+ end
- def loaded_recipe(cookbook, recipe)
- @loaded_recipes["#{cookbook}::#{recipe}"] = true
+ protected
+
+ attr_reader :cookbook_compiler
+ attr_reader :loaded_attributes_hash
+ attr_reader :loaded_recipes_hash
+
+ module Deprecated
+ ###
+ # These need to be settable so deploy can run a resource_collection
+ # independent of any cookbooks via +recipe_eval+
+ def resource_collection=(value)
+ Chef.log_deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @resource_collection = value
+ end
+
+ def audits=(value)
+ Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @audits = value
+ end
+
+ def immediate_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @immediate_notification_collection = value
+ end
+
+ def delayed_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @delayed_notification_collection = value
+ end
end
+ prepend Deprecated
+
+
+ #
+ # A child run context. Delegates all root context calls to its parent.
+ #
+ # @api private
+ #
+ class ChildRunContext < RunContext
+ extend Forwardable
+ def_delegators :parent_run_context, *%w(
+ cancel_reboot
+ config
+ cookbook_collection
+ cookbook_compiler
+ definitions
+ events
+ has_cookbook_file_in_cookbook?
+ has_template_in_cookbook?
+ load
+ loaded_attribute
+ loaded_attributes
+ loaded_attributes_hash
+ loaded_fully_qualified_attribute?
+ loaded_fully_qualified_recipe?
+ loaded_recipe
+ loaded_recipe?
+ loaded_recipes
+ loaded_recipes_hash
+ node
+ open_stream
+ reboot_info
+ reboot_info=
+ reboot_requested?
+ request_reboot
+ resolve_attribute
+ unreachable_cookbook?
+ )
+
+ def initialize(parent_run_context)
+ @parent_run_context = parent_run_context
+
+ # We don't call super, because we don't bother initializing stuff we're
+ # going to delegate to the parent anyway. Just initialize things that
+ # every instance needs.
+ initialize_child_state
+ end
+ CHILD_STATE = %w(
+ audits
+ audits=
+ create_child
+ delayed_notification_collection
+ delayed_notification_collection=
+ delayed_notifications
+ immediate_notification_collection
+ immediate_notification_collection=
+ immediate_notifications
+ include_recipe
+ initialize_child_state
+ load_recipe
+ load_recipe_file
+ notifies_immediately
+ notifies_delayed
+ parent_run_context
+ resource_collection
+ resource_collection=
+ ).map { |x| x.to_sym }
+
+ # Verify that we didn't miss any methods
+ missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+ if !missing_methods.empty?
+ raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+ end
+ end
end
end
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 7cce6fa48c..2824f08f31 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -70,15 +70,16 @@ class Chef
# @return [Array] Array of strings with fully-qualified recipe names
def with_fully_qualified_names_and_version_constraints
self.map do |recipe_name|
- ret = if recipe_name.include?('::')
+ qualified_recipe = if recipe_name.include?('::')
recipe_name
else
"#{recipe_name}::default"
end
- if @versions[recipe_name]
- ret << "@#{@versions[recipe_name]}"
- end
- ret
+
+ version = @versions[recipe_name]
+ qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+ qualified_recipe
end
end
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 717deb63c3..31ebeda86f 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,85 +21,45 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
-require 'chef/mixin/api_version_request_handling'
-require 'chef/exceptions'
require 'chef/server_api'
-# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
+# TODO
+# DEPRECATION NOTE
+# This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object
+# corrosponding to the Open Source Chef Server 11 and only still exists to support
+# users still on OSC 11.
#
-# In general, Chef::User is no longer expected to support Open Source Chef 11 Server requests.
-# The object that handles those requests has been moved to the Chef::OscUser namespace.
+# Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13.
#
-# Exception: self.list is backwards compatible with OSC 11
+# New development should occur in Chef::UserV1.
+# This file and corrosponding osc_user knife files
+# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- include Chef::Mixin::ApiVersionRequestHandling
-
- SUPPORTED_API_VERSIONS = [0,1]
def initialize
- @username = nil
- @display_name = nil
- @first_name = nil
- @middle_name = nil
- @last_name = nil
- @email = nil
- @password = nil
+ @name = ''
@public_key = nil
@private_key = nil
- @create_key = nil
@password = nil
+ @admin = false
end
- def chef_root_rest_v0
- @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"})
- end
-
- def chef_root_rest_v1
- @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"})
+ def chef_rest_v0
+ @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
- def username(arg=nil)
- set_or_return(:username, arg,
+ def name(arg=nil)
+ set_or_return(:name, arg,
:regex => /^[a-z0-9\-_]+$/)
end
- def display_name(arg=nil)
- set_or_return(:display_name,
- arg, :kind_of => String)
- end
-
- def first_name(arg=nil)
- set_or_return(:first_name,
- arg, :kind_of => String)
- end
-
- def middle_name(arg=nil)
- set_or_return(:middle_name,
- arg, :kind_of => String)
- end
-
- def last_name(arg=nil)
- set_or_return(:last_name,
- arg, :kind_of => String)
- end
-
- def email(arg=nil)
- set_or_return(:email,
- arg, :kind_of => String)
- end
-
- def password(arg=nil)
- set_or_return(:password,
- arg, :kind_of => String)
- end
-
- def create_key(arg=nil)
- set_or_return(:create_key, arg,
- :kind_of => [TrueClass, FalseClass])
+ def admin(arg=nil)
+ set_or_return(:admin,
+ arg, :kind_of => [TrueClass, FalseClass])
end
def public_key(arg=nil)
@@ -119,17 +79,12 @@ class Chef
def to_hash
result = {
- "username" => @username
+ "name" => @name,
+ "public_key" => @public_key,
+ "admin" => @admin
}
- result["display_name"] = @display_name unless @display_name.nil?
- result["first_name"] = @first_name unless @first_name.nil?
- result["middle_name"] = @middle_name unless @middle_name.nil?
- result["last_name"] = @last_name unless @last_name.nil?
- result["email"] = @email unless @email.nil?
- result["password"] = @password unless @password.nil?
- result["public_key"] = @public_key unless @public_key.nil?
- result["private_key"] = @private_key unless @private_key.nil?
- result["create_key"] = @create_key unless @create_key.nil?
+ result["private_key"] = @private_key if @private_key
+ result["password"] = @password if @password
result
end
@@ -138,86 +93,21 @@ class Chef
end
def destroy
- # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
+ chef_rest_v0.delete("users/#{@name}")
end
def create
- # try v1, fail back to v0 if v1 not supported
- begin
- payload = {
- :username => @username,
- :display_name => @display_name,
- :first_name => @first_name,
- :last_name => @last_name,
- :email => @email,
- :password => @password
- }
- payload[:public_key] = @public_key unless @public_key.nil?
- payload[:create_key] = @create_key unless @create_key.nil?
- payload[:middle_name] = @middle_name unless @middle_name.nil?
- raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil?
- new_user = chef_root_rest_v1.post("users", payload)
-
- # get the private_key out of the chef_key hash if it exists
- if new_user['chef_key']
- if new_user['chef_key']['private_key']
- new_user['private_key'] = new_user['chef_key']['private_key']
- end
- new_user['public_key'] = new_user['chef_key']['public_key']
- new_user.delete('chef_key')
- end
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- payload = {
- :username => @username,
- :display_name => @display_name,
- :first_name => @first_name,
- :last_name => @last_name,
- :email => @email,
- :password => @password
- }
- payload[:middle_name] = @middle_name unless @middle_name.nil?
- payload[:public_key] = @public_key unless @public_key.nil?
- # under API V0, the server will create a key pair if public_key isn't passed
- new_user = chef_root_rest_v0.post("users", payload)
- end
-
+ payload = {:name => self.name, :admin => self.admin, :password => self.password }
+ payload[:public_key] = public_key if public_key
+ new_user = chef_rest_v0.post("users", payload)
Chef::User.from_hash(self.to_hash.merge(new_user))
end
def update(new_key=false)
- begin
- payload = {:username => username}
- payload[:display_name] = display_name unless display_name.nil?
- payload[:first_name] = first_name unless first_name.nil?
- payload[:middle_name] = middle_name unless middle_name.nil?
- payload[:last_name] = last_name unless last_name.nil?
- payload[:email] = email unless email.nil?
- payload[:password] = password unless password.nil?
-
- # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned
- payload[:public_key] = public_key unless public_key.nil?
- payload[:private_key] = new_key if new_key
-
- updated_user = chef_root_rest_v1.put("users/#{username}", payload)
- rescue Net::HTTPServerException => e
- if e.response.code == "400"
- # if a 400 is returned but the error message matches the error related to private / public key fields, try V0
- # else, raise the 400
- error = Chef::JSONCompat.from_json(e.response.body)["error"].first
- error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
- if error_match.nil?
- raise e
- end
- else # for other types of errors, test for API versioning errors right away
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- end
- updated_user = chef_root_rest_v0.put("users/#{username}", payload)
- end
+ payload = {:name => name, :admin => admin}
+ payload[:private_key] = new_key if new_key
+ payload[:password] = password if password
+ updated_user = chef_rest_v0.put("users/#{name}", payload)
Chef::User.from_hash(self.to_hash.merge(updated_user))
end
@@ -233,47 +123,30 @@ class Chef
end
end
- # Note: remove after API v0 no longer supported by client (and knife command).
def reregister
- begin
- payload = self.to_hash.merge({"private_key" => true})
- reregistered_self = chef_root_rest_v0.put("users/#{username}", payload)
- private_key(reregistered_self["private_key"])
- # only V0 supported for reregister
- rescue Net::HTTPServerException => e
- # if there was a 406 related to versioning, give error explaining that
- # only API version 0 is supported for reregister command
- if e.response.code == "406" && e.response["x-ops-server-api-version"]
- version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
- min_version = version_header["min_version"]
- max_version = version_header["max_version"]
- error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
- raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
- else
- raise e
- end
- end
+ reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true })
+ private_key(reregistered_self["private_key"])
self
end
def to_s
- "user[#{@username}]"
+ "user[#{@name}]"
+ end
+
+ def inspect
+ "Chef::User name:'#{name}' admin:'#{admin.inspect}'" +
+ "public_key:'#{public_key}' private_key:#{private_key}"
end
# Class Methods
def self.from_hash(user_hash)
user = Chef::User.new
- user.username user_hash['username']
- user.display_name user_hash['display_name'] if user_hash.key?('display_name')
- user.first_name user_hash['first_name'] if user_hash.key?('first_name')
- user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
- user.last_name user_hash['last_name'] if user_hash.key?('last_name')
- user.email user_hash['email'] if user_hash.key?('email')
- user.password user_hash['password'] if user_hash.key?('password')
- user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+ user.name user_hash['name']
user.private_key user_hash['private_key'] if user_hash.key?('private_key')
- user.create_key user_hash['create_key'] if user_hash.key?('create_key')
+ user.password user_hash['password'] if user_hash.key?('password')
+ user.public_key user_hash['public_key']
+ user.admin user_hash['admin']
user
end
@@ -286,19 +159,12 @@ class Chef
end
def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get('users')
users = if response.is_a?(Array)
- # EC 11 / CS 12 V0, V1
- # GET /organizations/<org>/users
- transform_list_response(response)
- else
- # OSC 11
- # GET /users
- # EC 11 / CS 12 V0, V1
- # GET /users
- response # OSC
- end
-
+ transform_ohc_list_response(response) # OHC/OPC
+ else
+ response # OSC
+ end
if inflate
users.inject({}) do |user_map, (name, _url)|
user_map[name] = Chef::User.load(name)
@@ -309,9 +175,8 @@ class Chef
end
end
- def self.load(username)
- # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
+ def self.load(name)
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get("users/#{name}")
Chef::User.from_hash(response)
end
@@ -319,7 +184,7 @@ class Chef
# [ { "user" => { "username" => USERNAME }}, ...]
# into the form
# { "USERNAME" => "URI" }
- def self.transform_list_response(response)
+ def self.transform_ohc_list_response(response)
new_response = Hash.new
response.each do |u|
name = u['user']['username']
@@ -328,7 +193,6 @@ class Chef
new_response
end
- private_class_method :transform_list_response
-
+ private_class_method :transform_ohc_list_response
end
end
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
new file mode 100644
index 0000000000..31cb0576a2
--- /dev/null
+++ b/lib/chef/user_v1.rb
@@ -0,0 +1,335 @@
+#
+# Author:: Steven Danna (steve@opscode.com)
+# Copyright:: Copyright 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/exceptions'
+require 'chef/server_api'
+
+# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
+#
+# In general, Chef::UserV1 is no longer expected to support Open Source Chef 11 Server requests.
+# The object that handles those requests remain in the Chef::User namespace.
+# This code will be moved to the Chef::User namespace as of Chef 13.
+#
+# Exception: self.list is backwards compatible with OSC 11
+class Chef
+ class UserV1
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
+
+ def initialize
+ @username = nil
+ @display_name = nil
+ @first_name = nil
+ @middle_name = nil
+ @last_name = nil
+ @email = nil
+ @password = nil
+ @public_key = nil
+ @private_key = nil
+ @create_key = nil
+ @password = nil
+ end
+
+ def chef_root_rest_v0
+ @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"})
+ end
+
+ def chef_root_rest_v1
+ @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"})
+ end
+
+ def username(arg=nil)
+ set_or_return(:username, arg,
+ :regex => /^[a-z0-9\-_]+$/)
+ end
+
+ def display_name(arg=nil)
+ set_or_return(:display_name,
+ arg, :kind_of => String)
+ end
+
+ def first_name(arg=nil)
+ set_or_return(:first_name,
+ arg, :kind_of => String)
+ end
+
+ def middle_name(arg=nil)
+ set_or_return(:middle_name,
+ arg, :kind_of => String)
+ end
+
+ def last_name(arg=nil)
+ set_or_return(:last_name,
+ arg, :kind_of => String)
+ end
+
+ def email(arg=nil)
+ set_or_return(:email,
+ arg, :kind_of => String)
+ end
+
+ def password(arg=nil)
+ set_or_return(:password,
+ arg, :kind_of => String)
+ end
+
+ def create_key(arg=nil)
+ set_or_return(:create_key, arg,
+ :kind_of => [TrueClass, FalseClass])
+ end
+
+ def public_key(arg=nil)
+ set_or_return(:public_key,
+ arg, :kind_of => String)
+ end
+
+ def private_key(arg=nil)
+ set_or_return(:private_key,
+ arg, :kind_of => String)
+ end
+
+ def password(arg=nil)
+ set_or_return(:password,
+ arg, :kind_of => String)
+ end
+
+ def to_hash
+ result = {
+ "username" => @username
+ }
+ result["display_name"] = @display_name unless @display_name.nil?
+ result["first_name"] = @first_name unless @first_name.nil?
+ result["middle_name"] = @middle_name unless @middle_name.nil?
+ result["last_name"] = @last_name unless @last_name.nil?
+ result["email"] = @email unless @email.nil?
+ result["password"] = @password unless @password.nil?
+ result["public_key"] = @public_key unless @public_key.nil?
+ result["private_key"] = @private_key unless @private_key.nil?
+ result["create_key"] = @create_key unless @create_key.nil?
+ result
+ end
+
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def destroy
+ # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+ Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
+ end
+
+ def create
+ # try v1, fail back to v0 if v1 not supported
+ begin
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password
+ }
+ payload[:public_key] = @public_key unless @public_key.nil?
+ payload[:create_key] = @create_key unless @create_key.nil?
+ payload[:middle_name] = @middle_name unless @middle_name.nil?
+ raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil?
+ new_user = chef_root_rest_v1.post("users", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_user['chef_key']
+ if new_user['chef_key']['private_key']
+ new_user['private_key'] = new_user['chef_key']['private_key']
+ end
+ new_user['public_key'] = new_user['chef_key']['public_key']
+ new_user.delete('chef_key')
+ end
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password
+ }
+ payload[:middle_name] = @middle_name unless @middle_name.nil?
+ payload[:public_key] = @public_key unless @public_key.nil?
+ # under API V0, the server will create a key pair if public_key isn't passed
+ new_user = chef_root_rest_v0.post("users", payload)
+ end
+
+ Chef::UserV1.from_hash(self.to_hash.merge(new_user))
+ end
+
+ def update(new_key=false)
+ begin
+ payload = {:username => username}
+ payload[:display_name] = display_name unless display_name.nil?
+ payload[:first_name] = first_name unless first_name.nil?
+ payload[:middle_name] = middle_name unless middle_name.nil?
+ payload[:last_name] = last_name unless last_name.nil?
+ payload[:email] = email unless email.nil?
+ payload[:password] = password unless password.nil?
+
+ # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned
+ payload[:public_key] = public_key unless public_key.nil?
+ payload[:private_key] = new_key if new_key
+
+ updated_user = chef_root_rest_v1.put("users/#{username}", payload)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "400"
+ # if a 400 is returned but the error message matches the error related to private / public key fields, try V0
+ # else, raise the 400
+ error = Chef::JSONCompat.from_json(e.response.body)["error"].first
+ error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
+ if error_match.nil?
+ raise e
+ end
+ else # for other types of errors, test for API versioning errors right away
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ end
+ updated_user = chef_root_rest_v0.put("users/#{username}", payload)
+ end
+ Chef::UserV1.from_hash(self.to_hash.merge(updated_user))
+ end
+
+ def save(new_key=false)
+ begin
+ create
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ update(new_key)
+ else
+ raise e
+ end
+ end
+ end
+
+ # Note: remove after API v0 no longer supported by client (and knife command).
+ def reregister
+ begin
+ payload = self.to_hash.merge({"private_key" => true})
+ reregistered_self = chef_root_rest_v0.put("users/#{username}", payload)
+ private_key(reregistered_self["private_key"])
+ # only V0 supported for reregister
+ rescue Net::HTTPServerException => e
+ # if there was a 406 related to versioning, give error explaining that
+ # only API version 0 is supported for reregister command
+ if e.response.code == "406" && e.response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+ min_version = version_header["min_version"]
+ max_version = version_header["max_version"]
+ error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+ raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+ else
+ raise e
+ end
+ end
+ self
+ end
+
+ def to_s
+ "user[#{@username}]"
+ end
+
+ # Class Methods
+
+ def self.from_hash(user_hash)
+ user = Chef::UserV1.new
+ user.username user_hash['username']
+ user.display_name user_hash['display_name'] if user_hash.key?('display_name')
+ user.first_name user_hash['first_name'] if user_hash.key?('first_name')
+ user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
+ user.last_name user_hash['last_name'] if user_hash.key?('last_name')
+ user.email user_hash['email'] if user_hash.key?('email')
+ user.password user_hash['password'] if user_hash.key?('password')
+ user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+ user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+ user.create_key user_hash['create_key'] if user_hash.key?('create_key')
+ user
+ end
+
+ def self.from_json(json)
+ Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json))
+ end
+
+ class << self
+ alias_method :json_create, :from_json
+ end
+
+ def self.list(inflate=false)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
+ users = if response.is_a?(Array)
+ # EC 11 / CS 12 V0, V1
+ # GET /organizations/<org>/users
+ transform_list_response(response)
+ else
+ # OSC 11
+ # GET /users
+ # EC 11 / CS 12 V0, V1
+ # GET /users
+ response # OSC
+ end
+
+ if inflate
+ users.inject({}) do |user_map, (name, _url)|
+ user_map[name] = Chef::UserV1.load(name)
+ user_map
+ end
+ else
+ users
+ end
+ end
+
+ def self.load(username)
+ # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
+ Chef::UserV1.from_hash(response)
+ end
+
+ # Gross. Transforms an API response in the form of:
+ # [ { "user" => { "username" => USERNAME }}, ...]
+ # into the form
+ # { "USERNAME" => "URI" }
+ def self.transform_list_response(response)
+ new_response = Hash.new
+ response.each do |u|
+ name = u['user']['username']
+ new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
+ end
+ new_response
+ end
+
+ private_class_method :transform_list_response
+
+ end
+end
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
index 01f8c27b6c..3f4558a77c 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -29,6 +29,10 @@ class Chef::Util::Powershell
"New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
end
+ def to_s
+ to_psobject
+ end
+
private
def encrypt(str)
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 924bd392f9..2085747eb9 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -1,106 +1,85 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright (c) 2010 VMware, 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/util/windows'
-
-#wrapper around a subset of the NetGroup* APIs.
-#nothing Chef specific, but not complete enough to be its own gem, so util for now.
-class Chef::Util::Windows::NetGroup < Chef::Util::Windows
-
- private
-
- def pack_str(s)
- [str_to_ptr(s)].pack('L')
- end
-
- def modify_members(members, func)
- buffer = 0.chr * (members.size * PTR_SIZE)
- members.each_with_index do |member,offset|
- buffer[offset*PTR_SIZE,PTR_SIZE] = pack_str(multi_to_wide(member))
- end
- rc = func.call(nil, @name, 3, buffer, members.size)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-
- public
-
- def initialize(groupname)
- @name = multi_to_wide(groupname)
- end
-
- def local_get_members
- group_members = []
- handle = 0.chr * PTR_SIZE
- rc = ERROR_MORE_DATA
-
- while rc == ERROR_MORE_DATA
- ptr = 0.chr * PTR_SIZE
- nread = 0.chr * PTR_SIZE
- total = 0.chr * PTR_SIZE
-
- rc = NetLocalGroupGetMembers.call(nil, @name, 0, ptr, -1,
- nread, total, handle)
- if (rc == NERR_Success) || (rc == ERROR_MORE_DATA)
- ptr = ptr.unpack('L')[0]
- nread = nread.unpack('i')[0]
- members = 0.chr * (nread * PTR_SIZE ) #nread * sizeof(LOCALGROUP_MEMBERS_INFO_0)
- memcpy(members, ptr, members.size)
-
- # 1 pointer field in LOCALGROUP_MEMBERS_INFO_0, offset 0 is lgrmi0_sid
- nread.times do |i|
- sid_address = members[i * PTR_SIZE, PTR_SIZE].unpack('L')[0]
- sid_ptr = FFI::Pointer.new(sid_address)
- member_sid = Chef::ReservedNames::Win32::Security::SID.new(sid_ptr)
- group_members << member_sid.to_s
- end
- NetApiBufferFree(ptr)
- else
- raise ArgumentError, get_last_error(rc)
- end
- end
- group_members
- end
-
- def local_add
- rc = NetLocalGroupAdd.call(nil, 0, pack_str(@name), nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-
- def local_set_members(members)
- modify_members(members, NetLocalGroupSetMembers)
- end
-
- def local_add_members(members)
- modify_members(members, NetLocalGroupAddMembers)
- end
-
- def local_delete_members(members)
- modify_members(members, NetLocalGroupDelMembers)
- end
-
- def local_delete
- rc = NetLocalGroupDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-end
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, 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/util/windows'
+require 'chef/win32/net'
+
+#wrapper around a subset of the NetGroup* APIs.
+class Chef::Util::Windows::NetGroup
+
+ private
+
+ def groupname
+ @groupname
+ end
+
+ public
+
+ def initialize(groupname)
+ @groupname = groupname
+ end
+
+ def local_get_members
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_get_members(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_add
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_add(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_set_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_set_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_add_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_add_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_delete_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_del_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+
+ end
+
+ def local_delete
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_del(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+end
diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb
index 62d7e169dc..b94576e702 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -21,61 +21,18 @@
#see also cmd.exe: net use /?
require 'chef/util/windows'
+require 'chef/win32/net'
class Chef::Util::Windows::NetUse < Chef::Util::Windows
-
- private
-
- USE_NOFORCE = 0
- USE_FORCE = 1
- USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
-
- USE_INFO_2 = [
- [:local, nil],
- [:remote, nil],
- [:password, nil],
- [:status, 0],
- [:asg_type, 0],
- [:refcount, 0],
- [:usecount, 0],
- [:username, nil],
- [:domainname, nil]
- ]
-
- USE_INFO_2_TEMPLATE =
- USE_INFO_2.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
- SIZEOF_USE_INFO_2 = #sizeof(USE_INFO_2)
- USE_INFO_2.inject(0) do |sum, item|
- sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
- end
-
- def use_info_2(args)
- USE_INFO_2.collect { |field|
- args.include?(field[0]) ? args[field[0]] : field[1]
- }
- end
-
- def use_info_2_pack(use)
- use.collect { |v|
- v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
- }.pack(USE_INFO_2_TEMPLATE)
+ def initialize(localname)
+ @use_name = localname
end
- def use_info_2_unpack(buffer)
- use = Hash.new
- USE_INFO_2.each_with_index do |field,offset|
- use[field[0]] = field[1].class == Fixnum ?
- dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+ def to_ui2_struct(use_info)
+ use_info.inject({}) do |memo, (k,v)|
+ memo["ui2_#{k}".to_sym] = v
+ memo
end
- use
- end
-
- public
-
- def initialize(localname)
- @localname = localname
- @name = multi_to_wide(localname)
end
def add(args)
@@ -84,38 +41,45 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows
args = Hash.new
args[:remote] = remote
end
- args[:local] ||= @localname
- use = use_info_2(args)
- buffer = use_info_2_pack(use)
- rc = NetUseAdd.call(nil, 2, buffer, nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ args[:local] ||= use_name
+ ui2_hash = to_ui2_struct(args)
+
+ begin
+ Chef::ReservedNames::Win32::Net.net_use_add_l2(nil, ui2_hash)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
- def get_info
- ptr = 0.chr * PTR_SIZE
- rc = NetUseGetInfo.call(nil, @name, 2, ptr)
-
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ def from_use_info_struct(ui2_hash)
+ ui2_hash.inject({}) do |memo, (k,v)|
+ memo[k.to_s.sub('ui2_', '').to_sym] = v
+ memo
end
+ end
- ptr = ptr.unpack('L')[0]
- buffer = 0.chr * SIZEOF_USE_INFO_2
- memcpy(buffer, ptr, buffer.size)
- NetApiBufferFree(ptr)
- use_info_2_unpack(buffer)
+ def get_info
+ begin
+ ui2 = Chef::ReservedNames::Win32::Net.net_use_get_info_l2(nil, use_name)
+ from_use_info_struct(ui2)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
+ end
end
def device
get_info()[:remote]
end
- #XXX should we use some FORCE here?
+
def delete
- rc = NetUseDel.call(nil, @name, USE_NOFORCE)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
+
+ def use_name
+ @use_name
+ end
end
diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb
index 08c3a53793..6e45594ba6 100644
--- a/lib/chef/util/windows/volume.rb
+++ b/lib/chef/util/windows/volume.rb
@@ -18,42 +18,42 @@
#simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex.
+require 'chef/win32/api/file'
require 'chef/util/windows'
-require 'windows/volume'
class Chef::Util::Windows::Volume < Chef::Util::Windows
-
- private
- include Windows::Volume
- #XXX not defined in the current windows-pr release
- DeleteVolumeMountPoint =
- Windows::API.new('DeleteVolumeMountPoint', 'S', 'B') unless defined? DeleteVolumeMountPoint
-
- public
+ attr_reader :mount_point
def initialize(name)
name += "\\" unless name =~ /\\$/ #trailing slash required
- @name = name
+ @mount_point = name
end
def device
- buffer = 0.chr * 256
- if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size)
- return buffer[0,buffer.size].unpack("Z*")[0]
- else
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def delete
- unless DeleteVolumeMountPoint.call(@name)
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def add(args)
- unless SetVolumeMountPoint(@name, args[:remote])
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.set_volume_mount_point(mount_point, args[:remote])
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
+
+ def mount_point
+ @mount_point
+ end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 80fd422c55..faa61aee54 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.rc.2'
+ VERSION = '12.5.0.current.0'
end
#
@@ -29,6 +29,6 @@ end
#
# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The
# Chef::Version class is for _cookbooks_ only, and cannot handle
-# pre-release chef-client versions like "10.14.0.rc.2". Please
-# use Rubygem's Gem::Version class instead.
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
#
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index e9d273808a..4786222bd4 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -188,6 +188,7 @@ class Chef
host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT.
host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB.
host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise.
+ host.typedef :pointer, :PSID
host.typedef :pointer, :PUCHAR # Pointer to a UCHAR.
host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR.
host.typedef :pointer, :PUINT # Pointer to a UINT.
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 86b2b942c2..9ff1ad40d6 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -20,6 +20,7 @@
require 'chef/win32/api'
require 'chef/win32/api/security'
require 'chef/win32/api/system'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -450,6 +451,25 @@ BOOL WINAPI DeviceIoControl(
=end
safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL
+
+#BOOL WINAPI DeleteVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint
+#);
+ safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL
+
+#BOOL WINAPI SetVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_In_ LPCTSTR lpszVolumeName
+#);
+ safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL
+
+#BOOL WINAPI GetVolumeNameForVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_Out_ LPTSTR lpszVolumeName,
+ #_In_ DWORD cchBufferLength
+#);
+ safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL
+
###############################################
# Helpers
###############################################
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index 72caf46628..b173987a05 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -17,6 +17,7 @@
#
require 'chef/win32/api'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -40,6 +41,10 @@ class Chef
UF_NORMAL_ACCOUNT = 0x000200
UF_DONT_EXPIRE_PASSWD = 0x010000
+ USE_NOFORCE = 0
+ USE_FORCE = 1
+ USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
+
NERR_Success = 0
NERR_InvalidComputer = 2351
NERR_NotPrimary = 2226
@@ -49,41 +54,13 @@ class Chef
NERR_BadPassword = 2203
NERR_PasswordTooShort = 2245
NERR_UserNotFound = 2221
+ NERR_GroupNotFound = 2220
ERROR_ACCESS_DENIED = 5
+ ERROR_MORE_DATA = 234
ffi_lib "netapi32"
- class USER_INFO_3 < FFI::Struct
- layout :usri3_name, :LPWSTR,
- :usri3_password, :LPWSTR,
- :usri3_password_age, :DWORD,
- :usri3_priv, :DWORD,
- :usri3_home_dir, :LPWSTR,
- :usri3_comment, :LPWSTR,
- :usri3_flags, :DWORD,
- :usri3_script_path, :LPWSTR,
- :usri3_auth_flags, :DWORD,
- :usri3_full_name, :LPWSTR,
- :usri3_usr_comment, :LPWSTR,
- :usri3_parms, :LPWSTR,
- :usri3_workstations, :LPWSTR,
- :usri3_last_logon, :DWORD,
- :usri3_last_logoff, :DWORD,
- :usri3_acct_expires, :DWORD,
- :usri3_max_storage, :DWORD,
- :usri3_units_per_week, :DWORD,
- :usri3_logon_hours, :PBYTE,
- :usri3_bad_pw_count, :DWORD,
- :usri3_num_logons, :DWORD,
- :usri3_logon_server, :LPWSTR,
- :usri3_country_code, :DWORD,
- :usri3_code_page, :DWORD,
- :usri3_user_id, :DWORD,
- :usri3_primary_group_id, :DWORD,
- :usri3_profile, :LPWSTR,
- :usri3_home_dir_drive, :LPWSTR,
- :usri3_password_expired, :DWORD
-
+ module StructHelpers
def set(key, val)
val = if val.is_a? String
encoded = if val.encoding == Encoding::UTF_16LE
@@ -115,6 +92,47 @@ class Chef
end
end
+ def as_ruby
+ members.inject({}) do |memo, key|
+ memo[key] = get(key)
+ memo
+ end
+ end
+ end
+
+
+ class USER_INFO_3 < FFI::Struct
+ include StructHelpers
+ layout :usri3_name, :LPWSTR,
+ :usri3_password, :LPWSTR,
+ :usri3_password_age, :DWORD,
+ :usri3_priv, :DWORD,
+ :usri3_home_dir, :LPWSTR,
+ :usri3_comment, :LPWSTR,
+ :usri3_flags, :DWORD,
+ :usri3_script_path, :LPWSTR,
+ :usri3_auth_flags, :DWORD,
+ :usri3_full_name, :LPWSTR,
+ :usri3_usr_comment, :LPWSTR,
+ :usri3_parms, :LPWSTR,
+ :usri3_workstations, :LPWSTR,
+ :usri3_last_logon, :DWORD,
+ :usri3_last_logoff, :DWORD,
+ :usri3_acct_expires, :DWORD,
+ :usri3_max_storage, :DWORD,
+ :usri3_units_per_week, :DWORD,
+ :usri3_logon_hours, :PBYTE,
+ :usri3_bad_pw_count, :DWORD,
+ :usri3_num_logons, :DWORD,
+ :usri3_logon_server, :LPWSTR,
+ :usri3_country_code, :DWORD,
+ :usri3_code_page, :DWORD,
+ :usri3_user_id, :DWORD,
+ :usri3_primary_group_id, :DWORD,
+ :usri3_profile, :LPWSTR,
+ :usri3_home_dir_drive, :LPWSTR,
+ :usri3_password_expired, :DWORD
+
def usri3_logon_hours
val = self[:usri3_logon_hours]
if !val.nil? && !val.null?
@@ -123,19 +141,66 @@ class Chef
nil
end
end
+ end
- def as_ruby
- members.inject({}) do |memo, key|
- memo[key] = get(key)
- memo
- end
- end
+ class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct
+ layout :lgrmi0_sid, :PSID
end
class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
layout :lgrmi3_domainandname, :LPWSTR
end
+ class LOCALGROUP_INFO_0 < FFI::Struct
+ layout :lgrpi0_name, :LPWSTR
+ end
+
+ class USE_INFO_2 < FFI::Struct
+ include StructHelpers
+
+ layout :ui2_local, :LMSTR,
+ :ui2_remote, :LMSTR,
+ :ui2_password, :LMSTR,
+ :ui2_status, :DWORD,
+ :ui2_asg_type, :DWORD,
+ :ui2_refcount, :DWORD,
+ :ui2_usecount, :DWORD,
+ :ui2_username, :LPWSTR,
+ :ui2_domainname, :LMSTR
+ end
+
+
+#NET_API_STATUS NetLocalGroupAdd(
+ #_In_ LPCWSTR servername,
+ #_In_ DWORD level,
+ #_In_ LPBYTE buf,
+ #_Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetLocalGroupAdd, [
+ :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDel(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR groupname
+#);
+ safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetLocalGroupGetMembers(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR localgroupname,
+ #_In_ DWORD level,
+ #_Out_ LPBYTE *bufptr,
+ #_In_ DWORD prefmaxlen,
+ #_Out_ LPDWORD entriesread,
+ #_Out_ LPDWORD totalentries,
+ #_Inout_ PDWORD_PTR resumehandle
+#);
+ safe_attach_function :NetLocalGroupGetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD,
+ :LPDWORD, :LPDWORD, :PDWORD_PTR
+ ], :DWORD
+
# NET_API_STATUS NetUserEnum(
# _In_ LPCWSTR servername,
# _In_ DWORD level,
@@ -146,12 +211,15 @@ class Chef
# _Out_ LPDWORD totalentries,
# _Inout_ LPDWORD resume_handle
# );
- safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD
+ safe_attach_function :NetUserEnum, [
+ :LPCWSTR, :DWORD, :DWORD, :LPBYTE,
+ :DWORD, :LPDWORD, :LPDWORD, :LPDWORD
+ ], :DWORD
# NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
# );
- safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD
+ safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
#NET_API_STATUS NetUserAdd(
#_In_ LMSTR servername,
@@ -159,7 +227,9 @@ class Chef
#_In_ LPBYTE buf,
#_Out_ LPDWORD parm_err
#);
- safe_attach_function :NetUserAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD
+ safe_attach_function :NetUserAdd, [
+ :LMSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
#NET_API_STATUS NetLocalGroupAddMembers(
# _In_ LPCWSTR servername,
@@ -168,7 +238,31 @@ class Chef
# _In_ LPBYTE buf,
# _In_ DWORD totalentries
#);
- safe_attach_function :NetLocalGroupAddMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD
+ safe_attach_function :NetLocalGroupAddMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupSetMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupSetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDelMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupDelMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
#NET_API_STATUS NetUserGetInfo(
# _In_ LPCWSTR servername,
@@ -176,7 +270,9 @@ class Chef
# _In_ DWORD level,
# _Out_ LPBYTE *bufptr
#);
- safe_attach_function :NetUserGetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE], :DWORD
+ safe_attach_function :NetUserGetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE
+ ], :DWORD
#NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
@@ -190,7 +286,9 @@ class Chef
# _In_ LPBYTE buf,
# _Out_ LPDWORD parm_err
#);
- safe_attach_function :NetUserSetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
+ safe_attach_function :NetUserSetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
#NET_API_STATUS NetUserDel(
# _In_ LPCWSTR servername,
@@ -198,6 +296,28 @@ class Chef
#);
safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD
+#NET_API_STATUS NetUseDel(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD ForceCond
+#);
+ safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD
+
+#NET_API_STATUS NetUseGetInfo(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD Level,
+ #_Out_ LPBYTE *BufPtr
+#);
+ safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD
+
+#NET_API_STATUS NetUseAdd(
+ #_In_ LMSTR UncServerName,
+ #_In_ DWORD Level,
+ #_In_ LPBYTE Buf,
+ #_Out_ LPDWORD ParmError
+#);
+ safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
end
end
end
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
new file mode 100644
index 0000000000..45b91d7d32
--- /dev/null
+++ b/lib/chef/win32/api/registry.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Salim Alam (<salam@chef.io>)
+# Copyright:: Copyright 2015 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/win32/api'
+
+class Chef
+ module ReservedNames::Win32
+ module API
+ module Registry
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib 'advapi32'
+
+ # LONG WINAPI RegDeleteKeyEx(
+ # _In_ HKEY hKey,
+ # _In_ LPCTSTR lpSubKey,
+ # _In_ REGSAM samDesired,
+ # _Reserved_ DWORD Reserved
+ # );
+ safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+ safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index d57699acb4..a485f89708 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -187,6 +187,29 @@ int WINAPI GetSystemMetrics(
safe_attach_function :GetSystemMetrics, [:int], :int
=begin
+UINT WINAPI GetSystemWow64Directory(
+ _Out_ LPTSTR lpBuffer,
+ _In_ UINT uSize
+);
+=end
+ safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT
+ safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT
+
+=begin
+BOOL WINAPI Wow64DisableWow64FsRedirection(
+ _Out_ PVOID *OldValue
+);
+=end
+ safe_attach_function :Wow64DisableWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
+BOOL WINAPI Wow64RevertWow64FsRedirection(
+ _In_ PVOID OldValue
+);
+=end
+ safe_attach_function :Wow64RevertWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
LRESULT WINAPI SendMessageTimeout(
_In_ HWND hWnd,
_In_ UINT Msg,
diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb
index 2e3a599f0a..2a9166aa99 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -129,49 +129,6 @@ int WideCharToMultiByte(
=end
safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int
- ###############################################
- # Helpers
- ###############################################
-
- def utf8_to_wide(ustring)
- # ensure it is actually UTF-8
- # Ruby likes to mark binary data as ASCII-8BIT
- ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
-
- # ensure we have the double-null termination Windows Wide likes
- ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000"
-
- # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
- ustring = begin
- if ustring.respond_to?(:encode)
- ustring.encode('UTF-16LE')
- else
- require 'iconv'
- Iconv.conv("UTF-16LE", "UTF-8", ustring)
- end
- end
- ustring
- end
-
- def wide_to_utf8(wstring)
- # ensure it is actually UTF-16LE
- # Ruby likes to mark binary data as ASCII-8BIT
- wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
-
- # encode it all as UTF-8
- wstring = begin
- if wstring.respond_to?(:encode)
- wstring.encode('UTF-8')
- else
- require 'iconv'
- Iconv.conv("UTF-8", "UTF-16LE", wstring)
- end
- end
- # remove trailing CRLF and NULL characters
- wstring.strip!
- wstring
- end
-
end
end
end
diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb
index 79cf51b002..aa20c2dfd4 100644
--- a/lib/chef/win32/crypto.rb
+++ b/lib/chef/win32/crypto.rb
@@ -19,6 +19,7 @@
require 'chef/win32/error'
require 'chef/win32/api/memory'
require 'chef/win32/api/crypto'
+require 'chef/win32/unicode'
require 'digest'
class Chef
diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb
index e6640caa3c..700ddb24d3 100644
--- a/lib/chef/win32/file.rb
+++ b/lib/chef/win32/file.rb
@@ -17,9 +17,11 @@
# limitations under the License.
#
+require 'chef/mixin/wide_string'
require 'chef/win32/api/file'
require 'chef/win32/api/security'
require 'chef/win32/error'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -27,6 +29,9 @@ class Chef
include Chef::ReservedNames::Win32::API::File
extend Chef::ReservedNames::Win32::API::File
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
# Creates a symbolic link called +new_name+ for the file or directory
# +old_name+.
#
@@ -157,9 +162,9 @@ class Chef
def self.file_access_check(path, desired_access)
security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path)
- token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
+ token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
Chef::ReservedNames::Win32::Security::TOKEN_QUERY |
- Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
+ Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ
token = Chef::ReservedNames::Win32::Security.open_process_token(
Chef::ReservedNames::Win32::Process.get_current_process,
@@ -172,10 +177,30 @@ class Chef
mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE
mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS
- Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
+ Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
desired_access, mapping)
end
+ def self.delete_volume_mount_point(mount_point)
+ unless DeleteVolumeMountPointW(wstring(mount_point))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.set_volume_mount_point(mount_point, name)
+ unless SetVolumeMountPointW(wstring(mount_point), wstring(name))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.get_volume_name_for_volume_mount_point(mount_point)
+ buffer = FFI::MemoryPointer.new(2, 128)
+ unless GetVolumeNameForVolumeMountPointW(wstring(mount_point), buffer, buffer.size/buffer.type_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ buffer.read_wstring
+ end
+
# ::File compat
class << self
alias :stat :info
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
index 0b7d99f111..f4755e9019 100644
--- a/lib/chef/win32/mutex.rb
+++ b/lib/chef/win32/mutex.rb
@@ -17,6 +17,7 @@
#
require 'chef/win32/api/synchronization'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -113,5 +114,3 @@ if the mutex is attempted to be acquired by other threads.")
end
end
end
-
-
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
index 1349091eb9..59f29c4d1b 100644
--- a/lib/chef/win32/net.rb
+++ b/lib/chef/win32/net.rb
@@ -18,11 +18,11 @@
require 'chef/win32/api/net'
require 'chef/win32/error'
-require 'chef/mixin/wstring'
+require 'chef/mixin/wide_string'
class Chef
module ReservedNames::Win32
- class NetUser
+ class Net
include Chef::ReservedNames::Win32::API::Error
extend Chef::ReservedNames::Win32::API::Error
@@ -91,19 +91,72 @@ The password is shorter than required. (The password could also be too
long, be too recent in its change history, not have enough unique characters,
or not meet another password policy requirement.)
END
+ when NERR_GroupNotFound
+ "The group name could not be found."
when ERROR_ACCESS_DENIED
"The user does not have access to the requested information."
else
"Received unknown error code (#{code})"
end
- formatted_message = ""
- formatted_message << "---- Begin Win32 API output ----\n"
- formatted_message << "Net Api Error Code: #{code}\n"
- formatted_message << "Net Api Error Message: #{msg}\n"
- formatted_message << "---- End Win32 API output ----\n"
+ raise Chef::Exceptions::Win32NetAPIError.new(msg, code)
+ end
+
+ def self.net_local_group_add(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = LOCALGROUP_INFO_0.new
+ buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name)
+
+ rc = NetLocalGroupAdd(server_name, 0, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
- raise Chef::Exceptions::Win32APIError, msg + "\n" + formatted_message
+ rc = NetLocalGroupDel(server_name, group_name)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_get_members(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = FFI::MemoryPointer.new(:pointer)
+ entries_read_ptr = FFI::MemoryPointer.new(:long)
+ total_read_ptr = FFI::MemoryPointer.new(:long)
+ resume_handle_ptr = FFI::MemoryPointer.new(:pointer)
+
+ rc = ERROR_MORE_DATA
+ group_members = []
+ while rc == ERROR_MORE_DATA
+ rc = NetLocalGroupGetMembers(
+ server_name, group_name, 0, buf, -1,
+ entries_read_ptr, total_read_ptr, resume_handle_ptr
+ )
+
+ nread = entries_read_ptr.read_long
+ nread.times do |i|
+ member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer +
+ (i * LOCALGROUP_MEMBERS_INFO_0.size))
+ member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid])
+ group_members << member_sid.to_s
+ end
+ NetApiBufferFree(buf.read_pointer)
+ end
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ group_members
end
def self.net_user_add_l3(server_name, args)
@@ -185,6 +238,107 @@ END
end
end
+ def self.members_to_lgrmi3(members)
+ buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
+ members.size.times.collect do |i|
+ member_info = LOCALGROUP_MEMBERS_INFO_3.new(
+ buf + i * LOCALGROUP_MEMBERS_INFO_3.size)
+ member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
+ member_info
+ end
+ end
+
+ def self.net_local_group_add_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupAddMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_set_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupSetMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupDelMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_del(server_name, use_name, force=:use_noforce)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ force_const = case force
+ when :use_noforce
+ USE_NOFORCE
+ when :use_force
+ USE_FORCE
+ when :use_lots_of_force
+ USE_LOTS_OF_FORCE
+ else
+ raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]"
+ end
+
+ rc = NetUseDel(server_name, use_name, force_const)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_get_info_l2(server_name, use_name)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ ui2_p = FFI::MemoryPointer.new(:pointer)
+
+ rc = NetUseGetInfo(server_name, use_name, 2, ui2_p)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby
+ NetApiBufferFree(ui2_p.read_pointer)
+
+ ui2
+ end
+
+ def self.net_use_add_l2(server_name, ui2_hash)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = USE_INFO_2.new
+
+ ui2_hash.each do |(k,v)|
+ buf.set(k,v)
+ end
+
+ rc = NetUseAdd(server_name, 2, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
end
+ NetUser = Net # For backwards compatibility
end
end
diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb
index 2df39bb918..767d4f390c 100644
--- a/lib/chef/win32/process.rb
+++ b/lib/chef/win32/process.rb
@@ -69,6 +69,19 @@ class Chef
result
end
+ def self.is_wow64_process
+ is_64_bit_process_result = FFI::MemoryPointer.new(:int)
+
+ # The return value of IsWow64Process is nonzero value if the API call succeeds.
+ # The result data are returned in the last parameter, not the return value.
+ call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
+
+ # The result is nonzero if IsWow64Process's calling process, in the case here
+ # this process, is running under WOW64, i.e. the result is nonzero if this
+ # process is 32-bit (aka :i386).
+ (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+ end
+
# Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights,
# AND the PROCESS_VM_READ right
def self.get_process_memory_info(handle)
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 18f12d26b8..b25ce7937e 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -17,8 +17,11 @@
# limitations under the License.
#
require 'chef/reserved_names'
+require 'chef/win32/api'
+require 'chef/mixin/wide_string'
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/win32/api/registry'
require 'win32/registry'
require 'win32/api'
end
@@ -27,6 +30,14 @@ class Chef
class Win32
class Registry
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::ReservedNames::Win32::API::Registry
+ extend Chef::ReservedNames::Win32::API::Registry
+ end
+
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
attr_accessor :run_context
attr_accessor :architecture
@@ -142,9 +153,8 @@ class Chef
#Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003)
#instead of the 'RegDeleteKey'
def delete_key_ex(hive, key)
- regDeleteKeyEx = ::Win32::API.new('RegDeleteKeyEx', 'LPLL', 'L', 'advapi32')
hive_num = hive.hkey - (1 << 32)
- regDeleteKeyEx.call(hive_num, key, ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0)
+ RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == 0
end
def key_exists?(key_path)
@@ -203,7 +213,7 @@ class Chef
key_exists!(key_path)
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
- return true if reg.any? {|val| val == value[:name] }
+ return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) }
end
return false
end
@@ -213,7 +223,7 @@ class Chef
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each do |val_name, val_type, val_data|
- if val_name == value[:name] &&
+ if safely_downcase(val_name) == safely_downcase(value[:name]) &&
val_type == get_type_from_name(value[:type]) &&
val_data == value[:data]
return true
@@ -289,6 +299,14 @@ class Chef
private
+
+ def safely_downcase(val)
+ if val.is_a? String
+ return val.downcase
+ end
+ return val
+ end
+
def node
run_context && run_context.node
end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 5c83180bc0..bc80517d80 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -22,7 +22,7 @@ require 'chef/win32/memory'
require 'chef/win32/process'
require 'chef/win32/unicode'
require 'chef/win32/security/token'
-require 'chef/mixin/wstring'
+require 'chef/mixin/wide_string'
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb
index 9e494a73b9..8d4e54ad8c 100644
--- a/lib/chef/win32/security/token.rb
+++ b/lib/chef/win32/security/token.rb
@@ -18,7 +18,7 @@
require 'chef/win32/security'
require 'chef/win32/api/security'
-
+require 'chef/win32/unicode'
require 'ffi'
class Chef
diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb
new file mode 100755
index 0000000000..cdd063f174
--- /dev/null
+++ b/lib/chef/win32/system.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Salim Alam (<salam@chef.io>)
+# Copyright:: Copyright 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api/system'
+require 'chef/win32/error'
+require 'ffi'
+
+class Chef
+ module ReservedNames::Win32
+ class System
+ include Chef::ReservedNames::Win32::API::System
+ extend Chef::ReservedNames::Win32::API::System
+
+ def self.get_system_wow64_directory
+ ptr = FFI::MemoryPointer.new(:char, 255, true)
+ succeeded = GetSystemWow64DirectoryA(ptr, 255)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to get Wow64 system directory"
+ end
+
+ ptr.read_string.strip
+ end
+
+ def self.wow64_disable_wow64_fs_redirection
+ original_redirection_state = FFI::MemoryPointer.new(:pointer)
+
+ succeeded = Wow64DisableWow64FsRedirection(original_redirection_state)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to disable Wow64 file redirection"
+ end
+
+ original_redirection_state
+ end
+
+ def self.wow64_revert_wow64_fs_redirection(original_redirection_state)
+ succeeded = Wow64RevertWow64FsRedirection(original_redirection_state)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to revert Wow64 file redirection"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb
index e7399d5255..562301a040 100644
--- a/lib/chef/win32/unicode.rb
+++ b/lib/chef/win32/unicode.rb
@@ -17,6 +17,7 @@
# limitations under the License.
#
+require 'chef/mixin/wide_string'
require 'chef/win32/api/unicode'
class Chef
@@ -30,6 +31,8 @@ end
module FFI
class Pointer
+ include Chef::Mixin::WideString
+
def read_wstring(num_wchars = nil)
if num_wchars.nil?
# Find the length of the string
@@ -43,13 +46,42 @@ module FFI
num_wchars = length
end
- Chef::ReservedNames::Win32::Unicode.wide_to_utf8(self.get_bytes(0, num_wchars*2))
+ wide_to_utf8(self.get_bytes(0, num_wchars*2))
end
end
end
class String
+ include Chef::Mixin::WideString
+
def to_wstring
- Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self)
+ utf8_to_wide(self)
end
end
+
+# https://bugs.ruby-lang.org/issues/11439
+if RUBY_VERSION =~ /^2\.1/
+ module Win32
+ class Registry
+ def write(name, type, data)
+ case type
+ when REG_SZ, REG_EXPAND_SZ
+ data = data.to_s.encode(WCHAR) + WCHAR_NUL
+ when REG_MULTI_SZ
+ data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL
+ when REG_BINARY
+ data = data.to_s
+ when REG_DWORD
+ data = API.packdw(data.to_i)
+ when REG_DWORD_BIG_ENDIAN
+ data = [data.to_i].pack('N')
+ when REG_QWORD
+ data = API.packqw(data.to_i)
+ else
+ raise TypeError, "Unsupported type #{type}"
+ end
+ API.SetValue(@hkey, name, type, data, data.bytesize)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 17c27e4780..6a7a65b01b 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -122,10 +122,6 @@ class Chef
# WMI always returns the truth. See article at
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
- # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
- # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
- # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
wmi = WmiLite::Wmi.new
os_info = wmi.first_of('Win32_OperatingSystem')
os_version = os_info['version']
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 2454c9cccf..8398c5d616 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,5 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Author:: Claire McQuin (<claire@chef.io>)
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
@@ -16,163 +16,8 @@
# limitations under the License.
#
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
-require 'chef/util/path_helper'
+require 'chef-config/workstation_config_loader'
class Chef
-
- class WorkstationConfigLoader
-
- # Path to a config file requested by user, (e.g., via command line option). Can be nil
- attr_accessor :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
- Chef::Util::PathHelper.home('.chef') do |dot_chef_dir|
- candidate_configs << File.join(dot_chef_dir, 'config.rb')
- candidate_configs << File.join(dot_chef_dir, '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
+ WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader
end