summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/api_client.rb156
-rw-r--r--lib/chef/application.rb1
-rw-r--r--lib/chef/application/client.rb6
-rw-r--r--lib/chef/audit/audit_reporter.rb19
-rw-r--r--lib/chef/audit/logger.rb36
-rw-r--r--lib/chef/audit/runner.rb6
-rw-r--r--lib/chef/chef_class.rb73
-rw-r--r--lib/chef/client.rb794
-rw-r--r--lib/chef/config.rb737
-rw-r--r--lib/chef/cookbook/metadata.rb14
-rw-r--r--lib/chef/cookbook_loader.rb2
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb20
-rw-r--r--lib/chef/dsl/definitions.rb44
-rw-r--r--lib/chef/dsl/recipe.rb94
-rw-r--r--lib/chef/dsl/resources.rb29
-rw-r--r--lib/chef/event_dispatch/base.rb9
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb2
-rw-r--r--lib/chef/event_loggers/windows_eventlog.rb12
-rw-r--r--lib/chef/exceptions.rb5
-rw-r--r--lib/chef/file_access_control/unix.rb5
-rw-r--r--lib/chef/file_content_management/deploy/mv_windows.rb22
-rw-r--r--lib/chef/formatters/doc.rb22
-rw-r--r--lib/chef/formatters/error_inspectors/api_error_formatting.rb20
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb30
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/node_load_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/registration_error_inspector.rb4
-rw-r--r--lib/chef/formatters/error_inspectors/resource_failure_inspector.rb12
-rw-r--r--lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb2
-rw-r--r--lib/chef/guard_interpreter/default_guard_interpreter.rb2
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb5
-rw-r--r--lib/chef/http/authenticator.rb8
-rw-r--r--lib/chef/http/basic_client.rb16
-rw-r--r--lib/chef/http/json_input.rb7
-rw-r--r--lib/chef/key.rb30
-rw-r--r--lib/chef/knife.rb24
-rw-r--r--lib/chef/knife/bootstrap.rb6
-rw-r--r--lib/chef/knife/client_create.rb86
-rw-r--r--lib/chef/knife/client_key_delete.rb76
-rw-r--r--lib/chef/knife/client_key_edit.rb80
-rw-r--r--lib/chef/knife/client_key_list.rb69
-rw-r--r--lib/chef/knife/client_key_show.rb76
-rw-r--r--lib/chef/knife/core/generic_presenter.rb2
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb2
-rw-r--r--lib/chef/knife/key_delete.rb55
-rw-r--r--lib/chef/knife/key_edit.rb114
-rw-r--r--lib/chef/knife/key_edit_base.rb55
-rw-r--r--lib/chef/knife/key_list.rb88
-rw-r--r--lib/chef/knife/key_list_base.rb45
-rw-r--r--lib/chef/knife/key_show.rb53
-rw-r--r--lib/chef/knife/osc_user_create.rb97
-rw-r--r--lib/chef/knife/osc_user_delete.rb51
-rw-r--r--lib/chef/knife/osc_user_edit.rb58
-rw-r--r--lib/chef/knife/osc_user_list.rb47
-rw-r--r--lib/chef/knife/osc_user_reregister.rb64
-rw-r--r--lib/chef/knife/osc_user_show.rb54
-rw-r--r--lib/chef/knife/ssh.rb54
-rw-r--r--lib/chef/knife/user_create.rb131
-rw-r--r--lib/chef/knife/user_delete.rb54
-rw-r--r--lib/chef/knife/user_edit.rb44
-rw-r--r--lib/chef/knife/user_key_delete.rb76
-rw-r--r--lib/chef/knife/user_key_edit.rb80
-rw-r--r--lib/chef/knife/user_key_list.rb69
-rw-r--r--lib/chef/knife/user_key_show.rb76
-rw-r--r--lib/chef/knife/user_list.rb3
-rw-r--r--lib/chef/knife/user_reregister.rb47
-rw-r--r--lib/chef/knife/user_show.rb31
-rw-r--r--lib/chef/log.rb2
-rw-r--r--lib/chef/log/syslog.rb46
-rw-r--r--lib/chef/log/winevt.rb99
-rw-r--r--lib/chef/mixin/api_version_request_handling.rb66
-rw-r--r--lib/chef/mixin/convert_to_class_name.rb14
-rw-r--r--lib/chef/mixin/deprecation.rb24
-rw-r--r--lib/chef/mixin/powershell_out.rb98
-rw-r--r--lib/chef/mixin/provides.rb28
-rw-r--r--lib/chef/mixin/unformatter.rb32
-rw-r--r--lib/chef/mixin/uris.rb44
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb7
-rw-r--r--lib/chef/mixin/windows_env_helper.rb13
-rw-r--r--lib/chef/mixin/wstring.rb31
-rw-r--r--lib/chef/node.rb23
-rw-r--r--lib/chef/node_map.rb205
-rw-r--r--lib/chef/osc_user.rb194
-rw-r--r--lib/chef/platform/provider_mapping.rb283
-rw-r--r--lib/chef/platform/provider_priority_map.rb75
-rw-r--r--lib/chef/platform/query_helpers.rb11
-rw-r--r--lib/chef/platform/resource_priority_map.rb27
-rw-r--r--lib/chef/platform/service_helpers.rb42
-rw-r--r--lib/chef/policy_builder/policyfile.rb1
-rw-r--r--lib/chef/provider.rb52
-rw-r--r--lib/chef/provider/cron/unix.rb1
-rw-r--r--lib/chef/provider/directory.rb3
-rw-r--r--lib/chef/provider/dsc_resource.rb9
-rw-r--r--lib/chef/provider/file.rb9
-rw-r--r--lib/chef/provider/group/aix.rb1
-rw-r--r--lib/chef/provider/group/dscl.rb2
-rw-r--r--lib/chef/provider/group/gpasswd.rb1
-rw-r--r--lib/chef/provider/group/groupmod.rb2
-rw-r--r--lib/chef/provider/group/pw.rb1
-rw-r--r--lib/chef/provider/group/suse.rb2
-rw-r--r--lib/chef/provider/group/usermod.rb3
-rw-r--r--lib/chef/provider/group/windows.rb2
-rw-r--r--lib/chef/provider/ifconfig.rb2
-rw-r--r--lib/chef/provider/ifconfig/aix.rb1
-rw-r--r--lib/chef/provider/ifconfig/debian.rb2
-rw-r--r--lib/chef/provider/ifconfig/redhat.rb1
-rw-r--r--lib/chef/provider/lwrp_base.rb138
-rw-r--r--lib/chef/provider/mount.rb1
-rw-r--r--lib/chef/provider/mount/aix.rb1
-rw-r--r--lib/chef/provider/mount/mount.rb2
-rw-r--r--lib/chef/provider/mount/solaris.rb2
-rw-r--r--lib/chef/provider/ohai.rb1
-rw-r--r--lib/chef/provider/package.rb66
-rw-r--r--lib/chef/provider/package/aix.rb15
-rw-r--r--lib/chef/provider/package/apt.rb6
-rw-r--r--lib/chef/provider/package/dpkg.rb8
-rw-r--r--lib/chef/provider/package/easy_install.rb10
-rw-r--r--lib/chef/provider/package/freebsd/base.rb4
-rw-r--r--lib/chef/provider/package/freebsd/pkg.rb12
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb10
-rw-r--r--lib/chef/provider/package/freebsd/port.rb8
-rw-r--r--lib/chef/provider/package/homebrew.rb4
-rw-r--r--lib/chef/provider/package/ips.rb8
-rw-r--r--lib/chef/provider/package/macports.rb11
-rw-r--r--lib/chef/provider/package/openbsd.rb9
-rw-r--r--lib/chef/provider/package/pacman.rb8
-rw-r--r--lib/chef/provider/package/portage.rb2
-rw-r--r--lib/chef/provider/package/rpm.rb15
-rw-r--r--lib/chef/provider/package/rubygems.rb17
-rw-r--r--lib/chef/provider/package/smartos.rb10
-rw-r--r--lib/chef/provider/package/solaris.rb14
-rw-r--r--lib/chef/provider/package/windows.rb96
-rw-r--r--lib/chef/provider/package/windows/msi.rb2
-rw-r--r--lib/chef/provider/package/yum.rb114
-rw-r--r--lib/chef/provider/package/zypper.rb30
-rw-r--r--lib/chef/provider/powershell_script.rb176
-rw-r--r--lib/chef/provider/reboot.rb1
-rw-r--r--lib/chef/provider/registry_key.rb2
-rw-r--r--lib/chef/provider/remote_file.rb1
-rw-r--r--lib/chef/provider/remote_file/content.rb9
-rw-r--r--lib/chef/provider/remote_file/fetcher.rb30
-rw-r--r--lib/chef/provider/remote_file/local_file.rb14
-rw-r--r--lib/chef/provider/remote_file/network_file.rb48
-rw-r--r--lib/chef/provider/service.rb44
-rw-r--r--lib/chef/provider/service/aix.rb25
-rw-r--r--lib/chef/provider/service/freebsd.rb2
-rw-r--r--lib/chef/provider/service/init.rb1
-rw-r--r--lib/chef/provider/service/macosx.rb2
-rw-r--r--lib/chef/provider/service/windows.rb1
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/provider/user/aix.rb5
-rw-r--r--lib/chef/provider/user/pw.rb1
-rw-r--r--lib/chef/provider/user/solaris.rb2
-rw-r--r--lib/chef/provider/user/useradd.rb1
-rw-r--r--lib/chef/provider_resolver.rb154
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource.rb339
-rw-r--r--lib/chef/resource/apt_package.rb2
-rw-r--r--lib/chef/resource/bash.rb1
-rw-r--r--lib/chef/resource/batch.rb2
-rw-r--r--lib/chef/resource/bff_package.rb8
-rw-r--r--lib/chef/resource/breakpoint.rb8
-rw-r--r--lib/chef/resource/chef_gem.rb3
-rw-r--r--lib/chef/resource/cookbook_file.rb4
-rw-r--r--lib/chef/resource/cron.rb6
-rw-r--r--lib/chef/resource/csh.rb1
-rw-r--r--lib/chef/resource/deploy.rb14
-rw-r--r--lib/chef/resource/deploy_revision.rb14
-rw-r--r--lib/chef/resource/directory.rb6
-rw-r--r--lib/chef/resource/dpkg_package.rb5
-rw-r--r--lib/chef/resource/dsc_resource.rb5
-rw-r--r--lib/chef/resource/dsc_script.rb5
-rw-r--r--lib/chef/resource/easy_install_package.rb7
-rw-r--r--lib/chef/resource/env.rb6
-rw-r--r--lib/chef/resource/erl_call.rb6
-rw-r--r--lib/chef/resource/execute.rb5
-rw-r--r--lib/chef/resource/file.rb24
-rw-r--r--lib/chef/resource/freebsd_package.rb5
-rw-r--r--lib/chef/resource/gem_package.rb3
-rw-r--r--lib/chef/resource/git.rb3
-rw-r--r--lib/chef/resource/group.rb6
-rw-r--r--lib/chef/resource/homebrew_package.rb2
-rw-r--r--lib/chef/resource/http_request.rb6
-rw-r--r--lib/chef/resource/ifconfig.rb8
-rw-r--r--lib/chef/resource/ips_package.rb4
-rw-r--r--lib/chef/resource/link.rb8
-rw-r--r--lib/chef/resource/log.rb7
-rw-r--r--lib/chef/resource/lwrp_base.rb176
-rw-r--r--lib/chef/resource/macosx_service.rb3
-rw-r--r--lib/chef/resource/macports_package.rb7
-rw-r--r--lib/chef/resource/mdadm.rb7
-rw-r--r--lib/chef/resource/mount.rb6
-rw-r--r--lib/chef/resource/ohai.rb5
-rw-r--r--lib/chef/resource/openbsd_package.rb6
-rw-r--r--lib/chef/resource/package.rb14
-rw-r--r--lib/chef/resource/pacman_package.rb7
-rw-r--r--lib/chef/resource/paludis_package.rb5
-rw-r--r--lib/chef/resource/perl.rb2
-rw-r--r--lib/chef/resource/portage_package.rb2
-rw-r--r--lib/chef/resource/powershell_script.rb3
-rw-r--r--lib/chef/resource/python.rb2
-rw-r--r--lib/chef/resource/reboot.rb4
-rw-r--r--lib/chef/resource/registry_key.rb7
-rw-r--r--lib/chef/resource/remote_directory.rb8
-rw-r--r--lib/chef/resource/remote_file.rb9
-rw-r--r--lib/chef/resource/route.rb9
-rw-r--r--lib/chef/resource/rpm_package.rb2
-rw-r--r--lib/chef/resource/ruby.rb3
-rw-r--r--lib/chef/resource/ruby_block.rb5
-rw-r--r--lib/chef/resource/scm.rb7
-rw-r--r--lib/chef/resource/script.rb2
-rw-r--r--lib/chef/resource/service.rb7
-rw-r--r--lib/chef/resource/smartos_package.rb9
-rw-r--r--lib/chef/resource/solaris_package.rb10
-rw-r--r--lib/chef/resource/subversion.rb3
-rw-r--r--lib/chef/resource/template.rb4
-rw-r--r--lib/chef/resource/timestamped_deploy.rb4
-rw-r--r--lib/chef/resource/user.rb7
-rw-r--r--lib/chef/resource/whyrun_safe_ruby_block.rb6
-rw-r--r--lib/chef/resource/windows_package.rb32
-rw-r--r--lib/chef/resource/windows_script.rb5
-rw-r--r--lib/chef/resource/windows_service.rb6
-rw-r--r--lib/chef/resource/yum_package.rb5
-rw-r--r--lib/chef/resource/zypper_package.rb27
-rw-r--r--lib/chef/resource_builder.rb7
-rw-r--r--lib/chef/resource_definition.rb1
-rw-r--r--lib/chef/resource_reporter.rb15
-rw-r--r--lib/chef/resource_resolver.rb166
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--lib/chef/rest.rb1
-rw-r--r--lib/chef/run_context.rb1
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb18
-rw-r--r--lib/chef/run_status.rb6
-rw-r--r--lib/chef/server_api.rb2
-rw-r--r--lib/chef/shell.rb2
-rw-r--r--lib/chef/user.rb235
-rw-r--r--lib/chef/util/backup.rb10
-rw-r--r--lib/chef/util/path_helper.rb208
-rw-r--r--lib/chef/util/windows/net_user.rb191
-rw-r--r--lib/chef/version.rb12
-rw-r--r--lib/chef/win32/api.rb3
-rw-r--r--lib/chef/win32/api/installer.rb2
-rw-r--r--lib/chef/win32/api/net.rb117
-rw-r--r--lib/chef/win32/api/security.rb24
-rw-r--r--lib/chef/win32/api/unicode.rb2
-rw-r--r--lib/chef/win32/eventlog.rb31
-rw-r--r--lib/chef/win32/net.rb190
-rw-r--r--lib/chef/win32/security.rb53
-rw-r--r--lib/chef/win32/security/sid.rb17
250 files changed, 6083 insertions, 2903 deletions
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index ce9ceb312c..ad31fb7d7b 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -1,7 +1,7 @@
#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Nuo Yan (<nuo@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# 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");
@@ -23,12 +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'
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
@@ -37,6 +43,25 @@ 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.
@@ -88,7 +113,8 @@ class Chef
)
end
- # Gets or sets the private key.
+ # 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.
@@ -96,7 +122,19 @@ class Chef
set_or_return(
:private_key,
arg,
- :kind_of => [String, FalseClass]
+ :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
@@ -107,13 +145,14 @@ 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 if @private_key
+ 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
@@ -127,10 +166,11 @@ 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
@@ -142,10 +182,6 @@ class Chef
from_hash(Chef::JSONCompat.parse(j))
end
- def self.http_api
- Chef::REST.new(Chef::Config[:chef_server_url])
- end
-
def self.reregister(name)
api_client = load(name)
api_client.reregister
@@ -182,11 +218,11 @@ class Chef
# Save this client via the REST API, returns a hash including the private key
def save
begin
- http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator})
+ update
rescue Net::HTTPServerException => e
# If that fails, go ahead and try and update it
if e.response.code == "404"
- http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator })
+ create
else
raise e
end
@@ -194,18 +230,95 @@ class Chef
end
def reregister
- reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ # 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
+
+ new_client
end
# Create the client via the REST API
def create
- http_api.post("clients", self)
+ 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))
end
# As a string
@@ -213,14 +326,5 @@ 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::REST.new(Chef::Config[:chef_server_url])
- end
-
end
end
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 297e46ef3c..0563822ede 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -93,7 +93,6 @@ class Chef
if config[:config_file].nil?
Chef::Log.warn("No config file found or specified on command line, using command line options.")
elsif config_fetcher.config_missing?
- pp config_missing: true
Chef::Log.warn("*****************************************")
Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
Chef::Log.warn("*****************************************")
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index bd19afa914..409680b553 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -279,6 +279,12 @@ class Chef::Application::Client < Chef::Application
Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+ if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil?
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Log.warn "chef_repo_path was set in a config file but was empty. Assuming #{Chef::Config.chef_repo_path}"
+ end
+
if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
index a4f84ed7eb..d952d8a249 100644
--- a/lib/chef/audit/audit_reporter.rb
+++ b/lib/chef/audit/audit_reporter.rb
@@ -34,6 +34,7 @@ class Chef
@rest_client = rest_client
# Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted."
@ordered_control_groups = Hash.new
+ @audit_phase_error = nil
end
def run_context
@@ -46,7 +47,7 @@ class Chef
@run_status = run_status
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
Chef::Log.debug("Audit Reporter completed successfully without errors.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -57,8 +58,9 @@ class Chef
# that runs tests - normal errors are interpreted as EXAMPLE failures and captured.
# We still want to send available audit information to the server so we process the
# known control groups.
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
# The stacktrace information has already been logged elsewhere
+ @audit_phase_error = error
Chef::Log.debug("Audit Reporter failed.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -70,7 +72,9 @@ class Chef
end
def run_failed(error)
- post_auditing_data(error)
+ # Audit phase errors are captured when audit_phase_failed gets called.
+ # The error passed here isn't relevant to auditing, so we ignore it.
+ post_auditing_data
end
def control_group_started(name)
@@ -98,7 +102,7 @@ class Chef
private
- def post_auditing_data(error = nil)
+ def post_auditing_data
unless auditing_enabled?
Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
return
@@ -116,8 +120,10 @@ class Chef
Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})")
run_data = audit_data.to_hash
- if error
- run_data[:error] = "#{error.class.to_s}: #{error.message}\n#{error.backtrace.join("\n")}"
+ if @audit_phase_error
+ error_info = "#{@audit_phase_error.class}: #{@audit_phase_error.message}"
+ error_info << "\n#{@audit_phase_error.backtrace.join("\n")}" if @audit_phase_error.backtrace
+ run_data[:error] = error_info
end
Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}"
@@ -163,7 +169,6 @@ class Chef
def iso8601ify(time)
time.utc.iso8601.to_s
end
-
end
end
end
diff --git a/lib/chef/audit/logger.rb b/lib/chef/audit/logger.rb
new file mode 100644
index 0000000000..e46f54e582
--- /dev/null
+++ b/lib/chef/audit/logger.rb
@@ -0,0 +1,36 @@
+#
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'stringio'
+
+class Chef
+ class Audit
+ class Logger
+ def self.puts(message="")
+ @buffer ||= StringIO.new
+ @buffer.puts(message)
+
+ Chef::Log.info(message)
+ end
+
+ def self.read_buffer
+ return "" if @buffer.nil?
+ @buffer.string
+ end
+ end
+ end
+end
diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb
index 801bf5ee58..234d83ab8f 100644
--- a/lib/chef/audit/runner.rb
+++ b/lib/chef/audit/runner.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/audit/logger'
+
class Chef
class Audit
class Runner
@@ -115,8 +117,8 @@ class Chef
# the output stream to be changed for a formatter once the formatter has
# been added.
def set_streams
- RSpec.configuration.output_stream = Chef::Config[:log_location]
- RSpec.configuration.error_stream = Chef::Config[:log_location]
+ RSpec.configuration.output_stream = Chef::Audit::Logger
+ RSpec.configuration.error_stream = Chef::Audit::Logger
end
# Add formatters which we use to
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index d3f7ee55c7..f1dd797c04 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -26,6 +26,9 @@
# injected" into this class by other objects and do not reference the class symbols in those files
# directly and we do not need to require those files here.
+require 'chef/platform/provider_priority_map'
+require 'chef/platform/resource_priority_map'
+
class Chef
class << self
@@ -33,50 +36,74 @@ class Chef
# Public API
#
+ #
# Get the node object
#
# @return [Chef::Node] node object of the chef-client run
+ #
attr_reader :node
+ #
# Get the run context
#
# @return [Chef::RunContext] run_context of the chef-client run
+ #
attr_reader :run_context
+ #
# 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
+ #
# @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
+ #
def get_provider_priority_array(resource_name)
- @provider_priority_map.get_priority_array(node, resource_name).dup
+ result = provider_priority_map.get_priority_array(node, resource_name)
+ result = result.dup if result
+ result
end
+ #
# Get the array of resources associated with a resource_name for the current node
#
# @param resource_name [Symbol] name of the resource as a symbol
+ #
# @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
+ #
def get_resource_priority_array(resource_name)
- @resource_priority_map.get_priority_array(node, resource_name).dup
+ result = resource_priority_map.get_priority_array(node, resource_name)
+ result = result.dup if result
+ result
end
+ #
# Set the array of providers associated with a resource_name for the current node
#
# @param resource_name [Symbol] name of the resource as a symbol
- # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
# @param filter [Hash] Chef::Nodearray-style filter
+ #
# @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)
- @provider_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ #
+ 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 = result.dup if result
+ result
end
+ #
# Get the array of resources associated with a resource_name for the current node
#
# @param resource_name [Symbol] name of the resource as a symbol
- # @param priority_array [Array<Class>] Array of Classes to set as the priority for resource_name on the node
+ # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
# @param filter [Hash] Chef::Nodearray-style filter
+ #
# @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)
- @resource_priority_map.set_priority_array(resource_name, priority_array, *filter).dup
+ #
+ 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 = result.dup if result
+ result
end
#
@@ -85,22 +112,27 @@ class Chef
# *NOT* for public consumption ]
#
+ #
# Sets the resource_priority_map
#
- # @api private
# @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
+ #
+ # @api private
def set_resource_priority_map(resource_priority_map)
@resource_priority_map = resource_priority_map
end
+ #
# Sets the provider_priority_map
#
- # @api private
# @param provider_priority_map [Chef::Platform::providerPriorityMap]
+ #
+ # @api private
def set_provider_priority_map(provider_priority_map)
@provider_priority_map = provider_priority_map
end
+ #
# Sets the node object
#
# @api private
@@ -109,14 +141,17 @@ class Chef
@node = node
end
+ #
# Sets the run_context object
#
- # @api private
# @param run_context [Chef::RunContext]
+ #
+ # @api private
def set_run_context(run_context)
@run_context = run_context
end
+ #
# Resets the internal state
#
# @api private
@@ -126,5 +161,21 @@ class Chef
@provider_priority_map = nil
@resource_priority_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
+ end
+ # @api private
+ def resource_priority_map
+ @resource_priority_map ||= begin
+ Chef::Platform::ResourcePriorityMap.instance
+ end
+ end
end
+
+ reset!
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index d04a3dbbd5..86e92585e3 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -50,6 +50,7 @@ require 'chef/run_lock'
require 'chef/policy_builder'
require 'chef/request_id'
require 'chef/platform/rebooter'
+require 'chef/mixin/deprecation'
require 'ohai'
require 'rbconfig'
@@ -60,121 +61,273 @@ class Chef
class Client
include Chef::Mixin::PathSanity
- # IO stream that will be used as 'STDOUT' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDOUT_FD = STDOUT
-
- # IO stream that will be used as 'STDERR' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDERR_FD = STDERR
+ extend Chef::Mixin::Deprecation
- # Clears all notifications for client run status events.
- # Primarily for testing purposes.
- def self.clear_notifications
- @run_start_notifications = nil
- @run_completed_successfully_notifications = nil
- @run_failed_notifications = nil
- end
-
- # The list of notifications to be run when the client run starts.
- def self.run_start_notifications
- @run_start_notifications ||= []
- end
-
- # The list of notifications to be run when the client run completes
- # successfully.
- def self.run_completed_successfully_notifications
- @run_completed_successfully_notifications ||= []
- end
-
- # The list of notifications to be run when the client run fails.
- def self.run_failed_notifications
- @run_failed_notifications ||= []
- end
-
- # Add a notification for the 'client run started' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_starts(&notification_block)
- run_start_notifications << notification_block
- end
-
- # Add a notification for the 'client run success' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_completes_successfully(&notification_block)
- run_completed_successfully_notifications << notification_block
- end
+ #
+ # The status of the Chef run.
+ #
+ # @return [Chef::RunStatus]
+ #
+ attr_reader :run_status
- # Add a notification for the 'client run failed' event. The notification
- # is provided as a block. The current Chef::RunStatus is passed to the
- # notification_block when the event is triggered.
- def self.when_run_fails(&notification_block)
- run_failed_notifications << notification_block
+ #
+ # The node represented by this client.
+ #
+ # @return [Chef::Node]
+ #
+ def node
+ run_status.node
end
-
- # Callback to fire notifications that the Chef run is starting
- def run_started
- self.class.run_start_notifications.each do |notification|
- notification.call(run_status)
- end
- @events.run_started(run_status)
+ def node=(value)
+ run_status.node = value
end
- # Callback to fire notifications that the run completed successfully
- def run_completed_successfully
- success_handlers = self.class.run_completed_successfully_notifications
- success_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The ohai system used by this client.
+ #
+ # @return [Ohai::System]
+ #
+ attr_reader :ohai
- # Callback to fire notifications that the Chef run failed
- def run_failed
- failure_handlers = self.class.run_failed_notifications
- failure_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The rest object used to communicate with the Chef server.
+ #
+ # @return [Chef::REST]
+ #
+ attr_reader :rest
- attr_accessor :node
- attr_accessor :ohai
- attr_accessor :rest
+ #
+ # The runner used to converge.
+ #
+ # @return [Chef::Runner]
+ #
attr_accessor :runner
+ #
+ # Extra node attributes that were applied to the node.
+ #
+ # @return [Hash]
+ #
attr_reader :json_attribs
- attr_reader :run_status
+
+ #
+ # The event dispatcher for the Chef run, including any configured output
+ # formatters and event loggers.
+ #
+ # @return [EventDispatch::Dispatcher]
+ #
+ # @see Chef::Formatters
+ # @see Chef::Config#formatters
+ # @see Chef::Config#stdout
+ # @see Chef::Config#stderr
+ # @see Chef::Config#force_logger
+ # @see Chef::Config#force_formatter
+ # TODO add stdout, stderr, and default formatters to Chef::Config so the
+ # defaults aren't calculated here. Remove force_logger and force_formatter
+ # from this code.
+ # @see Chef::EventLoggers
+ # @see Chef::Config#disable_event_logger
+ # @see Chef::Config#event_loggers
+ # @see Chef::Config#event_handlers
+ #
attr_reader :events
+ #
# Creates a new Chef::Client.
+ #
+ # @param json_attribs [Hash] Node attributes to layer into the node when it is
+ # fetched.
+ # @param args [Hash] Options:
+ # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to
+ # use instead of the node's embedded run list.
+ # @option args [Array<String>] :specific_recipes A list of recipe file paths
+ # to load after the run list has been loaded.
+ #
def initialize(json_attribs=nil, args={})
@json_attribs = json_attribs || {}
- @node = nil
- @run_status = nil
- @runner = nil
@ohai = Ohai::System.new
event_handlers = configure_formatters + configure_event_loggers
event_handlers += Array(Chef::Config[:event_handlers])
@events = EventDispatch::Dispatcher.new(*event_handlers)
+ # TODO it seems like a bad idea to be deletin' other peoples' hashes.
@override_runlist = args.delete(:override_runlist)
@specific_recipes = args.delete(:specific_recipes)
+ @run_status = Chef::RunStatus.new(nil, events)
if new_runlist = args.delete(:runlist)
@json_attribs["run_list"] = new_runlist
end
+ end
- # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
- require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap
- require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap
+ #
+ # Do a full run for this Chef::Client.
+ #
+ # Locks the run while doing its job.
+ #
+ # Fires run_start before doing anything and fires run_completed or
+ # run_failed when finished. Also notifies client listeners of run_started
+ # at the beginning of Compile, and run_completed_successfully or run_failed
+ # when all is complete.
+ #
+ # Phase 1: Setup
+ # --------------
+ # Gets information about the system and the run we are doing.
+ #
+ # 1. Run ohai to collect system information.
+ # 2. Register / connect to the Chef server (unless in solo mode).
+ # 3. Retrieve the node (or create a new one).
+ # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list.
+ #
+ # @see #run_ohai
+ # @see #load_node
+ # @see #build_node
+ # @see Chef::Config#lockfile
+ # @see Chef::RunLock#acquire
+ #
+ # Phase 2: Compile
+ # ----------------
+ # Decides *what* we plan to converge by compiling recipes.
+ #
+ # 1. Sync required cookbooks to the local cache.
+ # 2. Load libraries from all cookbooks.
+ # 3. Load attributes from all cookbooks.
+ # 4. Load LWRPs from all cookbooks.
+ # 5. Load resource definitions from all cookbooks.
+ # 6. Load recipes in the run list.
+ # 7. Load recipes from the command line.
+ #
+ # @see #setup_run_context Syncs and compiles cookbooks.
+ # @see Chef::CookbookCompiler#compile
+ #
+ # Phase 3: Converge
+ # -----------------
+ # Brings the system up to date.
+ #
+ # 1. Converge the resources built from recipes in Phase 2.
+ # 2. Save the node.
+ # 3. Reboot if we were asked to.
+ #
+ # @see #converge_and_save
+ # @see Chef::Runner
+ #
+ # Phase 4: Audit
+ # --------------
+ # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config.
+ #
+ # 1. 'control_group' DSL collects audits during Phase 2
+ # 2. Audits are run using RSpec
+ # 3. Errors are collected and reported using the formatters
+ #
+ # @see #run_audits
+ # @see Chef::Audit::Runner#run
+ #
+ # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed.
+ #
+ # @see Chef::Config#enforce_path_sanity
+ # @see Chef::Config#solo
+ # @see Chef::Config#audit_mode
+ #
+ # @return Always returns true.
+ #
+ def run
+ run_error = nil
- Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
- Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
+ runlock = RunLock.new(Chef::Config.lockfile)
+ # TODO feels like acquire should have its own block arg for this
+ runlock.acquire
+ # don't add code that may fail before entering this section to be sure to release lock
+ begin
+ runlock.save_pid
+
+ request_id = Chef::RequestID.instance.request_id
+ run_context = nil
+ events.run_start(Chef::VERSION)
+ Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+ Chef::Log.info "Chef-client pid: #{Process.pid}"
+ Chef::Log.debug("Chef-client request_id: #{request_id}")
+ enforce_path_sanity
+ run_ohai
+
+ register unless Chef::Config[:solo]
+
+ load_node
+
+ build_node
+
+ run_status.run_id = request_id
+ run_status.start_clock
+ Chef::Log.info("Starting Chef Run for #{node.name}")
+ run_started
+
+ do_windows_admin_check
+
+ run_context = setup_run_context
+
+ if Chef::Config[:audit_mode] != :audit_only
+ converge_error = converge_and_save(run_context)
+ end
+
+ if Chef::Config[:why_run] == true
+ # why_run should probably be renamed to why_converge
+ Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
+ elsif Chef::Config[:audit_mode] != :disabled
+ audit_error = run_audits(run_context)
+ end
+
+ # Raise converge_error so run_failed reporters/events are processed.
+ raise converge_error if converge_error
+
+ run_status.stop_clock
+ Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+ run_completed_successfully
+ events.run_completed(node)
+
+ # rebooting has to be the last thing we do, no exceptions.
+ Chef::Platform::Rebooter.reboot_if_needed!(node)
+ rescue Exception => run_error
+ # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
+ Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}")
+ # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
+ if run_status
+ run_status.stop_clock
+ run_status.exception = run_error
+ run_failed
+ end
+ events.run_failed(run_error)
+ ensure
+ Chef::RequestID.instance.reset_request_id
+ request_id = nil
+ @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
+ Chef::Application.debug_stacktrace(error)
+ raise error
+ end
+
+ true
end
+ #
+ # Private API
+ # TODO make this stuff protected or private
+ #
+
+ # @api private
def configure_formatters
formatters_for_run.map do |formatter_name, output_path|
if output_path.nil?
@@ -187,6 +340,7 @@ class Chef
end
end
+ # @api private
def formatters_for_run
if Chef::Config.formatters.empty?
[default_formatter]
@@ -195,6 +349,7 @@ class Chef
end
end
+ # @api private
def default_formatter
if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
[:doc]
@@ -203,6 +358,7 @@ class Chef
end
end
+ # @api private
def configure_event_loggers
if Chef::Config.disable_event_logger
[]
@@ -219,8 +375,9 @@ class Chef
end
end
- # Resource repoters send event information back to the chef server for processing.
- # Can only be called after we have a @rest object
+ # Resource reporters send event information back to the chef server for
+ # processing. Can only be called after we have a @rest object
+ # @api private
def register_reporters
[
Chef::ResourceReporter.new(rest),
@@ -230,43 +387,123 @@ class Chef
end
end
+ #
+ # Callback to fire notifications that the Chef run is starting
+ #
+ # @api private
+ #
+ def run_started
+ self.class.run_start_notifications.each do |notification|
+ notification.call(run_status)
+ end
+ events.run_started(run_status)
+ end
+
+ #
+ # Callback to fire notifications that the run completed successfully
+ #
+ # @api private
+ #
+ def run_completed_successfully
+ success_handlers = self.class.run_completed_successfully_notifications
+ success_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
+ # Callback to fire notifications that the Chef run failed
+ #
+ # @api private
+ #
+ def run_failed
+ failure_handlers = self.class.run_failed_notifications
+ failure_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
# Instantiates a Chef::Node object, possibly loading the node's prior state
- # when using chef-client. Delegates to policy_builder. Injects the built node
- # into the Chef class.
+ # when using chef-client. Sets Chef.node to the new node.
#
# @return [Chef::Node] The node object for this Chef run
+ #
+ # @see Chef::PolicyBuilder#load_node
+ #
+ # @api private
+ #
def load_node
policy_builder.load_node
- @node = policy_builder.node
- Chef.set_node(@node)
+ run_status.node = policy_builder.node
+ Chef.set_node(policy_builder.node)
node
end
- # Mutates the `node` object to prepare it for the chef run. Delegates to
- # policy_builder
+ #
+ # Mutates the `node` object to prepare it for the chef run.
#
# @return [Chef::Node] The updated node object
+ #
+ # @see Chef::PolicyBuilder#build_node
+ #
+ # @api private
+ #
def build_node
policy_builder.build_node
- @run_status = Chef::RunStatus.new(node, events)
+ run_status.node = node
node
end
+ #
+ # Sync cookbooks to local cache.
+ #
+ # TODO this appears to be unused.
+ #
+ # @see Chef::PolicyBuilder#sync_cookbooks
+ #
+ # @api private
+ #
+ def sync_cookbooks
+ policy_builder.sync_cookbooks
+ end
+
+ #
+ # Sets up the run context.
+ #
+ # @see Chef::PolicyBuilder#setup_run_context
+ #
+ # @return The newly set up run context
+ #
+ # @api private
def setup_run_context
- run_context = policy_builder.setup_run_context(@specific_recipes)
+ run_context = policy_builder.setup_run_context(specific_recipes)
assert_cookbook_path_not_empty(run_context)
run_status.run_context = run_context
run_context
end
- def sync_cookbooks
- policy_builder.sync_cookbooks
- end
-
+ #
+ # The PolicyBuilder strategy for figuring out run list and cookbooks.
+ #
+ # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject]
+ #
+ # @api private
+ #
def policy_builder
- @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events)
+ @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, override_runlist, events)
end
+ #
+ # Save the updated node to Chef.
+ #
+ # Does not save if we are in solo mode or using override_runlist.
+ #
+ # @see Chef::Node#save
+ # @see Chef::Config#solo
+ #
+ # @api private
+ #
def save_updated_node
if Chef::Config[:solo]
# nothing to do
@@ -274,16 +511,46 @@ class Chef
Chef::Log.warn("Skipping final node save because override_runlist was given")
else
Chef::Log.debug("Saving the current state of node #{node_name}")
- @node.save
+ node.save
end
end
+ #
+ # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified.
+ #
+ # Sends the ohai_completed event when finished.
+ #
+ # @see Chef::EventDispatcher#
+ # @see Chef::Config#minimal_ohai
+ #
+ # @api private
+ #
def run_ohai
filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
ohai.all_plugins(filter)
- @events.ohai_completed(node)
+ events.ohai_completed(node)
end
+ #
+ # Figure out the node name we are working with.
+ #
+ # It tries these, in order:
+ # - Chef::Config.node_name
+ # - ohai[:fqdn]
+ # - ohai[:machinename]
+ # - ohai[:hostname]
+ #
+ # If we are running against a server with authentication protocol < 1.0, we
+ # *require* authentication protocol version 1.1.
+ #
+ # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not
+ # set and cannot be determined via ohai.
+ #
+ # @see Chef::Config#node_name
+ # @see Chef::Config#authentication_protocol_version
+ #
+ # @api private
+ #
def node_name
name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
Chef::Config[:node_name] = name
@@ -292,6 +559,8 @@ class Chef
# node names > 90 bytes only work with authentication protocol >= 1.1
# see discussion in config.rb.
+ # TODO use a computed default in Chef::Config to determine this instead of
+ # setting it.
if name.bytesize > 90
Chef::Config[:authentication_protocol_version] = "1.1"
end
@@ -300,46 +569,86 @@ class Chef
end
#
- # === Returns
- # rest<Chef::REST>:: returns Chef::REST connection object
+ # Determine our private key and set up the connection to the Chef server.
+ #
+ # Skips registration and fires the `skipping_registration` event if
+ # Chef::Config.client_key is unspecified or already exists.
+ #
+ # If Chef::Config.client_key does not exist, we register the client with the
+ # Chef server and fire the registration_start and registration_completed events.
+ #
+ # @return [Chef::REST] The server connection object.
+ #
+ # @see Chef::Config#chef_server_url
+ # @see Chef::Config#client_key
+ # @see Chef::ApiClient::Registration#run
+ # @see Chef::EventDispatcher#skipping_registration
+ # @see Chef::EventDispatcher#registration_start
+ # @see Chef::EventDispatcher#registration_completed
+ # @see Chef::EventDispatcher#registration_failed
+ #
+ # @api private
+ #
def register(client_name=node_name, config=Chef::Config)
if !config[:client_key]
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key is unspecified - skipping registration")
elsif File.exists?(config[:client_key])
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
else
- @events.registration_start(node_name, config)
+ events.registration_start(node_name, config)
Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
- @events.registration_completed
+ events.registration_completed
end
# We now have the client key, and should use it from now on.
@rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
register_reporters
rescue Exception => e
+ # TODO this should probably only ever fire if we *started* registration.
+ # Move it to the block above.
# TODO: munge exception so a semantic failure message can be given to the
# user
- @events.registration_failed(client_name, e, config)
+ events.registration_failed(client_name, e, config)
raise
end
- # Converges the node.
#
- # === Returns
- # The thrown exception, if there was one. If this returns nil the converge was successful.
+ # Converges all compiled resources.
+ #
+ # Fires the converge_start, converge_complete and converge_failed events.
+ #
+ # If the exception `:end_client_run_early` is thrown during convergence, it
+ # does not mark the run complete *or* failed, and returns `nil`
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge exception, unless we are in audit mode, in which case
+ # we *return* the exception.
+ #
+ # @see Chef::Runner#converge
+ # @see Chef::Config#audit_mode
+ # @see Chef::EventDispatch#converge_start
+ # @see Chef::EventDispatch#converge_complete
+ # @see Chef::EventDispatch#converge_failed
+ #
+ # @api private
+ #
def converge(run_context)
converge_exception = nil
catch(:end_client_run_early) do
begin
- @events.converge_start(run_context)
+ events.converge_start(run_context)
Chef::Log.debug("Converging node #{node_name}")
@runner = Chef::Runner.new(run_context)
- runner.converge
- @events.converge_complete
+ @runner.converge
+ events.converge_complete
rescue Exception => e
- @events.converge_failed(e)
+ events.converge_failed(e)
raise e if Chef::Config[:audit_mode] == :disabled
converge_exception = e
end
@@ -347,8 +656,28 @@ class Chef
converge_exception
end
+ #
+ # Converge the node via and then save it if successful.
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge or node save exception, unless we are in audit mode,
+ # in which case we *return* the exception.
+ #
+ # @see #converge
+ # @see #save_updated_mode
+ # @see Chef::Config#audit_mode
+ #
+ # @api private
+ #
# We don't want to change the old API on the `converge` method to have it perform
# saving. So we wrap it in this method.
+ # TODO given this seems to be pretty internal stuff, how badly do we need to
+ # split this stuff up?
+ #
def converge_and_save(run_context)
converge_exception = converge(run_context)
unless converge_exception
@@ -362,37 +691,67 @@ class Chef
converge_exception
end
+ #
+ # Run the audit phase.
+ #
+ # Triggers the audit_phase_start, audit_phase_complete and
+ # audit_phase_failed events.
+ #
+ # @param run_context The run context.
+ #
+ # @return Any thrown exceptions. `nil` if successful.
+ #
+ # @see Chef::Audit::Runner#run
+ # @see Chef::EventDispatch#audit_phase_start
+ # @see Chef::EventDispatch#audit_phase_complete
+ # @see Chef::EventDispatch#audit_phase_failed
+ #
+ # @api private
+ #
def run_audits(run_context)
- audit_exception = nil
begin
- @events.audit_phase_start(run_status)
+ events.audit_phase_start(run_status)
Chef::Log.info("Starting audit phase")
auditor = Chef::Audit::Runner.new(run_context)
auditor.run
if auditor.failed?
- raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ @events.audit_phase_failed(audit_exception, Chef::Audit::Logger.read_buffer)
+ else
+ @events.audit_phase_complete(Chef::Audit::Logger.read_buffer)
end
- @events.audit_phase_complete
rescue Exception => e
Chef::Log.error("Audit phase failed with error message: #{e.message}")
- @events.audit_phase_failed(e)
+ @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer)
audit_exception = e
end
audit_exception
end
- # Expands the run list. Delegates to the policy_builder.
#
- # Normally this does not need to be called from here, it will be called by
- # build_node. This is provided so external users (like the chefspec
- # project) can inject custom behavior into the run process.
+ # Expands the run list.
+ #
+ # @return [Chef::RunListExpansion] The expanded run list.
+ #
+ # @see Chef::PolicyBuilder#expand_run_list
#
- # === Returns
- # RunListExpansion: A RunListExpansion or API compatible object.
def expanded_run_list
policy_builder.expand_run_list
end
+ #
+ # Check if the user has Administrator privileges on windows.
+ #
+ # Throws an error if the user is not an admin, and
+ # `Chef::Config.fatal_windows_admin_check` is true.
+ #
+ # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin.
+ #
+ # @see Chef::platform#windows?
+ # @see Chef::Config#fatal_windows_admin_check
+ #
+ # @api private
+ #
def do_windows_admin_check
if Chef::Platform.windows?
Chef::Log.debug("Checking for administrator privileges....")
@@ -412,99 +771,121 @@ class Chef
end
end
- # Do a full run for this Chef::Client. Calls:
- #
- # * run_ohai - Collect information about the system
- # * build_node - Get the last known state, merge with local changes
- # * register - If not in solo mode, make sure the server knows about this client
- # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks
- # * converge - Bring this system up to date
- #
- # === Returns
- # true:: Always returns true.
- def run
- runlock = RunLock.new(Chef::Config.lockfile)
- runlock.acquire
- # don't add code that may fail before entering this section to be sure to release lock
- begin
- runlock.save_pid
-
- request_id = Chef::RequestID.instance.request_id
- run_context = nil
- @events.run_start(Chef::VERSION)
- Chef::Log.info("*** Chef #{Chef::VERSION} ***")
- Chef::Log.info "Chef-client pid: #{Process.pid}"
- Chef::Log.debug("Chef-client request_id: #{request_id}")
- enforce_path_sanity
- run_ohai
-
- register unless Chef::Config[:solo]
-
- load_node
-
- build_node
+ # Notification registration
+ class<<self
+ #
+ # Add a listener for the 'client run started' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_starts(&notification_block)
+ run_start_notifications << notification_block
+ end
- run_status.run_id = request_id
- run_status.start_clock
- Chef::Log.info("Starting Chef Run for #{node.name}")
- run_started
+ #
+ # Add a listener for the 'client run success' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_completes_successfully(&notification_block)
+ run_completed_successfully_notifications << notification_block
+ end
- do_windows_admin_check
+ #
+ # Add a listener for the 'client run failed' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_fails(&notification_block)
+ run_failed_notifications << notification_block
+ end
- run_context = setup_run_context
+ #
+ # Clears all listeners for client run status events.
+ #
+ # Primarily for testing purposes.
+ #
+ # @api private
+ #
+ def clear_notifications
+ @run_start_notifications = nil
+ @run_completed_successfully_notifications = nil
+ @run_failed_notifications = nil
+ end
- if Chef::Config[:audit_mode] != :audit_only
- converge_error = converge_and_save(run_context)
- end
+ #
+ # TODO These seem protected to me.
+ #
+
+ #
+ # Listeners to be run when the client run starts.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_start_notifications
+ @run_start_notifications ||= []
+ end
- if Chef::Config[:why_run] == true
- # why_run should probably be renamed to why_converge
- Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
- elsif Chef::Config[:audit_mode] != :disabled
- audit_error = run_audits(run_context)
- end
+ #
+ # Listeners to be run when the client run completes successfully.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_completed_successfully_notifications
+ @run_completed_successfully_notifications ||= []
+ end
- if converge_error || audit_error
- e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- e.fill_backtrace
- raise e
- end
+ #
+ # Listeners to be run when the client run fails.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_failed_notifications
+ @run_failed_notifications ||= []
+ end
+ end
- run_status.stop_clock
- Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
- run_completed_successfully
- @events.run_completed(node)
+ #
+ # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDOUT_FD = STDOUT
- # rebooting has to be the last thing we do, no exceptions.
- Chef::Platform::Rebooter.reboot_if_needed!(node)
+ #
+ # IO stream that will be used as 'STDERR' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDERR_FD = STDERR
- true
+ #
+ # Deprecated writers
+ #
- rescue Exception => e
- # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
- Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}")
- # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
- if run_status
- run_status.stop_clock
- run_status.exception = e
- run_failed
- end
- Chef::Application.debug_stacktrace(e)
- @events.run_failed(e)
- raise
- ensure
- Chef::RequestID.instance.reset_request_id
- request_id = nil
- @run_status = nil
- run_context = nil
- runlock.release
- GC.start
- end
- true
- end
+ include Chef::Mixin::Deprecation
+ deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
+ deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
+ deprecated_attr :runner, "There is no alternative. Leave runner alone!"
private
+ attr_reader :override_runlist
+ attr_reader :specific_recipes
+
def empty_directory?(path)
!File.exists?(path) || (Dir.entries(path).size <= 2)
end
@@ -536,7 +917,6 @@ class Chef
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
-
end
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index d2d3c736c2..9beb18b53e 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -20,739 +20,34 @@
# limitations under the License.
require 'chef/log'
-require 'chef/exceptions'
-require 'mixlib/config'
-require 'chef/util/selinux'
-require 'chef/util/path_helper'
-require 'pathname'
-require 'chef/mixin/shell_out'
+require 'chef-config/logger'
-class Chef
- class Config
-
- extend Mixlib::Config
- extend Chef::Mixin::ShellOut
-
- PathHelper = Chef::Util::PathHelper
-
- # Evaluates the given string as config.
- #
- # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
- def self.from_string(string, filename)
- self.instance_eval(string, filename, 1)
- end
-
- # Manages the chef secret session key
- # === Returns
- # <newkey>:: A new or retrieved session key
- #
- def self.manage_secret_key
- newkey = nil
- if Chef::FileCache.has_key?("chef_server_cookie_id")
- newkey = Chef::FileCache.load("chef_server_cookie_id")
- else
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- newkey = ""
- 40.times { |i| newkey << chars[rand(chars.size-1)] }
- Chef::FileCache.store("chef_server_cookie_id", newkey)
- end
- newkey
- end
+# DI our logger into ChefConfig before we load the config. Some defaults are
+# auto-detected, and this emits log messages on some systems, all of which will
+# occur at require-time. So we need to set the logger first.
+ChefConfig.logger = Chef::Log
- def self.inspect
- configuration.inspect
- end
+require 'chef-config/config'
- def self.platform_specific_path(path)
- path = PathHelper.cleanpath(path)
- if Chef::Platform.windows?
- # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
- if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
- path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
- end
- end
- path
- end
-
- def self.add_formatter(name, file_path=nil)
- formatters << [name, file_path]
- end
+require 'chef/platform/query_helpers'
- def self.add_event_logger(logger)
- event_handlers << logger
- end
-
- # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.)
- configurable(:config_file)
-
- default(:config_dir) do
- if config_file
- PathHelper.dirname(config_file)
- else
- PathHelper.join(user_home, ".chef", "")
- end
- end
-
- default :formatters, []
-
- # Override the config dispatch to set the value of multiple server options simultaneously
- #
- # === Parameters
- # url<String>:: String to be set for all of the chef-server-api URL's
- #
- configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
-
- # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
- # So while this is basically identical to what method_missing would do, we pull
- # it up here and get a real method written so that things get dispatched
- # properly.
- configurable(:daemonize).writes_value { |v| v }
-
- # The root where all local chef object data is stored. cookbooks, data bags,
- # environments are all assumed to be in separate directories under this.
- # chef-solo uses these directories for input data. knife commands
- # that upload or download files (such as knife upload, knife role from file,
- # etc.) work.
- default :chef_repo_path do
- if self.configuration[:cookbook_path]
- if self.configuration[:cookbook_path].kind_of?(String)
- File.expand_path('..', self.configuration[:cookbook_path])
- else
- self.configuration[:cookbook_path].map do |path|
- File.expand_path('..', path)
- end
- end
- else
- cache_path
- end
- end
-
- def self.find_chef_repo_path(cwd)
- # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
- # This allows us to run config-free.
- path = cwd
- until File.directory?(PathHelper.join(path, "cookbooks"))
- new_path = File.expand_path('..', path)
- if new_path == path
- Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.")
- return Dir.pwd
- end
- path = new_path
- end
- Chef::Log.info("Auto-discovered chef repository at #{path}")
- path
- end
-
- def self.derive_path_from_chef_repo_path(child_path)
- if chef_repo_path.kind_of?(String)
- PathHelper.join(chef_repo_path, child_path)
- else
- chef_repo_path.map { |path| PathHelper.join(path, child_path)}
- end
- end
-
- # Location of acls on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- # Only applies to Enterprise Chef commands.
- default(:acl_path) { derive_path_from_chef_repo_path('acls') }
-
- # Location of clients on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- default(:client_path) { derive_path_from_chef_repo_path('clients') }
-
- # Location of cookbooks on disk. String or array of strings.
- # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path
- # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]).
- default(:cookbook_path) do
- if self.configuration[:chef_repo_path]
- derive_path_from_chef_repo_path('cookbooks')
- else
- Array(derive_path_from_chef_repo_path('cookbooks')).flatten +
- Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten
- end
- end
-
- # Location of containers on disk. String or array of strings.
- # Defaults to <chef_repo_path>/containers.
- # Only applies to Enterprise Chef commands.
- default(:container_path) { derive_path_from_chef_repo_path('containers') }
-
- # Location of data bags on disk. String or array of strings.
- # Defaults to <chef_repo_path>/data_bags.
- default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') }
-
- # Location of environments on disk. String or array of strings.
- # Defaults to <chef_repo_path>/environments.
- default(:environment_path) { derive_path_from_chef_repo_path('environments') }
-
- # Location of groups on disk. String or array of strings.
- # Defaults to <chef_repo_path>/groups.
- # Only applies to Enterprise Chef commands.
- default(:group_path) { derive_path_from_chef_repo_path('groups') }
-
- # Location of nodes on disk. String or array of strings.
- # Defaults to <chef_repo_path>/nodes.
- default(:node_path) { derive_path_from_chef_repo_path('nodes') }
-
- # Location of roles on disk. String or array of strings.
- # Defaults to <chef_repo_path>/roles.
- default(:role_path) { derive_path_from_chef_repo_path('roles') }
-
- # Location of users on disk. String or array of strings.
- # Defaults to <chef_repo_path>/users.
- # Does not apply to Enterprise Chef commands.
- default(:user_path) { derive_path_from_chef_repo_path('users') }
-
- # Location of policies on disk. String or array of strings.
- # Defaults to <chef_repo_path>/policies.
- default(:policy_path) { derive_path_from_chef_repo_path('policies') }
-
- # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
- default :enforce_path_sanity, true
-
- # Formatted Chef Client output is a beta feature, disabled by default:
- default :formatter, "null"
-
- # The number of times the client should retry when registering with the server
- default :client_registration_retries, 5
-
- # An array of paths to search for knife exec scripts if they aren't in the current directory
- default :script_path, []
-
- # The root of all caches (checksums, cache and backup). If local mode is on,
- # this is under the user's home directory.
- default(:cache_path) do
- if local_mode
- PathHelper.join(config_dir, 'local-mode-cache')
- else
- primary_cache_root = platform_specific_path("/var")
- primary_cache_path = platform_specific_path("/var/chef")
- # Use /var/chef as the cache path only if that folder exists and we can read and write
- # into it, or /var exists and we can read and write into it (we'll create /var/chef later).
- # Otherwise, we'll create .chef under the user's home directory and use that as
- # the cache path.
- unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
- secondary_cache_path = PathHelper.join(user_home, '.chef')
- Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
- secondary_cache_path
- else
- primary_cache_path
- end
- end
- end
-
- # Returns true only if the path exists and is readable and writeable for the user.
- def self.path_accessible?(path)
- File.exists?(path) && File.readable?(path) && File.writable?(path)
- end
-
- # Where cookbook files are stored on the server (by content checksum)
- default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
-
- # Where chef's cache files should be stored
- default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
-
- # Where backups of chef-managed files should go
- default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
-
- # The chef-client (or solo) lockfile.
- #
- # If your `file_cache_path` resides on a NFS (or non-flock()-supporting
- # fs), it's recommended to set this to something like
- # '/tmp/chef-client-running.pid'
- default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
-
- ## Daemonization Settings ##
- # What user should Chef run as?
- default :user, nil
- default :group, nil
- default :umask, 0022
-
- # Valid log_levels are:
- # * :debug
- # * :info
- # * :warn
- # * :fatal
- # These work as you'd expect. There is also a special `:auto` setting.
- # When set to :auto, Chef will auto adjust the log verbosity based on
- # context. When a tty is available (usually because the user is running chef
- # in a console), the log level is set to :warn, and output formatters are
- # used as the primary mode of output. When a tty is not available, the
- # logger is the primary mode of output, and the log level is set to :info
- default :log_level, :auto
-
- # Logging location as either an IO stream or string representing log file path
- default :log_location, STDOUT
-
- # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
- default :force_formatter, false
-
- # Using `force_logger` causes chef to default to logger output when STDOUT is a tty
- default :force_logger, false
-
- default :http_retry_count, 5
- default :http_retry_delay, 5
- default :interval, nil
- default :once, nil
- default :json_attribs, nil
- # toggle info level log items that can create a lot of output
- default :verbose_logging, true
- default :node_name, nil
- default :diff_disabled, false
- default :diff_filesize_threshold, 10000000
- default :diff_output_threshold, 1000000
- default :local_mode, false
-
- default :pid_file, nil
-
- # Whether Chef Zero local mode should bind to a port. All internal requests
- # will go through the socketless code path regardless, so the socket is
- # only needed if other processes will connect to the local mode server.
- #
- # For compatibility this is set to true but it will be changed to false in
- # the future.
- default :listen, true
-
- config_context :chef_zero do
- config_strict_mode true
- default(:enabled) { Chef::Config.local_mode }
- default :host, 'localhost'
- default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works
- end
-
- default :chef_server_url, "https://localhost:443"
- default(:chef_server_root) do
- # if the chef_server_url is a path to an organization, aka
- # 'some_url.../organizations/*' then remove the '/organization/*' by default
- if self.configuration[:chef_server_url] =~ /\/organizations\/\S*$/
- self.configuration[:chef_server_url].split('/')[0..-3].join('/')
- elsif self.configuration[:chef_server_url] # default to whatever chef_server_url is
- self.configuration[:chef_server_url]
- else
- "https://localhost:443"
- end
- end
-
- default :rest_timeout, 300
- default :yum_timeout, 900
- default :yum_lock_timeout, 30
- default :solo, false
- default :splay, nil
- default :why_run, false
- default :color, false
- default :client_fork, true
- default :ez, false
- default :enable_reporting, true
- default :enable_reporting_url_fatals, false
- # Possible values for :audit_mode
- # :enabled, :disabled, :audit_only,
- #
- # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature
- # and is disabled by default. When users choose to enable audit-mode,
- # a warning is issued in application/client#reconfigure.
- # This can be removed when audit-mode is enabled by default.
- default :audit_mode, :disabled
-
- # Chef only needs ohai to run the hostname plugin for the most basic
- # functionality. If the rest of the ohai plugins are not needed (like in
- # most of our testing scenarios)
- default :minimal_ohai, false
-
- # Policyfile is an experimental feature where a node gets its run list and
- # cookbook version set from a single document on the server instead of
- # expanding the run list and having the server compute the cookbook version
- # set based on environment constraints.
- #
- # Because this feature is experimental, it is not recommended for
- # production use. Developent/release of this feature may not adhere to
- # semver guidelines.
- default :use_policyfile, false
-
- # Set these to enable SSL authentication / mutual-authentication
- # with the server
-
- # Client side SSL cert/key for mutual auth
- default :ssl_client_cert, nil
- default :ssl_client_key, nil
-
- # Whether or not to verify the SSL cert for all HTTPS requests. When set to
- # :verify_peer (default), all HTTPS requests will be validated regardless of other
- # SSL verification settings. When set to :verify_none no HTTPS requests will
- # be validated.
- default :ssl_verify_mode, :verify_peer
-
- # Whether or not to verify the SSL cert for HTTPS requests to the Chef
- # server API. If set to `true`, the server's cert will be validated
- # regardless of the :ssl_verify_mode setting. This is set to `true` when
- # running in local-mode.
- # NOTE: This is a workaround until verify_peer is enabled by default.
- default(:verify_api_cert) { Chef::Config.local_mode }
-
- # Path to the default CA bundle files.
- default :ssl_ca_path, nil
- default(:ssl_ca_file) do
- if Chef::Platform.windows? and embedded_path = embedded_dir
- cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem")
- cacert_path if File.exist?(cacert_path)
- else
- nil
- end
- end
-
- # A directory that contains additional SSL certificates to trust. Any
- # certificates in this directory will be added to whatever CA bundle ruby
- # is using. Use this to add self-signed certs for your Chef Server or local
- # HTTP file servers.
- default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") }
-
- # Where should chef-solo download recipes from?
- default :recipe_url, nil
-
- # Sets the version of the signed header authentication protocol to use (see
- # the 'mixlib-authorization' project for more detail). Currently, versions
- # 1.0 and 1.1 are available; however, the chef-server must first be
- # upgraded to support version 1.1 before clients can begin using it.
- #
- # Version 1.1 of the protocol is required when using a `node_name` greater
- # than ~90 bytes (~90 ascii characters), so chef-client will automatically
- # switch to using version 1.1 when `node_name` is too large for the 1.0
- # protocol. If you intend to use large node names, ensure that your server
- # supports version 1.1. Automatic detection of large node names means that
- # users will generally not need to manually configure this.
- #
- # In the future, this configuration option may be replaced with an
- # automatic negotiation scheme.
- default :authentication_protocol_version, "1.0"
-
- # This key will be used to sign requests to the Chef server. This location
- # must be writable by Chef during initial setup when generating a client
- # identity on the server.
- #
- # The chef-server will look up the public key for the client using the
- # `node_name` of the client.
- #
- # If chef-zero is enabled, this defaults to nil (no authentication).
- default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") }
-
- # When registering the client, should we allow the client key location to
- # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem
- # If the path of the key goes through a directory like /tmp this should
- # never be set to true or its possibly an easily exploitable security hole.
- default :follow_client_key_symlink, false
-
- # This secret is used to decrypt encrypted data bag items.
- default(:encrypted_data_bag_secret) do
- if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
- platform_specific_path("/etc/chef/encrypted_data_bag_secret")
- else
- nil
- end
- end
-
- # As of Chef 11.0, version "1" is the default encrypted data bag item
- # format. Version "2" is available which adds encrypt-then-mac protection.
- # To maintain compatibility, versions other than 1 must be opt-in.
- #
- # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure.
- # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO)
- default :data_bag_encrypt_version, 1
-
- # When reading data bag items, any supported version is accepted. However,
- # if all encrypted data bags have been generated with the version 2 format,
- # it is recommended to disable support for earlier formats to improve
- # security. For example, the version 2 format is identical to version 1
- # except for the addition of an HMAC, so an attacker with MITM capability
- # could downgrade an encrypted data bag to version 1 as part of an attack.
- default :data_bag_decrypt_minimum_version, 0
-
- # If there is no file in the location given by `client_key`, chef-client
- # will temporarily use the "validator" identity to generate one. If the
- # `client_key` is not present and the `validation_key` is also not present,
- # chef-client will not be able to authenticate to the server.
- #
- # The `validation_key` is never used if the `client_key` exists.
- #
- # If chef-zero is enabled, this defaults to nil (no authentication).
- default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") }
- default :validation_client_name, "chef-validator"
-
- # When creating a new client via the validation_client account, Chef 11
- # servers allow the client to generate a key pair locally and send the
- # public key to the server. This is more secure and helps offload work from
- # the server, enhancing scalability. If enabled and the remote server
- # implements only the Chef 10 API, client registration will not work
- # properly.
- #
- # The default value is `true`. Set to `false` to disable client-side key
- # generation (server generates client keys).
- default(:local_key_generation) { true }
-
- # Zypper package provider gpg checks. Set to true to enable package
- # gpg signature checking. This will be default in the
- # future. Setting to false disables the warnings.
- # Leaving this set to nil or false is a security hazard!
- default :zypper_check_gpg, nil
-
- # Report Handlers
- default :report_handlers, []
+class Chef
+ Config = ChefConfig::Config
- # Event Handlers
- default :event_handlers, []
+ # We re-open ChefConfig::Config to add additional settings. Generally,
+ # everything should go in chef-config so it's shared with whoever uses that.
+ # We make execeptions to that rule when:
+ # * The functionality isn't likely to be useful outside of Chef
+ # * The functionality makes use of a dependency we don't want to add to chef-config
+ class Config
- default :disable_event_loggers, false
default :event_loggers do
evt_loggers = []
- if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+ if ChefConfig.windows? and not Chef::Platform.windows_server_2003?
evt_loggers << :win_evt
end
evt_loggers
end
- # Exception Handlers
- default :exception_handlers, []
-
- # Start handlers
- default :start_handlers, []
-
- # Syntax Check Cache. Knife keeps track of files that is has already syntax
- # checked by storing files in this directory. `syntax_check_cache_path` is
- # the new (and preferred) configuration setting. If not set, knife will
- # fall back to using cache_options[:path], which is deprecated but exists in
- # many client configs generated by pre-Chef-11 bootstrappers.
- default(:syntax_check_cache_path) { cache_options[:path] }
-
- # Deprecated:
- # Move this to the default value of syntax_cache_path when this is removed.
- default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } }
-
- # Whether errors should be raised for deprecation warnings. When set to
- # `false` (the default setting), a warning is emitted but code using
- # deprecated methods/features/etc. should work normally otherwise. When set
- # to `true`, usage of deprecated methods/features will raise a
- # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that
- # deprecated functionality is not used internally by Chef. End users
- # should generally leave this at the default setting (especially in
- # production), but it may be useful when testing cookbooks or other code if
- # the user wishes to aggressively address deprecations.
- default(:treat_deprecation_warnings_as_errors) do
- # Using an environment variable allows this setting to be inherited in
- # tests that spawn new processes.
- ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS")
- end
-
- # knife configuration data
- config_context :knife do
- default :ssh_port, nil
- default :ssh_user, nil
- default :ssh_attribute, nil
- default :ssh_gateway, nil
- default :bootstrap_version, nil
- default :bootstrap_proxy, nil
- default :bootstrap_template, nil
- default :secret, nil
- default :secret_file, nil
- default :identity_file, nil
- default :host_key_verify, nil
- default :forward_agent, nil
- default :sort_status_reverse, nil
- default :hints, {}
- end
-
- def self.set_defaults_for_windows
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
- principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
- default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
- default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-
- default :fatal_windows_admin_check, false
- end
-
- def self.set_defaults_for_nix
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- #
- # user/group cannot start with '-', '+' or '~'
- # user/group cannot contain ':', ',' or non-space-whitespace or null byte
- # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
- # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
- default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- end
-
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- if Chef::Platform.windows?
- set_defaults_for_windows
- else
- set_defaults_for_nix
- end
-
- # This provides a hook which rspec can stub so that we can avoid twiddling
- # global state in tests.
- def self.env
- ENV
- end
-
- def self.windows_home_path
- Chef::Log.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.")
- PathHelper.home
- end
-
- # returns a platform specific path to the user home dir if set, otherwise default to current directory.
- default( :user_home ) { PathHelper.home || Dir.pwd }
-
- # Enable file permission fixup for selinux. Fixup will be done
- # only if selinux is enabled in the system.
- default :enable_selinux_file_permission_fixup, true
-
- # Use atomic updates (i.e. move operation) while updating contents
- # of the files resources. When set to false copy operation is
- # used to update files.
- default :file_atomic_update, true
-
- # There are 3 possible values for this configuration setting.
- # true => file staging is done in the destination directory
- # false => file staging is done via tempfiles under ENV['TMP']
- # :auto => file staging will try using destination directory if possible and
- # will fall back to ENV['TMP'] if destination directory is not usable.
- default :file_staging_uses_destdir, :auto
-
- # Exit if another run is in progress and the chef-client is unable to
- # get the lock before time expires. If nil, no timeout is enforced. (Exits
- # immediately if 0.)
- default :run_lock_timeout, nil
-
- # Number of worker threads for syncing cookbooks in parallel. Increasing
- # this number can result in gateway errors from the server (namely 503 and 504).
- # If you are seeing this behavior while using the default setting, reducing
- # the number of threads will help.
- default :cookbook_sync_threads, 10
-
- # At the beginning of the Chef Client run, the cookbook manifests are downloaded which
- # contain URLs for every file in every relevant cookbook. Most of the files
- # (recipes, resources, providers, libraries, etc) are immediately synchronized
- # at the start of the run. The handling of "files" and "templates" directories,
- # however, have two modes of operation. They can either all be downloaded immediately
- # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as
- # cookbook_file or template resources are converged which require them (no_lazy_load==false).
- #
- # The advantage of lazily loading these files is that unnecessary files are not
- # synchronized. This may be useful to users with large files checked into cookbooks which
- # are only selectively downloaded to a subset of clients which use the cookbook. However,
- # better solutions are to either isolate large files into individual cookbooks and only
- # include those cookbooks in the run lists of the servers that need them -- or move to
- # using remote_file and a more appropriate backing store like S3 for large file
- # distribution.
- #
- # The disadvantages of lazily loading files are that users some time find it
- # confusing that their cookbooks are not fully synchronzied to the cache initially,
- # and more importantly the time-sensitive URLs which are in the manifest may time
- # out on long Chef runs before the resource that uses the file is converged
- # (leading to many confusing 403 errors on template/cookbook_file resources).
- #
- default :no_lazy_load, true
-
- # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit
- # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to
- # true then the user will get backcompat behavior but with a single nag warning that cookbooks
- # may break with this setting in the future. The false setting is the recommended setting and
- # will become the default.
- default :chef_gem_compile_time, nil
-
- # A whitelisted array of attributes you want sent over the wire when node
- # data is saved.
- # The default setting is nil, which collects all data. Setting to [] will not
- # collect any data for save.
- default :automatic_attribute_whitelist, nil
- default :default_attribute_whitelist, nil
- default :normal_attribute_whitelist, nil
- default :override_attribute_whitelist, nil
-
- config_context :windows_service do
- # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run
- # to finish
- default :watchdog_timeout, 2 * (60 * 60) # 2 hours
- end
-
- # Chef requires an English-language UTF-8 locale to function properly. We attempt
- # to use the 'locale -a' command and search through a list of preferences until we
- # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be
- # able to use that even if there is no English locale on the server, but Mac, Solaris,
- # AIX, etc do not have that locale. We then try to find an English locale and fall
- # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try
- # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
- # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then
- # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding
- # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
- # default rather than drop English.
- #
- # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
- # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
- def self.guess_internal_locale
- # https://github.com/opscode/chef/issues/2181
- # Some systems have the `locale -a` command, but the result has
- # invalid characters for the default encoding.
- #
- # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
- # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
- locales = shell_out_with_systems_locale!("locale -a").stdout.split
- case
- when locales.include?('C.UTF-8')
- 'C.UTF-8'
- when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
- 'en_US.UTF-8'
- when locales.include?('en.UTF-8')
- 'en.UTF-8'
- else
- # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
- guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
- unless guesses.empty?
- guessed_locale = guesses.first
- # Transform into the form en_ZZ.UTF-8
- guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
- else
- Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
- 'C'
- end
- end
- rescue
- if Chef::Platform.windows?
- Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
- else
- Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
- end
- 'en_US.UTF-8'
- end
-
- default :internal_locale, guess_internal_locale
-
- # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
- # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
- # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
- # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be
- # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
- # magic tags to make ruby correctly identify the encoding being used. Changing this default will
- # break Chef community cookbooks and is very highly discouraged.
- default :ruby_encoding, Encoding::UTF_8
-
- # If installed via an omnibus installer, this gives the path to the
- # "embedded" directory which contains all of the software packaged with
- # omnibus. This is used to locate the cacert.pem file on windows.
- def self.embedded_dir
- Pathname.new(_this_file).ascend do |path|
- if path.basename.to_s == "embedded"
- return path.to_s
- end
- end
-
- nil
- end
-
- # Path to this file in the current install.
- def self._this_file
- File.expand_path(__FILE__)
- end
end
end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 781d3b40b0..01a98fda39 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -286,9 +286,13 @@ class Chef
# === Returns
# versions<Array>:: Returns the list of versions for the platform
def depends(cookbook, *version_args)
- version = new_args_format(:depends, cookbook, version_args)
- constraint = validate_version_constraint(:depends, cookbook, version)
- @dependencies[cookbook] = constraint.to_s
+ if cookbook == name
+ Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)."
+ else
+ version = new_args_format(:depends, cookbook, version_args)
+ constraint = validate_version_constraint(:depends, cookbook, version)
+ @dependencies[cookbook] = constraint.to_s
+ end
@dependencies[cookbook]
end
@@ -603,7 +607,7 @@ class Chef
msg=<<-OBSOLETED
The dependency specification syntax you are using is no longer valid. You may not
specify more than one version constraint for a particular cookbook.
-Consult http://wiki.opscode.com/display/chef/Metadata for the updated syntax.
+Consult https://docs.chef.io/config_rb_metadata.html for the updated syntax.
Called by: #{caller_name} '#{dep_name}', #{version_constraints.map {|vc| vc.inspect}.join(", ")}
Called from:
@@ -622,7 +626,7 @@ OBSOLETED
The version constraint syntax you are using is not valid. If you recently
upgraded to Chef 0.10.0, be aware that you no may longer use "<<" and ">>" for
'less than' and 'greater than'; use '<' and '>' instead.
-Consult http://wiki.opscode.com/display/chef/Metadata for more information.
+Consult https://docs.chef.io/config_rb_metadata.html for more information.
Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
Called from:
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index c05fedb141..79005b1569 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -106,7 +106,7 @@ class Chef
if @cookbooks_by_name.has_key?(cookbook.to_sym) or load_cookbook(cookbook.to_sym)
@cookbooks_by_name[cookbook.to_sym]
else
- raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
+ raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)"
end
end
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index 9e7a55c772..0302a51165 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -106,7 +106,7 @@ class Chef
url = URI.parse(to_url)
- Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
# We use the body for signing the request if the file parameter
# wasn't a valid file or wasn't included. Extract the body (with
@@ -141,13 +141,8 @@ class Chef
req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
req.body_stream = body_stream
- http = Net::HTTP.new(url.host, url.port)
- if url.scheme == "https"
- http.use_ssl = true
- http.verify_mode = verify_mode
- end
+ http = Chef::HTTP::BasicClient.new(url).http_client
res = http.request(req)
- #res = http.start {|http_proc| http_proc.request(req) }
# alias status to code and to_s to body for test purposes
# TODO: stop the following madness!
@@ -166,17 +161,6 @@ class Chef
res
end
- private
-
- def verify_mode
- verify_mode = Chef::Config[:ssl_verify_mode]
- if verify_mode == :verify_none
- OpenSSL::SSL::VERIFY_NONE
- elsif verify_mode == :verify_peer
- OpenSSL::SSL::VERIFY_PEER
- end
- end
-
end
class StreamPart
diff --git a/lib/chef/dsl/definitions.rb b/lib/chef/dsl/definitions.rb
new file mode 100644
index 0000000000..1358f67720
--- /dev/null
+++ b/lib/chef/dsl/definitions.rb
@@ -0,0 +1,44 @@
+class Chef
+ module DSL
+ #
+ # Module containing a method for each declared definition
+ #
+ # Depends on declare_resource(name, created_at, &block)
+ #
+ # @api private
+ #
+ module Definitions
+ def self.add_definition(dsl_name)
+ module_eval <<-EOM, __FILE__, __LINE__+1
+ def #{dsl_name}(*args, &block)
+ evaluate_resource_definition(#{dsl_name.inspect}, *args, &block)
+ end
+ EOM
+ end
+
+ # @api private
+ def has_resource_definition?(name)
+ run_context.definitions.has_key?(name)
+ end
+
+ # Processes the arguments and block as a resource definition.
+ #
+ # @api private
+ def evaluate_resource_definition(definition_name, *args, &block)
+
+ # This dupes the high level object, but we still need to dup the params
+ new_def = run_context.definitions[definition_name].dup
+
+ new_def.params = new_def.params.dup
+ new_def.node = run_context.node
+ # This sets up the parameter overrides
+ new_def.instance_eval(&block) if block
+
+ new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
+ new_recipe.params = new_def.params
+ new_recipe.params[:name] = args[0]
+ new_recipe.instance_eval(&new_def.recipe)
+ end
+ end
+ end
+end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index c22f053292..d69f0a8f11 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -21,6 +21,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
@@ -31,48 +35,10 @@ class Chef
module Recipe
include Chef::Mixin::ShellOut
- include Chef::Mixin::ConvertToClassName
-
- def method_missing(method_symbol, *args, &block)
- # If we have a definition that matches, we want to use that instead. This should
- # let you do some really crazy over-riding of "native" types, if you really want
- # to.
- if has_resource_definition?(method_symbol)
- evaluate_resource_definition(method_symbol, *args, &block)
- elsif have_resource_class_for?(method_symbol)
- # Otherwise, we're rocking the regular resource call route.
- declare_resource(method_symbol, args[0], caller[0], &block)
- else
- begin
- super
- rescue NoMethodError
- raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
- rescue NameError
- raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
- end
- end
- end
+ include Chef::Mixin::PowershellOut
- def has_resource_definition?(name)
- run_context.definitions.has_key?(name)
- end
-
- # Processes the arguments and block as a resource definition.
- def evaluate_resource_definition(definition_name, *args, &block)
-
- # This dupes the high level object, but we still need to dup the params
- new_def = run_context.definitions[definition_name].dup
-
- new_def.params = new_def.params.dup
- new_def.node = run_context.node
- # This sets up the parameter overrides
- new_def.instance_eval(&block) if block
-
- new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
- new_recipe.params = new_def.params
- new_recipe.params[:name] = args[0]
- new_recipe.instance_eval(&new_def.recipe)
- end
+ include Chef::DSL::Resources
+ include Chef::DSL::Definitions
#
# Instantiates a resource (via #build_resource), then adds it to the
@@ -168,14 +134,52 @@ class Chef
raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\""
end
+ # DEPRECATED:
+ # method_missing must live for backcompat purposes until Chef 13.
+ def method_missing(method_symbol, *args, &block)
+ #
+ # If there is already DSL for this, someone must have called
+ # 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.")
+ return send(method_symbol, *args, &block)
+ end
+
+ #
+ # If a definition exists, then Chef::DSL::Definitions.add_definition was
+ # 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::DSL::Definitions.add_definition(method_symbol)
+ return send(method_symbol, *args, &block)
+ end
+
+ #
+ # See if the resource exists anyway. If the user had set
+ # Chef::Resource::Blah = <resource>, a deprecation warning will be
+ # emitted and the DSL method 'blah' will be added to the DSL.
+ #
+ resource_class = Chef::ResourceResolver.resolve(method_symbol, node: run_context ? run_context.node : nil)
+ if resource_class
+ Chef::DSL::Resources.add_resource_dsl(method_symbol)
+ return send(method_symbol, *args, &block)
+ end
+
+ begin
+ super
+ rescue NoMethodError
+ raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
+ rescue NameError
+ raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
+ end
+ end
end
end
end
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
-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
new file mode 100644
index 0000000000..1ce12ed0a0
--- /dev/null
+++ b/lib/chef/dsl/resources.rb
@@ -0,0 +1,29 @@
+class Chef
+ module DSL
+ #
+ # Module containing a method for each globally declared Resource
+ #
+ # Depends on declare_resource(name, created_at, &block)
+ #
+ # @api private
+ module Resources
+ 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)
+ 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)
+ end
+ end
+ end
+ def self.remove_resource_dsl(dsl_name)
+ remove_method(dsl_name) if method_defined?(dsl_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105802..73fe25ec13 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -82,6 +82,11 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ # Called after the Policyfile was loaded. This event only occurs when
+ # chef is in policyfile mode.
+ def policyfile_loaded(policy)
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
end
@@ -239,13 +244,13 @@ class Chef
end
# Called when audit phase successfully finishes
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
end
# Called if there is an uncaught exception during the audit phase. The audit runner should
# be catching and handling errors from the examples, so this is only uncaught errors (like
# bugs in our handling code)
- def audit_phase_failed(exception)
+ def audit_phase_failed(exception, audit_output)
end
# Signifies the start of a `control_group` block with a defined name
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 9f43f14311..370f8c51b4 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -9,6 +9,8 @@ class Chef
# the registered subscribers.
class Dispatcher < Base
+ attr_reader :subscribers
+
def initialize(*subscribers)
@subscribers = subscribers
end
diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index 37dcdc8693..7a3a28b61f 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -18,17 +18,7 @@
require 'chef/event_loggers/base'
require 'chef/platform/query_helpers'
-
-if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
- if defined? Windows::Constants
- [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
- # These are redefined in 'win32/eventlog'
- Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
- end
- end
-
- require 'win32/eventlog'
-end
+require 'chef/win32/eventlog'
class Chef
module EventLoggers
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index da562e70f4..dd0bac3cf9 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -73,10 +73,13 @@ class Chef
class KeyCommandInputError < ArgumentError; end
class InvalidKeyArgument < ArgumentError; end
class InvalidKeyAttribute < ArgumentError; end
+ class InvalidUserAttribute < ArgumentError; end
+ class InvalidClientAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
class CookbookNotFound < RuntimeError; end
+ class OnlyApiVersion0SupportedForAction < RuntimeError; end
# Cookbook loader used to raise an argument error when cookbook not found.
# for back compat, need to raise an error that inherits from ArgumentError
class CookbookNotFoundInRepo < ArgumentError; end
@@ -435,7 +438,7 @@ class Chef
wrapped_errors.each_with_index do |e,i|
backtrace << "#{i+1}) #{e.class} - #{e.message}"
backtrace += e.backtrace if e.backtrace
- backtrace << ""
+ backtrace << "" unless i == wrapped_errors.length - 1
end
set_backtrace(backtrace)
end
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index 472f30b752..c53d832414 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -197,6 +197,8 @@ class Chef
# the user has specified a permission, and it does not match the file, so fix the permission
Chef::Log.debug("found target_mode != current_mode, updating mode")
return true
+ elsif suid_bit_set? and (should_update_group? or should_update_owner?)
+ return true
else
Chef::Log.debug("found target_mode == current_mode, not updating mode")
# the user has specified a permission, but it matches the file, so behave idempotently
@@ -280,6 +282,9 @@ class Chef
return nil
end
+ def suid_bit_set?
+ return target_mode & 04000 > 0
+ end
end
end
end
diff --git a/lib/chef/file_content_management/deploy/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb
index 7504123012..0d16da9717 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -63,12 +63,22 @@ class Chef
raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privileges."
end
- if dst_sd.dacl_present?
- apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ dacl_present = dst_sd.dacl_present?
+ if dacl_present
+ if dst_sd.dacl.nil?
+ apply_dacl = nil
+ else
+ apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ end
end
- if dst_sd.sacl_present?
- apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ sacl_present = dst_sd.sacl_present?
+ if sacl_present
+ if dst_sd.sacl.nil?
+ apply_sacl = nil
+ else
+ apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ end
end
#
@@ -84,8 +94,8 @@ class Chef
dst_so = Security::SecurableObject.new(dst)
dst_so.group = dst_sd.group
dst_so.owner = dst_sd.owner
- dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dst_sd.dacl_present?
- dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if dst_sd.sacl_present?
+ dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dacl_present
+ dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if sacl_present
end
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7144d00b5d..e76a940c38 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -3,9 +3,9 @@ require 'chef/config'
class Chef
module Formatters
- #--
- # TODO: not sold on the name, but the output is similar to what rspec calls
- # "specdoc"
+
+ # Formatter similar to RSpec's documentation formatter. Uses indentation to
+ # show context.
class Doc < Formatters::Base
attr_reader :start_time, :end_time, :successful_audits, :failed_audits
@@ -93,6 +93,10 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ def policyfile_loaded(policy)
+ puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'"
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}"
@@ -175,17 +179,21 @@ class Chef
puts_line "Starting audit phase"
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
+ puts_line audit_output
puts_line "Auditing complete"
end
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
+ puts_line audit_output
puts_line ""
puts_line "Audit phase exception:"
indent
puts_line "#{error.message}"
- error.backtrace.each do |l|
- puts_line l
+ if error.backtrace
+ error.backtrace.each do |l|
+ puts_line l
+ end
end
end
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index 652d478b40..05ee3132a7 100644
--- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/http/authenticator'
+
class Chef
module Formatters
@@ -65,6 +67,24 @@ E
error_description.section("Server Response:",format_rest_error)
end
+ def describe_406_error(error_description, response)
+ if response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+
+ error_description.section("Incompatible server API version:",<<-E)
+This version of the API that this Chef request specified is not supported by the Chef server you sent this request to.
+The server supports a min API version of #{min_server_version} and a max API version of #{max_server_version}.
+Chef just made a request with an API version of #{client_api_version}.
+Please either update your Chef client or server to be a compatible set.
+E
+ else
+ describe_http_error(error_description)
+ end
+ end
+
def describe_500_error(error_description)
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index 93328adbe3..d64d5e7b01 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -30,15 +30,16 @@ class Chef
def initialize(path, exception)
@path, @exception = path, exception
+ @backtrace_lines_in_cookbooks = nil
+ @file_lines = nil
+ @culprit_backtrace_entry = nil
+ @culprit_line = nil
end
def add_explanation(error_description)
- case exception
- when Chef::Exceptions::RecipeNotFound
- error_description.section(exception.class.name, exception.message)
- else
- error_description.section(exception.class.name, exception.message)
+ error_description.section(exception.class.name, exception.message)
+ if found_error_in_cookbooks?
traceback = filtered_bt.map {|line| " #{line}"}.join("\n")
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
@@ -93,10 +94,21 @@ class Chef
end
def filtered_bt
- filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ }
- r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
- Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
- return r.count > 0 ? r : exception.backtrace
+ backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
+ end
+
+ def found_error_in_cookbooks?
+ !backtrace_lines_in_cookbooks.empty?
+ end
+
+ def backtrace_lines_in_cookbooks
+ @backtrace_lines_in_cookbooks ||=
+ begin
+ filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i }
+ r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
+ Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
+ r
+ end
end
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
index aa5eb8485d..e011fa9d9b 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -72,6 +72,8 @@ E
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
index 0cb849a17f..971dbd664e 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -67,6 +67,8 @@ class Chef
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable, Net::HTTPGatewayTimeOut
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
index e257ee30c0..d81a9f7cc8 100644
--- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -84,6 +84,8 @@ E
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
index f31b348278..dbd23f4a52 100644
--- a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
@@ -9,6 +9,8 @@ class Chef
# TODO: Lots of duplication with the node_load_error_inspector, just
# slightly tweaked to talk about validation keys instead of other keys.
class RegistrationErrorInspector
+ include APIErrorFormatting
+
attr_reader :exception
attr_reader :node_name
attr_reader :config
@@ -94,6 +96,8 @@ E
error_description.section("Relevant Config Settings:",<<-E)
chef_server_url "#{server_url}"
E
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
when Net::HTTPInternalServerError
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 48572d909b..6e4d9322f9 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -63,7 +63,7 @@ class Chef
def recipe_snippet
return nil if dynamic_resource?
@snippet ||= begin
- if file = resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1] and line = resource.source_line[/^#{file}:([\d]+)/,1].to_i
+ if file = parse_source and line = parse_line(file)
return nil unless ::File.exists?(file)
lines = IO.readlines(file)
@@ -111,6 +111,16 @@ class Chef
line_nr_string + line
end
+ def parse_source
+ resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1]
+ end
+
+ def parse_line(source)
+ resource.source_line[/^#{Regexp.escape(source)}:([\d]+)/,1].to_i
+ end
+
+
+
end
end
end
diff --git a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
index ac19a983af..818228276e 100644
--- a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
@@ -98,6 +98,8 @@ E
error_description.section("Possible Causes:",<<-E)
* Your client (#{username}) may have misconfigured authorization permissions.
E
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
when Net::HTTPInternalServerError
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load a role.
diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb
index df91c2b1ad..fead9886b2 100644
--- a/lib/chef/guard_interpreter/default_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/mixin/shell_out'
+
class Chef
class GuardInterpreter
class DefaultGuardInterpreter
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index 1e2a534c18..d4b386a15a 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -92,8 +92,11 @@ class Chef
raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource"
end
+ # Duplicate the node below because the new RunContext
+ # overwrites the state of Node instances passed to it.
+ # See https://github.com/chef/chef/issues/3485.
empty_events = Chef::EventDispatch::Dispatcher.new
- anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events)
+ anonymous_run_context = Chef::RunContext.new(parent_resource.node.dup, {}, empty_events)
interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
interpreter_resource.is_guard_interpreter = true
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 4255f18cbd..bffa9c4b3a 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -24,6 +24,8 @@ class Chef
class HTTP
class Authenticator
+ DEFAULT_SERVER_API_VERSION = "1"
+
attr_reader :signing_key_filename
attr_reader :raw_key
attr_reader :attr_names
@@ -37,10 +39,16 @@ class Chef
@signing_key_filename = opts[:signing_key_filename]
@key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
@auth_credentials = AuthCredentials.new(opts[:client_name], @key)
+ if opts[:api_version]
+ @api_version = opts[:api_version]
+ else
+ @api_version = DEFAULT_SERVER_API_VERSION
+ end
end
def handle_request(method, url, headers={}, data=false)
headers.merge!(authentication_headers(method, url, data)) if sign_requests?
+ headers.merge!({'X-Ops-Server-API-Version' => @api_version})
[method, url, headers, data]
end
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
index 076d152d16..de5e7c03a8 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -101,12 +101,16 @@ class Chef
env["#{url.scheme.upcase}_PROXY"] || env["#{url.scheme}_proxy"]
# Check if the proxy string contains a scheme. If not, add the url's scheme to the
- # proxy before parsing. The regex /^.*:\/\// matches, for example, http://.
- proxy = if proxy.match(/^.*:\/\//)
- URI.parse(proxy)
- else
- URI.parse("#{url.scheme}://#{proxy}")
- end if String === proxy
+ # proxy before parsing. The regex /^.*:\/\// matches, for example, http://. Reusing proxy
+ # here since we are really just trying to get the string built correctly.
+ if String === proxy && !proxy.strip.empty?
+ if proxy.match(/^.*:\/\//)
+ proxy = URI.parse(proxy.strip)
+ else
+ proxy = URI.parse("#{url.scheme}://#{proxy.strip}")
+ end
+ end
+
no_proxy = Chef::Config[:no_proxy] || env['NO_PROXY'] || env['no_proxy']
excludes = no_proxy.to_s.split(/\s*,\s*/).compact
excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb
index 23ccc3a8a7..3296d8821f 100644
--- a/lib/chef/http/json_input.rb
+++ b/lib/chef/http/json_input.rb
@@ -25,14 +25,19 @@ class Chef
# Middleware that takes json input and turns it into raw text
class JSONInput
+ attr_accessor :opts
+
def initialize(opts={})
+ @opts = opts
end
def handle_request(method, url, headers={}, data=false)
if data && should_encode_as_json?(headers)
headers.delete_if { |key, _value| key.downcase == 'content-type' }
headers["Content-Type"] = 'application/json'
- data = Chef::JSONCompat.to_json(data)
+ json_opts = {}
+ json_opts[:validate_utf8] = opts[:validate_utf8] if opts.has_key?(:validate_utf8)
+ data = Chef::JSONCompat.to_json(data, json_opts)
# Force encoding to binary to fix SSL related EOFErrors
# cf. http://tickets.opscode.com/browse/CHEF-2363
# http://redmine.ruby-lang.org/issues/5233
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
index 1ba0ab49c9..be4be7f230 100644
--- a/lib/chef/key.rb
+++ b/lib/chef/key.rb
@@ -99,6 +99,10 @@ class Chef
@public_key = nil
end
+ def delete_create_key
+ @create_key = nil
+ end
+
def create_key(arg=nil)
raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil?
set_or_return(:create_key, arg,
@@ -159,12 +163,22 @@ class Chef
self.class.generate_fingerprint(@public_key)
end
- def update
- if @name.nil?
- raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when update is called"
+ # set @name and pass put_name if you wish to update the name of an existing key put_name to @name
+ def update(put_name=nil)
+ if @name.nil? && put_name.nil?
+ raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated or you must pass a name to update when update is called"
end
- new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{@name}", to_hash)
+ # If no name was passed, fall back to using @name in the PUT URL, otherwise
+ # use the put_name passed. This will update the a key by the name put_name
+ # to @name.
+ put_name = @name if put_name.nil?
+
+ new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{put_name}", to_hash)
+ # if the server returned a public_key, remove the create_key field, as we now have a key
+ if new_key["public_key"]
+ self.delete_create_key
+ end
Chef::Key.from_hash(self.to_hash.merge(new_key))
end
@@ -190,8 +204,10 @@ class Chef
def self.from_hash(key_hash)
if key_hash.has_key?("user")
key = Chef::Key.new(key_hash["user"], "user")
- else
+ elsif key_hash.has_key?("client")
key = Chef::Key.new(key_hash["client"], "client")
+ else
+ raise Chef::Exceptions::MissingKeyAttribute, "The hash passed to from_hash does not contain the key 'user' or 'client'. Please pass a hash that defines one of those keys."
end
key.name key_hash['name'] if key_hash.key?('name')
key.public_key key_hash['public_key'] if key_hash.key?('public_key')
@@ -221,12 +237,12 @@ class Chef
def self.load_by_user(actor, key_name)
response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys/#{key_name}")
- Chef::Key.from_hash(response)
+ Chef::Key.from_hash(response.merge({"user" => actor}))
end
def self.load_by_client(actor, key_name)
response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys/#{key_name}")
- Chef::Key.from_hash(response)
+ Chef::Key.from_hash(response.merge({"client" => actor}))
end
def self.generate_fingerprint(public_key)
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 2e0694aebc..4a93697a1b 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -27,6 +27,7 @@ require 'chef/knife/core/subcommand_loader'
require 'chef/knife/core/ui'
require 'chef/local_mode'
require 'chef/rest'
+require 'chef/http/authenticator'
require 'pp'
class Chef
@@ -358,7 +359,7 @@ class Chef
case Chef::Config[:verbosity]
when 0, nil
- Chef::Config[:log_level] = :error
+ Chef::Config[:log_level] = :warn
when 1
Chef::Config[:log_level] = :info
else
@@ -400,6 +401,8 @@ class Chef
end
def configure_chef
+ # knife needs to send logger output to STDERR by default
+ Chef::Config[:log_location] = STDERR
config_loader = self.class.load_config(config[:config_file])
config[:config_file] = config_loader.config_location
@@ -483,6 +486,15 @@ class Chef
when Net::HTTPServiceUnavailable
ui.error "Service temporarily unavailable"
ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPNotAcceptable
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+ ui.error "The version of Chef that Knife is using is not supported by the Chef server you sent this request to"
+ ui.info "The request that Knife sent was using API version #{client_api_version}"
+ ui.info "The Chef server you sent the request to supports a min API verson of #{min_server_version} and a max API version of #{max_server_version}"
+ ui.info "Please either update your Chef client or server to be a compatible set"
else
ui.error response.message
ui.info "Response: #{format_rest_error(response)}"
@@ -539,6 +551,16 @@ class Chef
self.msg("Deleted #{obj_name}")
end
+ # helper method for testing if a field exists
+ # and returning the usage and proper error if not
+ def test_mandatory_field(field, fieldname)
+ if field.nil?
+ show_usage
+ ui.fatal("You must specify a #{fieldname}")
+ exit 1
+ end
+ end
+
def rest
@rest ||= begin
require 'chef/rest'
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index a4095e8402..5b29591fcc 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -316,6 +316,12 @@ class Chef
# new client-side hawtness, just delete your validation key.
if chef_vault_handler.doing_chef_vault? ||
(Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
+
+ unless config[:chef_node_name]
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
+ exit 1
+ end
+
client_builder.run
chef_vault_handler.run(node_name: config[:chef_node_name])
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 477a400e8a..570c1ee950 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -28,58 +28,82 @@ class Chef
end
option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "Write the key to a file"
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file if the server generated one."
option :admin,
- :short => "-a",
- :long => "--admin",
- :description => "Create the client as an admin",
- :boolean => true
+ :short => "-a",
+ :long => "--admin",
+ :description => "Open Source Chef 11 only. Create the client as an admin.",
+ :boolean => true
option :validator,
- :long => "--validator",
- :description => "Create the client as a validator",
- :boolean => true
+ :long => "--validator",
+ :description => "Create the client as a validator.",
+ :boolean => true
- banner "knife client create CLIENT (options)"
+ option :public_key,
+ :short => "-p FILE",
+ :long => "--public-key",
+ :description => "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.",
+ :boolean => true
+
+ banner "knife client create CLIENTNAME (options)"
+
+ def client
+ @client_field ||= Chef::ApiClient.new
+ end
+
+ def create_client(client)
+ # should not be using save :( bad behavior
+ client.save
+ end
def run
- @client_name = @name_args[0]
+ test_mandatory_field(@name_args[0], "client name")
+ client.name @name_args[0]
- if @client_name.nil?
+ if config[:public_key] && config[:prevent_keygen]
show_usage
- ui.fatal("You must specify a client name")
+ ui.fatal("You cannot pass --public-key and --prevent-keygen")
exit 1
end
- client_hash = {
- "name" => @client_name,
- "admin" => !!config[:admin],
- "validator" => !!config[:validator]
- }
+ if !config[:prevent_keygen] && !config[:public_key]
+ client.create_key(true)
+ end
+
+ if config[:admin]
+ client.admin(true)
+ end
- output = Chef::ApiClient.from_hash(edit_hash(client_hash))
+ if config[:validator]
+ client.validator(true)
+ end
- # Chef::ApiClient.save will try to create a client and if it
- # exists will update it instead silently.
- client = output.save
+ if config[:public_key]
+ client.public_key File.read(File.expand_path(config[:public_key]))
+ end
- # We only get a private_key on client creation, not on client update.
- if client['private_key']
- ui.info("Created #{output}")
+ output = edit_data(client)
+ final_client = create_client(output)
+ ui.info("Created #{output}")
+ # output private_key if one
+ if final_client.private_key
if config[:file]
File.open(config[:file], "w") do |f|
- f.print(client['private_key'])
+ f.print(final_client.private_key)
end
else
- puts client['private_key']
+ puts final_client.private_key
end
- else
- ui.error "Client '#{client['name']}' already exists"
- exit 1
end
end
end
diff --git a/lib/chef/knife/client_key_delete.rb b/lib/chef/knife/client_key_delete.rb
new file mode 100644
index 0000000000..8ecdfe1ec8
--- /dev/null
+++ b/lib/chef/knife/client_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@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'
+
+class Chef
+ class Knife
+ # Implements knife client key delete using Chef::Knife::KeyDelete
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyDelete < Knife
+ banner "knife client key delete CLIENT KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'client'
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_edit.rb b/lib/chef/knife/client_key_edit.rb
new file mode 100644
index 0000000000..1de45f4ca2
--- /dev/null
+++ b/lib/chef/knife/client_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler@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/key_edit_base'
+
+class Chef
+ class Knife
+ # Implements knife client key edit using Chef::Knife::KeyEdit
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyEdit < Knife
+ include Chef::Knife::KeyEditBase
+
+ banner 'knife client key edit CLIENT KEYNAME (options)'
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'client'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb
new file mode 100644
index 0000000000..f6f29ae03f
--- /dev/null
+++ b/lib/chef/knife/client_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler@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/key_list_base'
+
+class Chef
+ class Knife
+ # Implements knife user key list using Chef::Knife::KeyList
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyList < Knife
+ include Chef::Knife::KeyListBase
+
+ banner "knife client key list CLIENT (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def list_method
+ :list_by_client
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_show.rb b/lib/chef/knife/client_key_show.rb
new file mode 100644
index 0000000000..c39a279000
--- /dev/null
+++ b/lib/chef/knife/client_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@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'
+
+class Chef
+ class Knife
+ # Implements knife client key show using Chef::Knife::KeyShow
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyShow < Knife
+ banner "knife client key show CLIENT KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def load_method
+ :load_by_client
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index f3ea0f0d6c..2df9603faa 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -181,7 +181,7 @@ class Chef
# Must check :[] before attr because spec can include
# `keys` - want the key named `keys`, not a list of
# available keys.
- elsif data.respond_to?(:[])
+ elsif data.respond_to?(:[]) && data.has_key?(attr)
data = data[attr]
elsif data.respond_to?(attr.to_sym)
data = data.send(attr.to_sym)
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 1f59515f44..a8705c724f 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -23,7 +23,7 @@ class Chef
class SubcommandLoader
MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
+ MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
attr_reader :chef_config_dir
attr_reader :env
diff --git a/lib/chef/knife/key_delete.rb b/lib/chef/knife/key_delete.rb
new file mode 100644
index 0000000000..fb996cff17
--- /dev/null
+++ b/lib/chef/knife/key_delete.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler@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/key'
+
+class Chef
+ class Knife
+ # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys.
+ # Implements common functionality of knife [user | org client] key delete.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it
+ class KeyDelete
+ def initialize(name, actor, actor_field_name, ui)
+ @name = name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ end
+
+ def confirm!
+ @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def print_destroyed
+ @ui.info("Destroyed key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ key.name(@name)
+ confirm!
+ key.destroy
+ print_destroyed
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/key_edit.rb b/lib/chef/knife/key_edit.rb
new file mode 100644
index 0000000000..48ae344e6e
--- /dev/null
+++ b/lib/chef/knife/key_edit.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Tyler Cloke (<tyler@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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyEdit and ClientKeyEdit,
+ # Implements common functionality of knife [user | org client] key edit.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it
+ class KeyEdit
+
+ attr_accessor :config
+
+ def initialize(original_name, actor, actor_field_name, ui, config)
+ @original_name = original_name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ @config = config
+ end
+
+ def public_key_and_create_key_error_msg
+<<EOS
+You passed both --public-key and --create-key. Only pass one, or the other, or neither.
+Do not pass either if you do not want to change the public_key field of your key.
+Pass --public-key if you want to update the public_key field of your key from a specific public key.
+Pass --create-key if you want the server to generate a new key and use that to update the public_key field of your key.
+EOS
+ end
+
+ def edit_data(key)
+ @ui.edit_data(key)
+ end
+
+ def display_info(input)
+ @ui.info(input)
+ end
+
+ def display_private_key(private_key)
+ @ui.msg(private_key)
+ end
+
+ def output_private_key_to_file(private_key)
+ File.open(@config[:file], "w") do |f|
+ f.print(private_key)
+ end
+ end
+
+ def update_key_from_hash(output)
+ Chef::Key.from_hash(output).update(@original_name)
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ if @config[:public_key] && @config[:create_key]
+ raise Chef::Exceptions::KeyCommandInputError, public_key_and_create_key_error_msg
+ end
+
+ if @config[:create_key]
+ key.create_key(true)
+ end
+
+ if @config[:public_key]
+ key.public_key(File.read(File.expand_path(@config[:public_key])))
+ end
+
+ if @config[:key_name]
+ key.name(@config[:key_name])
+ else
+ key.name(@original_name)
+ end
+
+ if @config[:expiration_date]
+ key.expiration_date(@config[:expiration_date])
+ end
+
+ output = edit_data(key)
+ key = update_key_from_hash(output)
+
+ to_display = "Updated key: #{key.name}"
+ to_display << " (formally #{@original_name})" if key.name != @original_name
+ display_info(to_display)
+ if key.private_key
+ if @config[:file]
+ output_private_key_to_file(key.private_key)
+ else
+ display_private_key(key.private_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_edit_base.rb b/lib/chef/knife/key_edit_base.rb
new file mode 100644
index 0000000000..bb5a951a5b
--- /dev/null
+++ b/lib/chef/knife/key_edit_base.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler@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.
+#
+
+class Chef
+ class Knife
+ # Extendable module that class_eval's common options into UserKeyEdit and ClientKeyEdit
+ #
+ # @author Tyler Cloke
+ module KeyEditBase
+ def self.included(includer)
+ includer.class_eval do
+ option :public_key,
+ :short => "-p FILENAME",
+ :long => "--public-key FILENAME",
+ :description => "Replace the public_key field from a file on disk. If not passed, the public_key field will not change."
+
+ option :create_key,
+ :short => "-c",
+ :long => "--create-key",
+ :description => "Replace the public_key field with a key generated by the server. The private key will be returned."
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file, if you requested the server to create one via --create-key."
+
+ option :key_name,
+ :short => "-k NAME",
+ :long => "--key-name NAME",
+ :description => "The new name for your key. Pass if you wish to update the name field of your key."
+
+ option :expiration_date,
+ :short => "-e DATE",
+ :long => "--expiration-date DATE",
+ :description => "Updates the expiration_date field of your key if passed. Pass in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z or infinity. UTC timezone assumed."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb
new file mode 100644
index 0000000000..e96a27161f
--- /dev/null
+++ b/lib/chef/knife/key_list.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Tyler Cloke (<tyler@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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyList and ClientKeyList, used to list keys.
+ # Implements common functionality of knife [user | org client] key list.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it
+ class KeyList
+
+ attr_accessor :config
+
+ def initialize(actor, list_method, ui, config)
+ @actor = actor
+ @list_method = list_method
+ @ui = ui
+ @config = config
+ end
+
+ def expired_and_non_expired_msg
+<<EOS
+You cannot pass both --only-expired and --only-non-expired.
+Please pass one or none.
+EOS
+ end
+
+ def display_info(string)
+ @ui.output(string)
+ end
+
+ def colorize(string)
+ @ui.color(string, :cyan)
+ end
+
+ def run
+ if @config[:only_expired] && @config[:only_non_expired]
+ raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg
+ end
+
+ # call proper list function
+ keys = Chef::Key.send(@list_method, @actor)
+ if @config[:with_details]
+ max_length = 0
+ keys.each do |key|
+ key['name'] = key['name'] + ":"
+ max_length = key['name'].length if key['name'].length > max_length
+ end
+ keys.each do |key|
+ next if !key['expired'] && @config[:only_expired]
+ next if key['expired'] && @config[:only_non_expired]
+ display = "#{colorize(key['name'].ljust(max_length))} #{key['uri']}"
+ display = "#{display} (expired)" if key["expired"]
+ display_info(display)
+ end
+ else
+ keys.each do |key|
+ next if !key['expired'] && @config[:only_expired]
+ next if key['expired'] && @config[:only_non_expired]
+ display_info(key['name'])
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/key_list_base.rb b/lib/chef/knife/key_list_base.rb
new file mode 100644
index 0000000000..861db0d75a
--- /dev/null
+++ b/lib/chef/knife/key_list_base.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Tyler Cloke (<tyler@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.
+#
+
+class Chef
+ class Knife
+ # Extendable module that class_eval's common options into UserKeyList and ClientKeyList
+ #
+ # @author Tyler Cloke
+ module KeyListBase
+ def self.included(includer)
+ includer.class_eval do
+ option :with_details,
+ :short => "-w",
+ :long => "--with-details",
+ :description => "Show corresponding URIs and whether the key has expired or not."
+
+ option :only_expired,
+ :short => "-e",
+ :long => "--only-expired",
+ :description => "Only show expired keys."
+
+ option :only_non_expired,
+ :short => "-n",
+ :long => "--only-non-expired",
+ :description => "Only show non-expired keys."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_show.rb b/lib/chef/knife/key_show.rb
new file mode 100644
index 0000000000..522f7a1a77
--- /dev/null
+++ b/lib/chef/knife/key_show.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Tyler Cloke (<tyler@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/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyShow and ClientKeyShow, used to show keys.
+ # Implements common functionality of knife [user | org client] key show.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it
+ class KeyShow
+
+ attr_accessor :config
+
+ def initialize(name, actor, load_method, ui)
+ @name = name
+ @actor = actor
+ @load_method = load_method
+ @ui = ui
+ end
+
+ def display_output(key)
+ @ui.output(@ui.format_for_display(key))
+ end
+
+ def run
+ key = Chef::Key.send(@load_method, @actor, @name)
+ key.public_key(key.public_key.strip)
+ display_output(key)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_create.rb b/lib/chef/knife/osc_user_create.rb
new file mode 100644
index 0000000000..c368296040
--- /dev/null
+++ b/lib/chef/knife/osc_user_create.rb
@@ -0,0 +1,97 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_create.rb.
+class Chef
+ class Knife
+ class OscUserCreate < Knife
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file"
+
+ option :admin,
+ :short => "-a",
+ :long => "--admin",
+ :description => "Create the user as an admin",
+ :boolean => true
+
+ option :user_password,
+ :short => "-p PASSWORD",
+ :long => "--password PASSWORD",
+ :description => "Password for newly created user",
+ :default => ""
+
+ option :user_key,
+ :long => "--user-key FILENAME",
+ :description => "Public key for newly created user. By default a key will be created for you."
+
+ banner "knife osc_user create USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ if config[:user_password].length == 0
+ show_usage
+ ui.fatal("You must specify a non-blank password")
+ exit 1
+ end
+
+ user = Chef::OscUser.new
+ user.name(@user_name)
+ user.admin(config[:admin])
+ user.password config[:user_password]
+
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
+
+ output = edit_data(user)
+ user = Chef::OscUser.from_hash(output).create
+
+ ui.info("Created #{user}")
+ if user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(user.private_key)
+ end
+ else
+ ui.msg user.private_key
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/osc_user_delete.rb
new file mode 100644
index 0000000000..d6fbd4a6a9
--- /dev/null
+++ b/lib/chef/knife/osc_user_delete.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in the user_delete.rb.
+
+class Chef
+ class Knife
+ class OscUserDelete < Knife
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user delete USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ delete_object(Chef::OscUser, @user_name)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_edit.rb b/lib/chef/knife/osc_user_edit.rb
new file mode 100644
index 0000000000..4c38674d08
--- /dev/null
+++ b/lib/chef/knife/osc_user_edit.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_edit.rb.
+
+class Chef
+ class Knife
+ class OscUserEdit < Knife
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user edit USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ original_user = Chef::OscUser.load(@user_name).to_hash
+ edited_user = edit_data(original_user)
+ if original_user != edited_user
+ user = Chef::OscUser.from_hash(edited_user)
+ user.update
+ ui.msg("Saved #{user}.")
+ else
+ ui.msg("User unchaged, not saving.")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/osc_user_list.rb
new file mode 100644
index 0000000000..92f049cd19
--- /dev/null
+++ b/lib/chef/knife/osc_user_list.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_list.rb.
+
+class Chef
+ class Knife
+ class OscUserList < Knife
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user list (options)"
+
+ option :with_uri,
+ :short => "-w",
+ :long => "--with-uri",
+ :description => "Show corresponding URIs"
+
+ def run
+ output(format_list_for_display(Chef::OscUser.list))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
new file mode 100644
index 0000000000..a71e0aa677
--- /dev/null
+++ b/lib/chef/knife/osc_user_reregister.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_reregister.rb.
+
+class Chef
+ class Knife
+ class OscUserReregister < Knife
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user reregister USER (options)"
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ user = Chef::OscUser.load(@user_name).reregister
+ Chef::Log.debug("Updated user data: #{user.inspect}")
+ key = user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb
new file mode 100644
index 0000000000..6a41ddae88
--- /dev/null
+++ b/lib/chef/knife/osc_user_show.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2009 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/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_show.rb.
+
+class Chef
+ class Knife
+ class OscUserShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require 'chef/osc_user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user show USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ user = Chef::OscUser.load(@user_name)
+ output(format_for_display(user))
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index 656baf5e8f..68e01cf94f 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -160,6 +160,31 @@ class Chef
session_from_list(list)
end
+ def get_ssh_attribute(node)
+ # Order of precedence for ssh target
+ # 1) command line attribute
+ # 2) configuration file
+ # 3) cloud attribute
+ # 4) fqdn
+ if config[:attribute]
+ Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
+ attribute = config[:attribute]
+ elsif Chef::Config[:knife][:ssh_attribute]
+ Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}")
+ attribute = Chef::Config[:knife][:ssh_attribute]
+ elsif node[:cloud] &&
+ node[:cloud][:public_hostname] &&
+ !node[:cloud][:public_hostname].empty?
+ Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
+ attribute = 'cloud.public_hostname'
+ else
+ # falling back to default of fqdn
+ Chef::Log.debug("Using node attribute 'fqdn' as the ssh target")
+ attribute = "fqdn"
+ end
+ attribute
+ end
+
def search_nodes
list = Array.new
query = Chef::Search::Query.new
@@ -168,25 +193,9 @@ class Chef
# we should skip the loop to next iteration if the item
# returned by the search is nil
next if item.nil?
- # if a command line attribute was not passed, and we have a
- # cloud public_hostname, use that. see #configure_attribute
- # for the source of config[:attribute] and
- # config[:attribute_from_cli]
- if config[:attribute_from_cli]
- Chef::Log.debug("Using node attribute '#{config[:attribute_from_cli]}' from the command line as the ssh target")
- host = extract_nested_value(item, config[:attribute_from_cli])
- elsif item[:cloud] &&
- item[:cloud][:public_hostname] &&
- !item[:cloud][:public_hostname].empty?
- Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
- host = item[:cloud][:public_hostname]
- else
- # ssh attribute from a configuration file or the default will land here
- Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
- host = extract_nested_value(item, config[:attribute])
- end
# next if we couldn't find the specified attribute in the
# returned node object
+ host = extract_nested_value(item,get_ssh_attribute(item))
next if host.nil?
ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port]
srv = [host, ssh_port]
@@ -416,16 +425,6 @@ class Chef
end
end
- def configure_attribute
- # Setting 'knife[:ssh_attribute] = "foo"' in knife.rb => Chef::Config[:knife][:ssh_attribute] == 'foo'
- # Running 'knife ssh -a foo' => both Chef::Config[:knife][:ssh_attribute] && config[:attribute] == foo
- # Thus we can differentiate between a config file value and a command line override at this point by checking config[:attribute]
- # We can tell here if fqdn was passed from the command line, rather than being the default, by checking config[:attribute]
- # However, after here, we cannot tell these things, so we must preserve config[:attribute]
- config[:attribute_from_cli] = config[:attribute]
- config[:attribute] = (config[:attribute_from_cli] || Chef::Config[:knife][:ssh_attribute] || "fqdn").strip
- end
-
def cssh
cssh_cmd = nil
%w[csshX cssh].each do |cmd|
@@ -499,7 +498,6 @@ class Chef
@longest = 0
- configure_attribute
configure_user
configure_password
configure_identity_file
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 4130f06878..e73f6be8b6 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,11 +18,14 @@
#
require 'chef/knife'
+require 'chef/knife/osc_user_create'
class Chef
class Knife
class UserCreate < Knife
+ attr_accessor :user_field
+
deps do
require 'chef/user'
require 'chef/json_compat'
@@ -30,63 +34,118 @@ class Chef
option :file,
:short => "-f FILE",
:long => "--file FILE",
- :description => "Write the private key to a file"
+ :description => "Write the private key to a file if the server generated one."
+
+ option :user_key,
+ :long => "--user-key FILENAME",
+ :description => "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+ :boolean => true
option :admin,
:short => "-a",
:long => "--admin",
- :description => "Create the user as an admin",
+ :description => "DEPRECATED: Open Source Chef 11 only. Create the user as an admin.",
:boolean => true
option :user_password,
:short => "-p PASSWORD",
:long => "--password PASSWORD",
- :description => "Password for newly created user",
+ :description => "DEPRECATED: Open Source Chef 11 only. Password for newly created user.",
:default => ""
- option :user_key,
- :long => "--user-key FILENAME",
- :description => "Public key for newly created user. By default a key will be created for you."
+ banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
+
+ def user
+ @user_field ||= Chef::User.new
+ end
+
+ def create_user_from_hash(hash)
+ Chef::User.from_hash(hash).create
+ end
+
+ def osc_11_warning
+<<-EOF
+IF YOU ARE USING CHEF SERVER 12+, PLEASE FOLLOW THE INSTRUCTIONS UNDER knife user create --help.
+You only passed a single argument to knife user create.
+For backwards compatibility, when only a single argument is passed,
+knife user create assumes you want Open Source 11 Server user creation.
+knife user create for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user create.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
- banner "knife user create USER (options)"
+ def run_osc_11_user_create
+ # run osc_user_create with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
def run
- @user_name = @name_args[0]
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # If only 1 arg is passed, assume OSC 11 case.
+ if @name_args.length == 1
+ ui.warn(osc_11_warning)
+ run_osc_11_user_create
+ else # EC / CS 12 user create
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
+ test_mandatory_field(@name_args[0], "username")
+ user.username @name_args[0]
- if config[:user_password].length == 0
- show_usage
- ui.fatal("You must specify a non-blank password")
- exit 1
- end
+ test_mandatory_field(@name_args[1], "display name")
+ user.display_name @name_args[1]
- user = Chef::User.new
- user.name(@user_name)
- user.admin(config[:admin])
- user.password config[:user_password]
+ test_mandatory_field(@name_args[2], "first name")
+ user.first_name @name_args[2]
- if config[:user_key]
- user.public_key File.read(File.expand_path(config[:user_key]))
- end
+ test_mandatory_field(@name_args[3], "last name")
+ user.last_name @name_args[3]
+
+ test_mandatory_field(@name_args[4], "email")
+ user.email @name_args[4]
+
+ test_mandatory_field(@name_args[5], "password")
+ user.password @name_args[5]
+
+ if config[:user_key] && config[:prevent_keygen]
+ show_usage
+ ui.fatal("You cannot pass --user-key and --prevent-keygen")
+ exit 1
+ end
+
+ if !config[:prevent_keygen] && !config[:user_key]
+ user.create_key(true)
+ end
- output = edit_data(user)
- user = Chef::User.from_hash(output).create
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
- ui.info("Created #{user}")
- if user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(user.private_key)
+ output = edit_data(user)
+ final_user = create_user_from_hash(output)
+
+ ui.info("Created #{user}")
+ if final_user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(final_user.private_key)
+ end
+ else
+ ui.msg final_user.private_key
end
- else
- ui.msg user.private_key
end
end
+
+
end
end
end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index b7af11bec8..803be6b90c 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -29,6 +29,40 @@ class Chef
banner "knife user delete USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user delete for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user delete.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_delete
+ # run osc_user_delete with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
+ # DEPRECATION NOTE
+ # Delete this override method after OSC 11 support is dropped
+ def delete_object(user_name)
+ confirm("Do you really want to delete #{user_name}")
+
+ if Kernel.block_given?
+ object = block.call
+ else
+ object = Chef::User.load(user_name)
+ object.destroy
+ end
+
+ output(format_for_display(object)) if config[:print_after]
+ self.msg("Deleted #{user_name}")
+ end
+
def run
@user_name = @name_args[0]
@@ -38,9 +72,25 @@ class Chef
exit 1
end
- delete_object(Chef::User, @user_name)
- end
+ # DEPRECATION NOTE
+ #
+ # 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)
+ #
+ # Also delete our override of delete_object above
+ object = Chef::User.load(@user_name)
+ # OSC 11 case
+ if object.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_delete
+ else # proceed with EC / CS delete
+ delete_object(@user_name)
+ end
+
+ end
end
end
end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index ae319c8872..dd2fc02743 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -29,6 +29,24 @@ class Chef
banner "knife user edit USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user edit for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife oc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user edit.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_edit
+ # run osc_user_create with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
def run
@user_name = @name_args[0]
@@ -39,14 +57,26 @@ class Chef
end
original_user = Chef::User.load(@user_name).to_hash
- edited_user = edit_data(original_user)
- if original_user != edited_user
- user = Chef::User.from_hash(edited_user)
- user.update
- ui.msg("Saved #{user}.")
- else
- ui.msg("User unchaged, not saving.")
+
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if original_user["username"].nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_edit
+ 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.update
+ ui.msg("Saved #{user}.")
+ else
+ ui.msg("User unchaged, not saving.")
+ end
end
+
end
end
end
diff --git a/lib/chef/knife/user_key_delete.rb b/lib/chef/knife/user_key_delete.rb
new file mode 100644
index 0000000000..6db1c9f552
--- /dev/null
+++ b/lib/chef/knife/user_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@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'
+
+class Chef
+ class Knife
+ # Implements knife user key delete using Chef::Knife::KeyDelete
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyDelete < Knife
+ banner "knife user key delete USER KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'user'
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_edit.rb b/lib/chef/knife/user_key_edit.rb
new file mode 100644
index 0000000000..0c35332523
--- /dev/null
+++ b/lib/chef/knife/user_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler@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/key_edit_base'
+
+class Chef
+ class Knife
+ # Implements knife user key edit using Chef::Knife::KeyEdit
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the user that this key is for
+ class UserKeyEdit < Knife
+ include Chef::Knife::KeyEditBase
+
+ banner 'knife user key edit USER KEYNAME (options)'
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'user'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb
new file mode 100644
index 0000000000..a73f59c86f
--- /dev/null
+++ b/lib/chef/knife/user_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler@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/key_list_base'
+
+class Chef
+ class Knife
+ # Implements knife user key list using Chef::Knife::KeyList
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyList < Knife
+ include Chef::Knife::KeyListBase
+
+ banner "knife user key list USER (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def list_method
+ :list_by_user
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_show.rb b/lib/chef/knife/user_key_show.rb
new file mode 100644
index 0000000000..b8453dd28e
--- /dev/null
+++ b/lib/chef/knife/user_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@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'
+
+class Chef
+ class Knife
+ # Implements knife user key show using Chef::Knife::KeyShow
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyShow < Knife
+ banner "knife user key show USER KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def load_method
+ :load_by_user
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 5d2e735a73..7ae43dadc9 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -18,6 +18,8 @@
require 'chef/knife'
+# NOTE: only knife user command that is backwards compatible with OSC 11,
+# so no deprecation warnings are necessary.
class Chef
class Knife
class UserList < Knife
@@ -37,6 +39,7 @@ class Chef
def run
output(format_list_for_display(Chef::User.list))
end
+
end
end
end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index 946150e6e4..eab2245025 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -29,6 +29,24 @@ class Chef
banner "knife user reregister USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user reregister for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user reregister.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_reregister
+ # run osc_user_edit with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
option :file,
:short => "-f FILE",
:long => "--file FILE",
@@ -43,16 +61,29 @@ class Chef
exit 1
end
- user = Chef::User.load(@user_name).reregister
- Chef::Log.debug("Updated user data: #{user.inspect}")
- key = user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(key)
+ user = Chef::User.load(@user_name)
+
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if user.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_reregister
+ else # EC / CS 12 case
+ user.reregister
+ Chef::Log.debug("Updated user data: #{user.inspect}")
+ key = user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
end
- else
- ui.msg key
end
+
end
end
end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index 61ea101e4c..f5e81e9972 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -31,6 +31,24 @@ class Chef
banner "knife user show USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user show for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user show.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_show
+ # run osc_user_edit with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
def run
@user_name = @name_args[0]
@@ -41,7 +59,18 @@ class Chef
end
user = Chef::User.load(@user_name)
- output(format_for_display(user))
+
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if user.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_show
+ else
+ output(format_for_display(user))
+ end
end
end
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 682afcea4b..9b27778a40 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -21,6 +21,8 @@ require 'logger'
require 'chef/monologger'
require 'chef/exceptions'
require 'mixlib/log'
+require 'chef/log/syslog' unless (RUBY_PLATFORM =~ /mswin|mingw|windows/)
+require 'chef/log/winevt'
class Chef
class Log
diff --git a/lib/chef/log/syslog.rb b/lib/chef/log/syslog.rb
new file mode 100644
index 0000000000..0c8190797f
--- /dev/null
+++ b/lib/chef/log/syslog.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>)
+# 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 'logger'
+require 'syslog-logger'
+require 'chef/mixin/unformatter'
+
+class Chef
+ class Log
+ #
+ # Chef::Log::Syslog class.
+ # usage in client.rb:
+ # log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON)
+ #
+ class Syslog < Logger::Syslog
+ include Chef::Mixin::Unformatter
+
+ attr_accessor :sync, :formatter
+
+ def initialize(program_name = 'chef-client', facility = ::Syslog::LOG_DAEMON, logopts=nil)
+ super
+ return if defined? ::Logger::Syslog::SYSLOG
+ ::Logger::Syslog.const_set :SYSLOG, SYSLOG
+ end
+
+ def close
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/log/winevt.rb b/lib/chef/log/winevt.rb
new file mode 100644
index 0000000000..c5b7c3485a
--- /dev/null
+++ b/lib/chef/log/winevt.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: 2015, Chef Software, Inc.
+#
+# 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_loggers/base'
+require 'chef/platform/query_helpers'
+require 'chef/mixin/unformatter'
+
+class Chef
+ class Log
+ #
+ # Chef::Log::WinEvt class.
+ # usage in client.rb:
+ # log_location Chef::Log::WinEvt.new
+ #
+ class WinEvt
+ # These must match those that are defined in the manifest file
+ INFO_EVENT_ID = 10100
+ WARN_EVENT_ID = 10101
+ DEBUG_EVENT_ID = 10102
+ ERROR_EVENT_ID = 10103
+ FATAL_EVENT_ID = 10104
+
+ # Since we must install the event logger, this is not really configurable
+ SOURCE = 'Chef'
+
+ include Chef::Mixin::Unformatter
+
+ attr_accessor :sync, :formatter, :level
+
+ def initialize(eventlog=nil)
+ @eventlog = eventlog || ::Win32::EventLog::open('Application')
+ end
+
+ def close
+ end
+
+ def info(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::INFO_TYPE,
+ :source => SOURCE,
+ :event_id => INFO_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def warn(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::WARN_TYPE,
+ :source => SOURCE,
+ :event_id => WARN_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def debug(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::INFO_TYPE,
+ :source => SOURCE,
+ :event_id => DEBUG_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def error(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::ERROR_TYPE,
+ :source => SOURCE,
+ :event_id => ERROR_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def fatal(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::ERROR_TYPE,
+ :source => SOURCE,
+ :event_id => FATAL_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/api_version_request_handling.rb b/lib/chef/mixin/api_version_request_handling.rb
new file mode 100644
index 0000000000..20ab3bf452
--- /dev/null
+++ b/lib/chef/mixin/api_version_request_handling.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Tyler Cloke (tyler@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.
+#
+
+class Chef
+ module Mixin
+ module ApiVersionRequestHandling
+ # Input:
+ # exeception:
+ # Net::HTTPServerException that may or may not contain the x-ops-server-api-version header
+ # supported_client_versions:
+ # An array of Integers that represent the API versions the client supports.
+ #
+ # Output:
+ # nil:
+ # If the execption was not a 406 or the server does not support versioning
+ # Array of length zero:
+ # If there was no intersection between supported client versions and supported server versions
+ # Arrary of Integers:
+ # If there was an intersection of supported versions, the array returns will contain that intersection
+ def server_client_api_version_intersection(exception, supported_client_versions)
+ # return empty array unless 406 Unacceptable with proper header
+ return nil if exception.response.code != "406" || exception.response["x-ops-server-api-version"].nil?
+
+ # intersection of versions the server and client support, will be of length zero if no intersection
+ server_supported_client_versions = Array.new
+
+ header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"])
+ min_server_version = Integer(header["min_version"])
+ max_server_version = Integer(header["max_version"])
+
+ supported_client_versions.each do |version|
+ if version >= min_server_version && version <= max_server_version
+ server_supported_client_versions.push(version)
+ end
+ end
+ server_supported_client_versions
+ end
+
+ def reregister_only_v0_supported_error_msg(max_version, min_version)
+ <<-EOH
+The reregister command only supports server API version 0.
+The server that received the request supports a min version of #{min_version} and a max version of #{max_version}.
+User keys are now managed via the key rotation commmands.
+Please refer to the documentation on how to manage your keys via the key rotation commands:
+https://docs.chef.io/server_security.html#key-rotation
+EOH
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index 19f229fdd3..14676e5ed4 100644
--- a/lib/chef/mixin/convert_to_class_name.rb
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -23,9 +23,7 @@ class Chef
extend self
def convert_to_class_name(str)
- str = str.dup
- str.gsub!(/[^A-Za-z0-9_]/,'_')
- str.gsub!(/^(_+)?/,'')
+ str = normalize_snake_case_name(str)
rname = nil
regexp = %r{^(.+?)(_(.+))?$}
@@ -51,6 +49,13 @@ class Chef
str
end
+ def normalize_snake_case_name(str)
+ str = str.dup
+ str.gsub!(/[^A-Za-z0-9_]/,'_')
+ str.gsub!(/^(_+)?/,'')
+ str
+ end
+
def snake_case_basename(str)
with_namespace = convert_to_snake_case(str)
with_namespace.split("::").last.sub(/^_/, '')
@@ -58,7 +63,8 @@ class Chef
def filename_to_qualified_string(base, filename)
file_base = File.basename(filename, ".rb")
- base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ str = base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ normalize_snake_case_name(str)
end
# Copied from rails activesupport. In ruby >= 2.0 const_get will just do this, so this can
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index 489f27c339..a3eacf75cb 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -95,6 +95,30 @@ class Chef
DeprecatedInstanceVariable.new(obj, name, level)
end
+ def deprecated_attr(name, alternative)
+ deprecated_attr_reader(name, alternative)
+ deprecated_attr_writer(name, alternative)
+ end
+
+ 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)}
+ 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)}
+ instance_variable_set("@#{name}", value)
+ end
+ end
end
end
end
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
new file mode 100644
index 0000000000..e4f29c07c4
--- /dev/null
+++ b/lib/chef/mixin/powershell_out.rb
@@ -0,0 +1,98 @@
+#--
+# 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/mixin/shell_out'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+ module Mixin
+ module PowershellOut
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::WindowsArchitectureHelper
+
+ # Run a command under powershell with the same API as shell_out. The
+ # options hash is extended to take an "architecture" flag which
+ # can be set to :i386 or :x86_64 to force the windows architecture.
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out(*command_args)
+ script = command_args.first
+ options = command_args.last.is_a?(Hash) ? command_args.last : nil
+
+ run_command_with_os_architecture(script, options)
+ end
+
+ # Run a command under powershell with the same API as shell_out!
+ # (raises exceptions on errors)
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out!(*command_args)
+ cmd = powershell_out(*command_args)
+ cmd.error!
+ cmd
+ end
+
+ private
+
+ # Helper function to run shell_out and wrap it with the correct
+ # flags to possibly disable WOW64 redirection (which we often need
+ # because chef-client runs as a 32-bit app on 64-bit windows).
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def run_command_with_os_architecture(script, options)
+ options ||= {}
+ options = options.dup
+ arch = options.delete(:architecture)
+
+ with_os_architecture(nil, architecture: arch) do
+ shell_out(
+ build_powershell_command(script),
+ options
+ )
+ end
+ end
+
+ # Helper to build a powershell command around the script to run.
+ #
+ # @param script [String] script to run
+ # @retrurn [String] powershell command to execute
+ def build_powershell_command(script)
+ flags = [
+ # Hides the copyright banner at startup.
+ "-NoLogo",
+ # Does not present an interactive prompt to the user.
+ "-NonInteractive",
+ # Does not load the Windows PowerShell profile.
+ "-NoProfile",
+ # always set the ExecutionPolicy flag
+ # see http://technet.microsoft.com/en-us/library/ee176961.aspx
+ "-ExecutionPolicy Unrestricted",
+ # Powershell will hang if STDIN is redirected
+ # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
+ "-InputFormat None"
+ ]
+
+ "powershell.exe #{flags.join(' ')} -Command \"#{script}\""
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
index bc25af62b2..095e273dab 100644
--- a/lib/chef/mixin/provides.rb
+++ b/lib/chef/mixin/provides.rb
@@ -4,29 +4,23 @@ require 'chef/mixin/descendants_tracker'
class Chef
module Mixin
module Provides
+ # TODO no longer needed, remove or deprecate?
include Chef::Mixin::DescendantsTracker
- def node_map
- @node_map ||= Chef::NodeMap.new
+ def provides(short_name, opts={}, &block)
+ raise NotImplementedError, :provides
end
- def provides(short_name, opts={}, &block)
- if !short_name.kind_of?(Symbol)
- # YAGNI: this is probably completely unnecessary and can be removed?
- Chef::Log.deprecation "Passing a non-Symbol to Chef::Resource#provides will be removed"
- if short_name.kind_of?(String)
- short_name.downcase!
- short_name.gsub!(/\s/, "_")
- end
- short_name = short_name.to_sym
- end
- node_map.set(short_name, true, opts, &block)
+ # Check whether this resource provides the resource_name DSL for the given
+ # node. TODO remove this when we stop checking unregistered things.
+ def provides?(node, resource)
+ raise NotImplementedError, :provides?
end
- # provides a node on the resource (early binding)
- def provides?(node, resource_name)
- resource_name = resource_name.resource_name if resource_name.is_a?(Chef::Resource)
- node_map.get(node, resource_name)
+ # Get the list of recipe DSL this resource is responsible for on the given
+ # node.
+ def provided_as(node)
+ node_map.list(node)
end
end
end
diff --git a/lib/chef/mixin/unformatter.rb b/lib/chef/mixin/unformatter.rb
new file mode 100644
index 0000000000..aa5977edd7
--- /dev/null
+++ b/lib/chef/mixin/unformatter.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 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 Unformatter
+
+ def write(message)
+ data = message.match(/(\[.+?\] )?([\w]+):(.*)$/)
+ self.send(data[2].downcase.chomp.to_sym, data[3].strip)
+ rescue NoMethodError
+ self.send(:info, message)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/uris.rb b/lib/chef/mixin/uris.rb
new file mode 100644
index 0000000000..0136b55f6a
--- /dev/null
+++ b/lib/chef/mixin/uris.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Jay Mundrawala (<jdm@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 'uri'
+
+class Chef
+ module Mixin
+ module Uris
+ # uri_scheme? returns true if the string starts with
+ # scheme://
+ # For example, it will match http://foo.bar.com
+ def uri_scheme?(source)
+ # From open-uri
+ !!(%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ source)
+ end
+
+
+ def as_uri(source)
+ begin
+ URI.parse(source)
+ rescue URI::InvalidURIError
+ Chef::Log.warn("#{source} was an invalid URI. Trying to escape invalid characters")
+ URI.parse(URI.escape(source))
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index a0ac34f627..c5f3e1bd79 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -42,7 +42,7 @@ class Chef
is_i386_process_on_x86_64_windows?
end
- def with_os_architecture(node)
+ def with_os_architecture(node, architecture: nil)
node ||= begin
os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
ENV['PROCESSOR_ARCHITECTURE']
@@ -51,9 +51,12 @@ class Chef
n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386
end
end
+
+ architecture ||= node_windows_architecture(node)
+
wow64_redirection_state = nil
- if wow64_architecture_override_required?(node, node_windows_architecture(node))
+ if wow64_architecture_override_required?(node, architecture)
wow64_redirection_state = disable_wow64_file_redirection(node)
end
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index 490b235065..a126801a28 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -21,11 +21,11 @@ require 'chef/exceptions'
require 'chef/platform/query_helpers'
require 'chef/win32/error' if Chef::Platform.windows?
require 'chef/win32/api/system' if Chef::Platform.windows?
+require 'chef/win32/api/unicode' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsEnvHelper
-
if Chef::Platform.windows?
include Chef::ReservedNames::Win32::API::System
end
@@ -39,7 +39,16 @@ class Chef
def broadcast_env_change
flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG
- SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil)
+ # for why two calls, see:
+ # http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables
+ if ( SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil) == 0 )
+ 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')
+ ).address, flags, 5000, nil) == 0 )
+ Chef::ReservedNames::Win32::Error.raise!
+ end
end
def expand_path(path)
diff --git a/lib/chef/mixin/wstring.rb b/lib/chef/mixin/wstring.rb
new file mode 100644
index 0000000000..bb6fdf4884
--- /dev/null
+++ b/lib/chef/mixin/wstring.rb
@@ -0,0 +1,31 @@
+#
+# 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
+ str.to_wstring
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 9823185ede..d5078371c5 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -77,13 +77,30 @@ class Chef
@run_state = {}
end
+ # after the run_context has been set on the node, go through the cookbook_collection
+ # and setup the node[:cookbooks] attribute so that it is published in the node object
+ def set_cookbook_attribute
+ return unless run_context.cookbook_collection
+ run_context.cookbook_collection.each do |cookbook_name, cookbook|
+ automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version
+ end
+ end
+
# Used by DSL
def node
self
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ # for saving node data we use validate_utf8: false which will not
+ # raise an exception on bad utf8 data, but will replace the bad
+ # characters and render valid JSON.
+ @chef_server_rest ||= Chef::REST.new(
+ Chef::Config[:chef_server_url],
+ Chef::Config[:node_name],
+ Chef::Config[:client_key],
+ validate_utf8: false,
+ )
end
# Set the name of this Node, or return the current name.
@@ -244,6 +261,7 @@ class Chef
# saved back to the node and be searchable
def loaded_recipe(cookbook, recipe)
fully_qualified_recipe = "#{cookbook}::#{recipe}"
+
automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe)
end
@@ -354,7 +372,8 @@ class Chef
self.tags # make sure they're defined
- automatic_attrs[:recipes] = expansion.recipes
+ automatic_attrs[:recipes] = expansion.recipes.with_fully_qualified_names_and_version_constraints
+ automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
automatic_attrs[:roles] = expansion.roles
apply_expansion_attributes(expansion)
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 2ca6d9ba17..f547018a38 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -19,128 +19,183 @@
class Chef
class NodeMap
- VALID_OPTS = [
- :on_platform,
- :on_platforms,
- :platform,
- :os,
- :platform_family,
- ]
-
- DEPRECATED_OPTS = [
- :on_platform,
- :on_platforms,
- ]
-
+ #
# 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.
#
# @param key [Object] Key to store
# @param value [Object] Value associated with the key
# @param filters [Hash] Node filter options to apply to key retrieval
+ #
# @yield [node] Arbitrary node filter as a block which takes a node argument
+ #
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, filters = {}, &block)
- validate_filter!(filters)
- deprecate_filter!(filters)
+ 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
+ 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] ||= []
- # we match on the first value we find, so we want to unshift so that the
- # last setter wins
- # FIXME: need a test for this behavior
- @map[key].unshift({ filters: filters, block: block, value: value })
+ # 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.)
+ insert_at = nil
+ @map[key].each_with_index do |matcher, index|
+ if specificity(new_matcher) >= specificity(matcher)
+ insert_at = index
+ break
+ end
+ end
+ if insert_at
+ @map[key].insert(insert_at, new_matcher)
+ else
+ @map[key] << new_matcher
+ end
self
end
+ #
# Get a value from the NodeMap via applying the node to the filters that
# were set on the key.
#
- # @param node [Chef::Node] The Chef::Node object for the run
+ # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+ # ignore all filters.
# @param key [Object] Key to look up
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
# @return [Object] Value
#
- def get(node, key)
- # FIXME: real exception
- raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node)
- return nil unless @map.has_key?(key)
- @map[key].each do |matcher|
- if filters_match?(node, matcher[:filters]) &&
- block_matches?(node, matcher[:block])
- return matcher[:value]
+ def get(node, key, canonical: nil)
+ raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
+ list(node, key, canonical: canonical).first
+ end
+
+ #
+ # List all matches for the given node and key from the NodeMap, from
+ # most-recently added to oldest.
+ #
+ # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+ # ignore all filters.
+ # @param key [Object] Key to look up
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
+ # @return [Object] Value
+ #
+ 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|
+ node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
+ end.map { |matcher| matcher[:value] }
+ end
+
+ # Seriously, don't use this, it's nearly certain to change on you
+ # @return remaining
+ # @api private
+ def delete_canonical(key, value)
+ remaining = @map[key]
+ if remaining
+ remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
+ if remaining.empty?
+ @map.delete(key)
+ remaining = nil
end
end
- nil
+ remaining
end
private
- # only allow valid filter options
- def validate_filter!(filters)
- filters.each_key do |key|
- # FIXME: real exception
- raise "Bad key #{key} in Chef::NodeMap filter expression" unless VALID_OPTS.include?(key)
+ #
+ # 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
- # warn on deprecated filter options
- def deprecate_filter!(filters)
- filters.each_key do |key|
- Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key)
- end
- end
+ #
+ # Succeeds if:
+ # - no negative matches (!value)
+ # - at least one positive match (value or :all), or no positive filters
+ #
+ def matches_black_white_list?(node, filters, attribute)
+ # It's super common for the filter to be nil. Catch that so we don't
+ # spend any time here.
+ return true if !filters[attribute]
+ filter_values = Array(filters[attribute])
+ value = node[attribute]
- # @todo: this works fine, but is probably hard to understand
- def negative_match(filter, param)
- # We support strings prefaced by '!' to mean 'not'. In particular, this is most useful
- # for os matching on '!windows'.
- negative_matches = filter.select { |f| f[0] == '!' }
- return true if !negative_matches.empty? && negative_matches.include?('!' + param)
+ # Split the blacklist and whitelist
+ blacklist, whitelist = filter_values.partition { |v| v.is_a?(String) && v.start_with?('!') }
- # We support the symbol :all to match everything, for backcompat, but this can and should
- # simply be ommitted.
- positive_matches = filter.reject { |f| f[0] == '!' || f == :all }
- return true if !positive_matches.empty? && !positive_matches.include?(param)
+ # If any blacklist value matches, we don't match
+ return false if blacklist.any? { |v| v[1..-1] == value }
- # sorry double-negative: this means we pass this filter.
- false
+ # If the whitelist is empty, or anything matches, we match.
+ whitelist.empty? || whitelist.any? { |v| v == :all || v == value }
end
- def filters_match?(node, filters)
- return true if filters.empty?
+ def matches_version_list?(node, filters, attribute)
+ # It's super common for the filter to be nil. Catch that so we don't
+ # spend any time here.
+ return true if !filters[attribute]
+ filter_values = Array(filters[attribute])
+ value = node[attribute]
- # each filter is applied in turn. if any fail, then it shortcuts and returns false.
- # if it passes or does not exist it succeeds and continues on. so multiple filters are
- # effectively joined by 'and'. all filters can be single strings, or arrays which are
- # effectively joined by 'or'.
-
- os_filter = [ filters[:os] ].flatten.compact
- unless os_filter.empty?
- return false if negative_match(os_filter, node[:os])
- end
-
- platform_family_filter = [ filters[:platform_family] ].flatten.compact
- unless platform_family_filter.empty?
- return false if negative_match(platform_family_filter, node[:platform_family])
- end
-
- # :on_platform and :on_platforms here are synonyms which are deprecated
- platform_filter = [ filters[:platform] || filters[:on_platform] || filters[:on_platforms] ].flatten.compact
- unless platform_filter.empty?
- return false if negative_match(platform_filter, node[:platform])
+ filter_values.empty? ||
+ Array(filter_values).any? do |v|
+ Chef::VersionConstraint::Platform.new(v).include?(value)
end
+ end
- return true
+ def filters_match?(node, filters)
+ matches_black_white_list?(node, filters, :os) &&
+ matches_black_white_list?(node, filters, :platform_family) &&
+ matches_black_white_list?(node, filters, :platform) &&
+ matches_version_list?(node, filters, :platform_version)
end
def block_matches?(node, block)
return true if block.nil?
block.call node
end
+
+ def node_matches?(node, matcher)
+ return true if !node
+ filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block])
+ end
+
+ def canonical_matches?(canonical, matcher)
+ return true if canonical.nil?
+ !!canonical == !!matcher[:canonical]
+ end
end
end
diff --git a/lib/chef/osc_user.rb b/lib/chef/osc_user.rb
new file mode 100644
index 0000000000..52bfd11108
--- /dev/null
+++ b/lib/chef/osc_user.rb
@@ -0,0 +1,194 @@
+#
+# 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/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index 0d7285729f..e3a894c8ac 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -20,13 +20,8 @@ require 'chef/log'
require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/version_constraint/platform'
-
-# This file depends on nearly every provider in chef, but requiring them
-# directly causes circular requires resulting in uninitialized constant errors.
-# Therefore, we do the includes inline rather than up top.
require 'chef/provider'
-
class Chef
class Platform
@@ -34,267 +29,7 @@ class Chef
attr_writer :platforms
def platforms
- @platforms ||= begin
- require 'chef/providers'
-
- {
- :freebsd => {
- :default => {
- :group => Chef::Provider::Group::Pw,
- :user => Chef::Provider::User::Pw,
- }
- },
- :ubuntu => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- },
- ">= 11.10" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- # Chef::Provider::Service::Upstart is a candidate to be used in
- # ubuntu versions >= 13.10 but it currently requires all the
- # services to have an entry under /etc/init. We need to update it
- # to use the service ctl apis in order to migrate to using it on
- # ubuntu >= 13.10.
- },
- :gcel => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :linaro => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :raspbian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :linuxmint => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Upstart,
- }
- },
- :debian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- },
- ">= 6.0" => {
- :service => Chef::Provider::Service::Insserv
- },
- ">= 7.0" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- },
- :xenserver => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :xcp => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :centos => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :amazon => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :scientific => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :fedora => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 15" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :opensuse => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Suse
- },
- # Only OpenSuSE 12.3+ should use the Usermod group provider:
- ">= 12.3" => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :suse => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Gpasswd
- },
- "< 12.0" => {
- :group => Chef::Provider::Group::Suse,
- :service => Chef::Provider::Service::Redhat
- }
- },
- :oracle => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :redhat => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :ibm_powerkvm => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :cloudlinux => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :parallels => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :gentoo => {
- :default => {
- :package => Chef::Provider::Package::Portage,
- :service => Chef::Provider::Service::Gentoo,
- }
- },
- :arch => {
- :default => {
- :package => Chef::Provider::Package::Pacman,
- :service => Chef::Provider::Service::Systemd,
- }
- },
- :solaris => {},
- :openindiana => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :opensolaris => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :nexentacore => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :omnios => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :solaris2 => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- },
- "< 5.11" => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :smartos => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::SmartOS,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :hpux => {
- :default => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :aix => {
- :default => {
- :group => Chef::Provider::Group::Aix,
- :mount => Chef::Provider::Mount::Aix,
- :ifconfig => Chef::Provider::Ifconfig::Aix,
- :package => Chef::Provider::Package::Aix,
- :user => Chef::Provider::User::Aix,
- :service => Chef::Provider::Service::Aix
- }
- },
- :exherbo => {
- :default => {
- :package => Chef::Provider::Package::Paludis,
- :service => Chef::Provider::Service::Systemd,
- }
- },
- :default => {
- :mount => Chef::Provider::Mount::Mount,
- :user => Chef::Provider::User::Useradd,
- :group => Chef::Provider::Group::Gpasswd,
- :ifconfig => Chef::Provider::Ifconfig,
- }
- }
- end
+ @platforms ||= { default: {} }
end
include Chef::Mixin::ParamsValidate
@@ -304,7 +39,7 @@ class Chef
name_sym = name
if name.kind_of?(String)
- name.downcase!
+ name = name.downcase
name.gsub!(/\s/, "_")
name_sym = name.to_sym
end
@@ -325,8 +60,6 @@ class Chef
Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}")
end
end
- else
- Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
end
provider_map
end
@@ -460,16 +193,20 @@ class Chef
pmap.has_key?(rtkey) ? pmap[rtkey] : nil
end
+ include Chef::Mixin::ConvertToClassName
+
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
+ class_name = resource_type.class.to_s.split('::').last
+
begin
- Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
+ result = Chef::Provider.const_get(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
- nil
end
- else
- nil
end
+ result
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 1539f61900..9d703c9178 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,88 +1,25 @@
+require 'singleton'
class Chef
class Platform
class ProviderPriorityMap
include Singleton
- def initialize
- load_default_map
- end
-
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)
- priority(resource_name.to_sym, priority_array.to_a, *filter)
+ def set_priority_array(resource_name, priority_array, *filter, &block)
+ priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
end
- def priority(*args)
- priority_map.set(*args)
+ # @api private
+ def list_handlers(node, resource_name)
+ priority_map.list(node, resource_name.to_sym).flatten(1).uniq
end
private
- def load_default_map
- require 'chef/providers'
-
- #
- # Linux
- #
-
- # default block for linux O/Sen must come before platform_family exceptions
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Redhat,
- ], os: "linux"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Arch,
- ], platform_family: "arch"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Gentoo,
- ], platform_family: "gentoo"
-
- priority :service, [
- # we can determine what systemd supports accurately
- Chef::Provider::Service::Systemd,
- # on debian-ish system if an upstart script exists that must win over sysv types
- Chef::Provider::Service::Upstart,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Debian,
- Chef::Provider::Service::Invokercd,
- ], platform_family: "debian"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Redhat,
- ], platform_family: [ "rhel", "fedora", "suse" ]
-
- #
- # BSDen
- #
-
- priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ]
- priority :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ]
-
- #
- # Solaris-en
- #
-
- priority :service, Chef::Provider::Service::Solaris, os: "solaris2"
-
- #
- # Mac
- #
-
- priority :service, Chef::Provider::Service::Macosx, os: "darwin"
- priority :package, Chef::Provider::Package::Homebrew, os: "darwin"
- end
-
def priority_map
require 'chef/node_map'
@priority_map ||= Chef::NodeMap.new
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index f7c85fbe23..b3948eac21 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -21,11 +21,7 @@ class Chef
class << self
def windows?
- if RUBY_PLATFORM =~ /mswin|mingw|windows/
- true
- else
- false
- end
+ ChefConfig.windows?
end
def windows_server_2003?
@@ -43,6 +39,11 @@ class Chef
is_server_2003
end
+ def supports_powershell_execution_bypass?(node)
+ node[:languages] && node[:languages][:powershell] &&
+ node[:languages][:powershell][:version].to_i >= 3
+ end
+
def supports_dsc?(node)
node[:languages] && node[:languages][:powershell] &&
node[:languages][:powershell][:version].to_i >= 4
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fc43b3e7db..fb08debc53 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,33 +1,30 @@
+require 'singleton'
+
class Chef
class Platform
class ResourcePriorityMap
include Singleton
- def initialize
- load_default_map
+ def get_priority_array(node, resource_name, canonical: nil)
+ priority_map.get(node, resource_name.to_sym, canonical: canonical)
end
- def get_priority_array(node, resource_name)
- priority_map.get(node, resource_name.to_sym)
+ def set_priority_array(resource_name, priority_array, *filter, &block)
+ priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
end
- def set_priority_array(resource_name, priority_array, *filter)
- priority resource_name.to_sym, priority_array.to_a, *filter
+ # @api private
+ def delete_canonical(resource_name, resource_class)
+ priority_map.delete_canonical(resource_name, resource_class)
end
- def priority(*args)
- priority_map.set(*args)
+ # @api private
+ def list_handlers(*args)
+ priority_map.list(*args).flatten(1).uniq
end
private
- def load_default_map
- require 'chef/resources'
-
- # MacOSX
- priority :package, Chef::Resource::HomebrewPackage, os: "darwin"
- end
-
def priority_map
require 'chef/node_map'
@priority_map ||= Chef::NodeMap.new
diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb
index dc0a808c06..d50812e687 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -42,34 +42,34 @@ class Chef
# different services is NOT a design concern of this module.
#
def service_resource_providers
- service_resource_providers = []
+ @service_resource_providers ||= [].tap do |service_resource_providers|
- if ::File.exist?("/usr/sbin/update-rc.d")
- service_resource_providers << :debian
- end
+ if ::File.exist?("/usr/sbin/update-rc.d")
+ service_resource_providers << :debian
+ end
- if ::File.exist?("/usr/sbin/invoke-rc.d")
- service_resource_providers << :invokercd
- end
+ if ::File.exist?("/usr/sbin/invoke-rc.d")
+ service_resource_providers << :invokercd
+ end
- if ::File.exist?("/sbin/insserv")
- service_resource_providers << :insserv
- end
+ if ::File.exist?("/sbin/insserv")
+ service_resource_providers << :insserv
+ end
- # debian >= 6.0 has /etc/init but does not have upstart
- if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
- service_resource_providers << :upstart
- end
+ # debian >= 6.0 has /etc/init but does not have upstart
+ if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
+ service_resource_providers << :upstart
+ end
- if ::File.exist?("/sbin/chkconfig")
- service_resource_providers << :redhat
- end
+ if ::File.exist?("/sbin/chkconfig")
+ service_resource_providers << :redhat
+ end
- if systemd_sanity_check?
- service_resource_providers << :systemd
- end
+ if systemd_sanity_check?
+ service_resource_providers << :systemd
+ end
- service_resource_providers
+ end
end
def config_for_service(service_name)
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index ac25b549be..5991e3ce10 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -119,6 +119,7 @@ class Chef
@node = Chef::Node.find_or_create(node_name)
validate_policyfile
+ events.policyfile_loaded(policy)
node
rescue Exception => e
events.node_load_failed(node_name, e, Chef::Config)
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 65a56cf726..e50e374804 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,14 +22,19 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
class Chef
class Provider
+ require 'chef/mixin/why_run'
+ require 'chef/mixin/shell_out'
+ require 'chef/mixin/provides'
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
extend Chef::Mixin::Provides
# supports the given resource and action (late binding)
@@ -83,6 +88,9 @@ class Chef
new_resource.cookbook_name
end
+ def check_resource_semantics!
+ end
+
def load_current_resource
raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}"
end
@@ -108,6 +116,8 @@ class Chef
# TODO: it would be preferable to get the action to be executed in the
# constructor...
+ 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?
load_current_resource
@@ -165,6 +175,14 @@ class Chef
converge_actions.add_action(descriptions, &block)
end
+ def self.provides(short_name, opts={}, &block)
+ Chef.set_provider_priority_array(short_name, self, opts, &block)
+ end
+
+ def self.provides?(node, resource)
+ Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
+ end
+
protected
def converge_actions
@@ -191,5 +209,39 @@ class Chef
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.")
+ deprecated_constants[class_name.to_sym]
+ else
+ raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
+ end
+ end
+
+ # @api private
+ def register_deprecated_lwrp_class(provider_class, class_name)
+ # Register Chef::Provider::MyProvider with deprecation warnings if you
+ # try to access it
+ if Chef::Provider.const_defined?(class_name, false)
+ Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}"
+ else
+ deprecated_constants[class_name.to_sym] = provider_class
+ end
+ end
+
+ private
+
+ def deprecated_constants
+ @deprecated_constants ||= {}
+ end
+ end
+ extend DeprecatedLWRPClass
end
end
+
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
+require 'chef/mixin/why_run'
+require 'chef/resource_collection'
+require 'chef/runner'
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 0750c0420b..01c61e4253 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -20,6 +20,7 @@
require 'chef/log'
require 'chef/provider'
+require 'chef/provider/cron'
class Chef
class Provider
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 416393ac60..4d5423d0e8 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -43,6 +43,9 @@ class Chef
end
def define_resource_requirements
+ # deep inside FAC we have to assert requirements, so call FACs hook to set that up
+ access_controls.define_resource_requirements
+
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 2812c154c6..5fa84a21e9 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -121,7 +121,14 @@ class Chef
# however Invoke-DscResource is not correctly writing to that
# stream and instead just dumping to stdout
@converge_description = result.stdout
- result.return_value[0]["InDesiredState"]
+
+ if result.return_value.is_a?(Array)
+ # WMF Feb 2015 Preview
+ result.return_value[0]["InDesiredState"]
+ else
+ # WMF April 2015 Preview
+ result.return_value["InDesiredState"]
+ end
end
def set_resource
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index c070d29458..5ed7c6ac5b 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -26,8 +26,10 @@ require 'fileutils'
require 'chef/scan_access_control'
require 'chef/mixin/checksum'
require 'chef/mixin/file_class'
+require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/util/backup'
require 'chef/util/diff'
+require 'chef/util/selinux'
require 'chef/deprecation/provider/file'
require 'chef/deprecation/warnings'
require 'chef/file_content_management/deploy'
@@ -386,10 +388,11 @@ class Chef
def update_file_contents
do_backup unless needs_creating?
- deployment_strategy.deploy(tempfile.path, ::File.realpath(@new_resource.path))
- Chef::Log.info("#{@new_resource} updated file contents #{@new_resource.path}")
+ deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path))
+ Chef::Log.info("#{new_resource} updated file contents #{new_resource.path}")
if managing_content?
- @new_resource.checksum(checksum(@new_resource.path)) # for reporting
+ # save final checksum for reporting.
+ new_resource.final_checksum = checksum(new_resource.path)
end
end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 6ac9d03357..92bb8cb225 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -23,6 +23,7 @@ class Chef
class Provider
class Group
class Aix < Chef::Provider::Group::Groupadd
+ provides :group, platform: 'aix'
def required_binaries
[ "/usr/bin/mkgroup",
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index d7e8f2e827..9775ac8270 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -21,7 +21,7 @@ class Chef
class Group
class Dscl < Chef::Provider::Group
- provides :group, os: "darwin"
+ provides :group, os: 'darwin'
def dscl(*args)
host = "."
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index 521affac11..432c524acd 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Group
class Gpasswd < Chef::Provider::Group::Groupadd
+ provides :group
def load_current_resource
super
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index f9299546c8..82b68b8672 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -21,7 +21,7 @@ class Chef
class Group
class Groupmod < Chef::Provider::Group
- provides :group, os: "netbsd"
+ provides :group, os: 'netbsd'
def load_current_resource
super
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 7a66ab4d69..f877ed2424 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -20,6 +20,7 @@ class Chef
class Provider
class Group
class Pw < Chef::Provider::Group
+ provides :group, platform: 'freebsd'
def load_current_resource
super
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index 7ac2831d02..b47ea33e80 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -22,6 +22,8 @@ class Chef
class Provider
class Group
class Suse < Chef::Provider::Group::Groupadd
+ provides :group, platform: 'opensuse', platform_version: '< 12.3'
+ provides :group, platform: 'suse', platform_version: '< 12.0'
def load_current_resource
super
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index e50e13c443..d78d42d6e1 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -23,7 +23,8 @@ class Chef
class Group
class Usermod < Chef::Provider::Group::Groupadd
- provides :group, os: "openbsd"
+ provides :group, os: %w(openbsd solaris2 hpux)
+ provides :group, platform: "opensuse"
def load_current_resource
super
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 54e49b0e06..46d8afc7f6 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -26,7 +26,7 @@ class Chef
class Group
class Windows < Chef::Provider::Group
- provides :group, os: "windows"
+ provides :group, os: 'windows'
def initialize(new_resource,run_context)
super
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 06080c90c3..468e1ec639 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -39,6 +39,8 @@ require 'erb'
class Chef
class Provider
class Ifconfig < Chef::Provider
+ provides :ifconfig
+
include Chef::Mixin::ShellOut
include Chef::Mixin::Command
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 8fead44bc6..25c3de3040 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Ifconfig
class Aix < Chef::Provider::Ifconfig
+ provides :ifconfig, platform: %w(aix)
def load_current_resource
@current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 7589971143..1e6863c8b5 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -23,6 +23,8 @@ class Chef
class Provider
class Ifconfig
class Debian < Chef::Provider::Ifconfig
+ provides :ifconfig, platform: %w(ubuntu), platform_version: '>= 11.10'
+ provides :ifconfig, platform: %w(debian), platform_version: '>= 7.0'
INTERFACES_FILE = "/etc/network/interfaces"
INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
index ef35b0e012..ee053d1e52 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Ifconfig
class Redhat < Chef::Provider::Ifconfig
+ provides :ifconfig, platform_family: %w(fedora rhel)
def initialize(new_resource, run_context)
super(new_resource, run_context)
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 492ddda6da..b5efbb284d 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -19,6 +19,7 @@
#
require 'chef/provider'
+require 'chef/dsl/include_recipe'
class Chef
class Provider
@@ -69,9 +70,6 @@ class Chef
end
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
@@ -80,71 +78,95 @@ class Chef
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
- def self.build_from_file(cookbook_name, filename, run_context)
- provider_class = nil
- provider_name = filename_to_qualified_string(cookbook_name, filename)
+ # Allow include_recipe from within LWRP provider code
+ include Chef::DSL::IncludeRecipe
+
+ # no-op `load_current_resource`. Allows simple LWRP providers to work
+ # without defining this method explicitly (silences
+ # Chef::Exceptions::Override exception)
+ def load_current_resource
+ end
+
+ # class methods
+ class <<self
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
+
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.info("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ return loaded_lwrps[filename]
+ end
- class_name = convert_to_class_name(provider_name)
+ resource_name = filename_to_qualified_string(cookbook_name, filename)
- if Chef::Provider.const_defined?(class_name, false)
- Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!")
- Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
- provider_class = Chef::Provider.const_get(class_name)
- else
+ # We load the class first to give it a chance to set its own name
provider_class = Class.new(self)
- Chef::Provider.const_set(class_name, provider_class)
+ provider_class.provides resource_name.to_sym
provider_class.class_from_file(filename)
- Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
- end
- provider_class
- end
+ # Respect resource_name set inside the LWRP
+ provider_class.instance_eval do
+ define_singleton_method(:to_s) do
+ "LWRP provider #{resource_name} from cookbook #{cookbook_name}"
+ end
+ define_singleton_method(:inspect) { to_s }
+ 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 self.use_inline_resources
- extend InlineResources::ClassMethods
- include InlineResources
- end
+ Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
+
+ LWRPBase.loaded_lwrps[filename] = true
- # DSL for defining a provider's actions.
- def self.action(name, &block)
- define_method("action_#{name}") do
- instance_eval(&block)
+ Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name))
+
+ provider_class
end
- end
- # no-op `load_current_resource`. Allows simple LWRP providers to work
- # without defining this method explicitly (silences
- # Chef::Exceptions::Override exception)
- def load_current_resource
- 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
+ instance_eval(&block)
+ end
+ end
+
+ protected
+ def loaded_lwrps
+ @loaded_lwrps ||= {}
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 1631d87033..2039e9ae51 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -24,7 +24,6 @@ require 'chef/provider'
class Chef
class Provider
class Mount < Chef::Provider
-
include Chef::Mixin::ShellOut
attr_accessor :unmount_retries
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 0d7e11a1b8..4ad7b24c15 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Mount
class Aix < Chef::Provider::Mount::Mount
+ provides :mount, platform: %w(aix)
# Override for aix specific handling
def initialize(new_resource, run_context)
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 0a6e269d2d..ef074166a9 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -24,6 +24,8 @@ class Chef
class Mount
class Mount < Chef::Provider::Mount
+ provides :mount
+
def initialize(new_resource, run_context)
super
@real_device = nil
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index d8cec24138..deb04d4d7b 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -27,6 +27,8 @@ class Chef
class Mount
# Mount Solaris File systems
class Solaris < Chef::Provider::Mount
+ provides :mount, platform: %w(openindiana opensolaris nexentacore omnios solaris2 smartos)
+
extend Forwardable
VFSTAB = '/etc/vfstab'.freeze
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index a6b5ab5daa..b7f4aa704b 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -21,6 +21,7 @@ require 'ohai'
class Chef
class Provider
class Ohai < Chef::Provider
+ provides :ohai
def whyrun_supported?
true
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 2e8e29981b..9d534ec414 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -43,6 +43,12 @@ class Chef
true
end
+ def check_resource_semantics!
+ if new_resource.package_name.is_a?(Array) && new_resource.source != nil
+ raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
+ end
+ end
+
def load_current_resource
end
@@ -464,10 +470,7 @@ class Chef
# @return [Array] new_version(s) as an array
def new_version_array
- @new_version_array ||=
- [ new_resource.version ].flatten.map do |v|
- ( v.nil? || v.empty? ) ? nil : v
- end
+ [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
end
# @todo: extract apt/dpkg specific preseeding to a helper class
@@ -487,6 +490,61 @@ class Chef
false
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)
+ shell_out(*add_timeout_option(command_args))
+ end
+
+ def shell_out_with_timeout!(*command_args)
+ shell_out!(*add_timeout_option(command_args))
+ end
+
+ def add_timeout_option(command_args)
+ args = command_args.dup
+ if args.last.is_a?(Hash)
+ options = args.pop.dup
+ options[:timeout] = new_resource.timeout if new_resource.timeout
+ options[:timeout] = 900 unless options.has_key?(:timeout)
+ args << options
+ else
+ args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+ end
+ args
+ end
+
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index 107f914c05..b97db9d061 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -52,7 +52,7 @@ class Chef
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- ret = shell_out("installp -L -d #{@new_resource.source}")
+ ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
ret.stdout.each_line do | line |
case line
when /#{@new_resource.package_name}:/
@@ -60,11 +60,12 @@ class Chef
@new_resource.version(fields[2])
end
end
+ raise Chef::Exceptions::Package, "package source #{@new_resource.source} does not provide package #{@new_resource.package_name}" unless @new_resource.version
end
end
Chef::Log.debug("#{@new_resource} checking install state")
- ret = shell_out("lslpp -lcq #{@current_resource.package_name}")
+ ret = shell_out_with_timeout("lslpp -lcq #{@current_resource.package_name}")
ret.stdout.each_line do | line |
case line
when /#{@current_resource.package_name}/
@@ -83,7 +84,7 @@ class Chef
def candidate_version
return @candidate_version if @candidate_version
- ret = shell_out("installp -L -d #{@new_resource.source}")
+ ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
ret.stdout.each_line do | line |
case line
when /\w:#{Regexp.escape(@new_resource.package_name)}:(.*)/
@@ -109,10 +110,10 @@ class Chef
def install_package(name, version)
Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
if @new_resource.options.nil?
- shell_out!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
+ shell_out_with_timeout!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
- shell_out!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
+ shell_out_with_timeout!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
@@ -121,10 +122,10 @@ class Chef
def remove_package(name, version)
if @new_resource.options.nil?
- shell_out!( "installp -u #{name}" )
+ shell_out_with_timeout!( "installp -u #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- shell_out!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index e426b51992..bd6ed283bf 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -62,7 +62,7 @@ class Chef
installed_version = nil
candidate_version = nil
- shell_out!("apt-cache#{expand_options(default_release_options)} policy #{pkg}", {:timeout=>900}).stdout.each_line do |line|
+ shell_out_with_timeout!("apt-cache#{expand_options(default_release_options)} policy #{pkg}").stdout.each_line do |line|
case line
when /^\s{2}Installed: (.+)$/
installed_version = $1
@@ -78,7 +78,7 @@ class Chef
if candidate_version == '(none)'
# This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
is_virtual_package = true
- showpkg = shell_out!("apt-cache showpkg #{pkg}", {:timeout => 900}).stdout
+ showpkg = shell_out_with_timeout!("apt-cache showpkg #{pkg}").stdout
providers = Hash.new
showpkg.rpartition(/Reverse Provides: ?#{$/}/)[2].each_line do |line|
provider, version = line.split
@@ -175,7 +175,7 @@ class Chef
# interactive prompts. Command is run with default localization rather
# than forcing locale to "C", so command output may not be stable.
def run_noninteractive(command)
- shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil }, :timeout => @new_resource.timeout)
+ shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
end
end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index 11691a2479..a262f1ab1a 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -62,7 +62,7 @@ class Chef
# Get information from the package if supplied
Chef::Log.debug("#{@new_resource} checking dpkg status")
- shell_out("dpkg-deb -W #{@new_resource.source}").stdout.each_line do |line|
+ 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])
@@ -79,7 +79,7 @@ class Chef
# Check to see if it is installed
package_installed = nil
Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out("dpkg -s #{@current_resource.package_name}")
+ status = shell_out_with_timeout("dpkg -s #{@current_resource.package_name}")
status.stdout.each_line do |line|
case line
when DPKG_INSTALLED
@@ -134,13 +134,13 @@ class Chef
run_noninteractive("dpkg-reconfigure #{name}")
end
- # Runs command via shell_out with magic environment to disable
+ # Runs command via shell_out_with_timeout with magic environment to disable
# interactive prompts. Command is run with default localization rather
# than forcing locale to "C", so command output may not be stable.
#
# FIXME: This should be "LC_ALL" => "en_US.UTF-8" in order to stabilize the output and get UTF-8
def run_noninteractive(command)
- shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+ shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
end
end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
index 90727b738d..2f7880bf08 100644
--- a/lib/chef/provider/package/easy_install.rb
+++ b/lib/chef/provider/package/easy_install.rb
@@ -32,10 +32,10 @@ class Chef
begin
# first check to see if we can import it
- output = shell_out!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
if output.include? "ImportError"
# then check to see if its on the path
- output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
if output.downcase.include? "#{name.downcase}"
check = true
end
@@ -73,10 +73,10 @@ class Chef
package_version = nil
if install_check(module_name)
begin
- output = shell_out!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
package_version = output.strip
rescue
- output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
output_array = output.gsub(/[\[\]]/,'').split(/\s*,\s*/)
package_path = ""
@@ -107,7 +107,7 @@ class Chef
return @candidate_version if @candidate_version
# do a dry run to get the latest version
- result = shell_out!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
+ result = shell_out_with_timeout!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
@candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
@candidate_version
end
diff --git a/lib/chef/provider/package/freebsd/base.rb b/lib/chef/provider/package/freebsd/base.rb
index 6a3b97a4fd..7c032b3787 100644
--- a/lib/chef/provider/package/freebsd/base.rb
+++ b/lib/chef/provider/package/freebsd/base.rb
@@ -47,7 +47,7 @@ class Chef
# Otherwise look up the path to the ports directory using 'whereis'
else
- whereis = shell_out!("whereis -s #{port}", :env => nil)
+ whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil)
unless path = whereis.stdout[/^#{Regexp.escape(port)}:\s+(.+)$/, 1]
raise Chef::Exceptions::Package, "Could not find port with the name #{port}"
end
@@ -57,7 +57,7 @@ class Chef
def makefile_variable_value(variable, dir = nil)
options = dir ? { :cwd => dir } : {}
- make_v = shell_out!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
+ make_v = shell_out_with_timeout!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
make_v.exitstatus.zero? ? make_v.stdout.strip.split($\).first : nil # $\ is the line separator, i.e. newline.
end
end
diff --git a/lib/chef/provider/package/freebsd/pkg.rb b/lib/chef/provider/package/freebsd/pkg.rb
index ebbfbb19b4..33a8c2c108 100644
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ b/lib/chef/provider/package/freebsd/pkg.rb
@@ -34,24 +34,24 @@ class Chef
case @new_resource.source
when /^http/, /^ftp/
if @new_resource.source =~ /\/$/
- shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
else
- shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
end
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
when /^\//
- shell_out!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
+ shell_out_with_timeout!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
else
- shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
+ shell_out_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status
end
end
end
def remove_package(name, version)
- shell_out!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
+ shell_out_with_timeout!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
end
# The name of the package (without the version number) as understood by pkg_add and pkg_info.
@@ -72,7 +72,7 @@ class Chef
end
def current_installed_version
- pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
+ pkg_info = shell_out_with_timeout!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1]
end
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index bfe6dca617..2fdc9dda71 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -28,11 +28,11 @@ class Chef
unless @current_resource.version
case @new_resource.source
when /^(http|ftp|\/)/
- shell_out!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
else
- shell_out!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
end
end
end
@@ -40,11 +40,11 @@ class Chef
def remove_package(name, version)
options = @new_resource.options && @new_resource.options.sub(repo_regex, '')
options && !options.empty? || options = nil
- shell_out!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
+ shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
end
def current_installed_version
- pkg_info = shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+ pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
pkg_info.stdout[/^Version +: (.+)$/, 1]
end
@@ -63,7 +63,7 @@ class Chef
options = $1
end
- pkg_query = shell_out!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
+ pkg_query = shell_out_with_timeout!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
pkg_query.exitstatus.zero? ? pkg_query.stdout.strip.split(/\n/).last : nil
end
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 8b191179f0..3fbd002214 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -26,18 +26,18 @@ class Chef
include PortsHelper
def install_package(name, version)
- shell_out!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
+ shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
end
def remove_package(name, version)
- shell_out!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
+ shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
end
def current_installed_version
pkg_info = if @new_resource.supports_pkgng?
- shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+ shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
else
- shell_out!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
+ shell_out_with_timeout!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
end
pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1]
end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index 603899646f..beede1c916 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -27,7 +27,6 @@ class Chef
class Homebrew < Chef::Provider::Package
provides :homebrew_package
- provides :package, os: "darwin"
include Chef::Mixin::HomebrewUser
@@ -126,7 +125,8 @@ class Chef
homebrew_user = Etc.getpwuid(homebrew_uid)
Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'"
- output = shell_out!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
+ # FIXME: this 1800 second default timeout should be deprecated
+ output = shell_out_with_timeout!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
output.stdout.chomp
end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 87022d770a..4d7f4a3583 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -42,14 +42,14 @@ class Chef
end
def get_current_version
- shell_out("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
return $1.split[0] if line =~ /^\s+Version: (.*)/
end
return nil
end
def get_candidate_version
- shell_out!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
return $1.split[0] if line =~ /Version: (.*)/
end
return nil
@@ -73,7 +73,7 @@ class Chef
else
normal_command
end
- shell_out(command)
+ shell_out_with_timeout(command)
end
def upgrade_package(name, version)
@@ -82,7 +82,7 @@ class Chef
def remove_package(name, version)
package_name = "#{name}@#{version}"
- shell_out!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
+ shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
end
end
end
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index b252344c99..e945211540 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -4,7 +4,6 @@ class Chef
class Macports < Chef::Provider::Package
provides :macports_package
- provides :package, os: "darwin"
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@@ -49,21 +48,21 @@ class Chef
unless @current_resource.version == version
command = "port#{expand_options(@new_resource.options)} install #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
end
def purge_package(name, version)
command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
def remove_package(name, version)
command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
def upgrade_package(name, version)
@@ -76,14 +75,14 @@ class Chef
# that hasn't been installed.
install_package(name, version)
elsif current_version != version
- shell_out!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
+ shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
end
end
private
def get_response_from_command(command)
output = nil
- status = shell_out(command)
+ status = shell_out_with_timeout(command)
begin
output = status.stdout
rescue Exception
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index 82048c3bd4..f231101390 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -22,7 +22,6 @@
require 'chef/resource/package'
require 'chef/provider/package'
-require 'chef/mixin/shell_out'
require 'chef/mixin/get_source_from_package'
require 'chef/exceptions'
@@ -72,7 +71,7 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
name = parts[1]
end
- shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
+ shell_out_with_timeout!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
Chef::Log.debug("#{new_resource.package_name} installed")
end
end
@@ -83,7 +82,7 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/)
name = parts[1]
end
- shell_out!("pkg_delete #{name}#{version_string}", :env => nil).status
+ shell_out_with_timeout!("pkg_delete #{name}#{version_string}", :env => nil).status
end
private
@@ -94,7 +93,7 @@ class Chef
else
name = new_resource.package_name
end
- pkg_info = shell_out!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
+ pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1]
Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'")
result
@@ -103,7 +102,7 @@ class Chef
def candidate_version
@candidate_version ||= begin
results = []
- shell_out!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
+ shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
else
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index f16fc811f5..bf03e54656 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -34,7 +34,7 @@ class Chef
@current_resource.version(nil)
Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
- status = shell_out("pacman -Qi #{@new_resource.package_name}")
+ status = shell_out_with_timeout("pacman -Qi #{@new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /^Version(\s?)*: (.+)$/
@@ -62,7 +62,7 @@ class Chef
package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
- status = shell_out("pacman -Sl")
+ status = shell_out_with_timeout("pacman -Sl")
status.stdout.each_line do |line|
case line
when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
@@ -85,7 +85,7 @@ class Chef
end
def install_package(name, version)
- shell_out!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def upgrade_package(name, version)
@@ -93,7 +93,7 @@ class Chef
end
def remove_package(name, version)
- shell_out!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index bb047ad2fa..4ba0160bb0 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 :portage_package
+
PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
def load_current_resource
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index f10fe23c71..21c39752d1 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -17,7 +17,6 @@
#
require 'chef/provider/package'
require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
require 'chef/resource/package'
require 'chef/mixin/get_source_from_package'
@@ -60,7 +59,7 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+ 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_.-]+)$/
@current_resource.package_name($1)
@@ -76,7 +75,7 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking install state")
- @rpm_status = shell_out("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+ @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_.-]+)$/
@@ -90,12 +89,12 @@ class Chef
def install_package(name, version)
unless @current_resource.version
- shell_out!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
else
if allow_downgrade
- shell_out!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
else
- shell_out!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
end
end
end
@@ -104,9 +103,9 @@ class Chef
def remove_package(name, version)
if version
- shell_out!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
else
- shell_out!( "rpm #{@new_resource.options} -e #{name}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" )
end
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index c53aa8934a..b5f7dbdd80 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -32,14 +32,7 @@ require 'rubygems/version'
require 'rubygems/dependency'
require 'rubygems/spec_fetcher'
require 'rubygems/platform'
-
-# Compatibility note: Rubygems 2.0 removes rubygems/format in favor of
-# rubygems/package.
-begin
- require 'rubygems/format'
-rescue LoadError
- require 'rubygems/package'
-end
+require 'rubygems/package'
require 'rubygems/dependency_installer'
require 'rubygems/uninstaller'
require 'rubygems/specification'
@@ -545,9 +538,9 @@ class Chef
src = @new_resource.source && " --source=#{@new_resource.source} --source=https://rubygems.org"
end
if !version.nil? && version.length > 0
- shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
end
end
@@ -571,9 +564,9 @@ class Chef
def uninstall_via_gem_command(name, version)
if version
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
end
end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 7cef91953a..0d5b801c96 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -43,7 +43,7 @@ class Chef
def check_package_state(name)
Chef::Log.debug("#{@new_resource} checking package #{name}")
version = nil
- info = shell_out!("/opt/local/sbin/pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
+ info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0,1])
if info.stdout
version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
@@ -60,11 +60,11 @@ class Chef
return @candidate_version if @candidate_version
name = nil
version = nil
- pkg = shell_out!("/opt/local/bin/pkgin se #{new_resource.package_name}", :env => nil, :returns => [0,1])
+ pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0,1])
pkg.stdout.each_line do |line|
case line
when /^#{new_resource.package_name}/
- name, version = line.split[0].split(/-([^-]+)$/)
+ name, version = line.split(/[; ]/)[0].split(/-([^-]+)$/)
end
end
@candidate_version = version
@@ -74,7 +74,7 @@ class Chef
def install_package(name, version)
Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}")
package = "#{name}-#{version}"
- out = shell_out!("/opt/local/bin/pkgin -y install #{package}", :env => nil)
+ out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil)
end
def upgrade_package(name, version)
@@ -85,7 +85,7 @@ class Chef
def remove_package(name, version)
Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}")
package = "#{name}"
- out = shell_out!("/opt/local/bin/pkgin -y remove #{package}", :env => nil)
+ out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil)
end
end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index a2cfd93ef6..9b10403344 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -55,7 +55,7 @@ class Chef
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- shell_out("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@new_resource.version($1)
@@ -65,7 +65,7 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out("pkginfo -l #{@current_resource.package_name}")
+ status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}")
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@@ -87,7 +87,7 @@ class Chef
def candidate_version
return @candidate_version if @candidate_version
- status = shell_out("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
+ status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@@ -110,7 +110,7 @@ class Chef
else
command = "pkgadd -n -d #{@new_resource.source} all"
end
- shell_out!(command)
+ shell_out_with_timeout!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
if ::File.directory?(@new_resource.source) # CHEF-4469
@@ -118,17 +118,17 @@ class Chef
else
command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
end
- shell_out!(command)
+ shell_out_with_timeout!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
def remove_package(name, version)
if @new_resource.options.nil?
- shell_out!( "pkgrm -n #{name}" )
+ shell_out_with_timeout!( "pkgrm -n #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index 143d82f111..7ff0b71807 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,14 +16,18 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/windows_package'
require 'chef/provider/package'
require 'chef/util/path_helper'
+require 'chef/mixin/checksum'
class Chef
class Provider
class Package
class Windows < Chef::Provider::Package
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
provides :package, os: "windows"
provides :windows_package, os: "windows"
@@ -36,19 +40,23 @@ class Chef
# load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
def load_current_resource
- @new_resource.source(Chef::Util::PathHelper.validate_path(@new_resource.source))
-
@current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
- @current_resource.version(package_provider.installed_version)
- @new_resource.version(package_provider.package_version)
- @current_resource
+ if downloadable_file_missing?
+ Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded")
+ current_resource.version(:unknown.to_s)
+ else
+ current_resource.version(package_provider.installed_version)
+ new_resource.version(package_provider.package_version)
+ end
+
+ current_resource
end
def package_provider
@package_provider ||= begin
case installer_type
when :msi
- Chef::Provider::Package::Windows::MSI.new(@new_resource)
+ Chef::Provider::Package::Windows::MSI.new(resource_for_provider)
else
raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
end
@@ -71,6 +79,17 @@ class Chef
end
end
+ def action_install
+ if uri_scheme?(new_resource.source)
+ download_source_file
+ load_current_resource
+ else
+ validate_content!
+ end
+
+ super
+ end
+
# Chef::Provider::Package action_install + action_remove call install_package + remove_package
# Pass those calls to the correct sub-provider
def install_package(name, version)
@@ -80,6 +99,71 @@ class Chef
def remove_package(name, version)
package_provider.remove_package(name, version)
end
+
+ # @return [Array] new_version(s) as an array
+ def new_version_array
+ # Because the one in the parent caches things
+ [new_resource.version]
+ end
+
+ private
+
+ def downloadable_file_missing?
+ uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ end
+
+ def resource_for_provider
+ @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
+ r.source(Chef::Util::PathHelper.validate_path(source_location))
+ r.timeout(new_resource.timeout)
+ r.returns(new_resource.returns)
+ r.options(new_resource.options)
+ end
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ Chef::Log.debug("#{@new_resource} fetched source file to #{source_resource.path}")
+ end
+
+ def source_resource
+ @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
+ r.source(new_resource.source)
+ r.checksum(new_resource.checksum)
+ r.backup(false)
+
+ if new_resource.remote_file_attributes
+ new_resource.remote_file_attributes.each do |(k,v)|
+ r.send(k.to_sym, v)
+ end
+ end
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def source_location
+ if uri_scheme?(new_resource.source)
+ source_resource.path
+ else
+ Chef::Util::PathHelper.cleanpath(new_resource.source)
+ end
+ end
+
+ def validate_content!
+ if new_resource.checksum
+ source_checksum = checksum(source_location)
+ if new_resource.checksum != source_checksum
+ raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
+ end
+ end
+ end
+
end
end
end
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index 938452945e..31faa78215 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -56,7 +56,7 @@ class Chef
Chef::Log.debug("#{@new_resource} installing MSI package '#{@new_resource.source}'")
shell_out!("msiexec /qn /i \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
end
-
+
def remove_package(name, version)
# We could use MsiConfigureProduct here, but we'll start off with msiexec
Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index b3d3d72844..85c2ba683c 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,4 +1,4 @@
-#
+
# Author:: Adam Jacob (<adam@opscode.com>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
@@ -18,7 +18,6 @@
require 'chef/config'
require 'chef/provider/package'
-require 'chef/mixin/shell_out'
require 'chef/mixin/which'
require 'chef/resource/package'
require 'singleton'
@@ -647,7 +646,6 @@ class Chef
# Cache for our installed and available packages, pulled in from yum-dump.py
class YumCache
- include Chef::Mixin::Command
include Chef::Mixin::Which
include Chef::Mixin::ShellOut
include Singleton
@@ -986,6 +984,17 @@ class Chef
# Extra attributes
#
+ def arch_for_name(n)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch
+ elsif @arch
+ idx = package_name_array.index(n)
+ as_array(@arch)[idx]
+ else
+ nil
+ end
+ end
+
def arch
if @new_resource.respond_to?("arch")
@new_resource.arch
@@ -994,6 +1003,12 @@ class Chef
end
end
+ def set_arch(arch)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch(arch)
+ end
+ end
+
def flush_cache
if @new_resource.respond_to?("flush_cache")
@new_resource.flush_cache
@@ -1005,12 +1020,13 @@ class Chef
# Helpers
#
- def yum_arch
+ def yum_arch(arch)
arch ? ".#{arch}" : nil
end
def yum_command(command)
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
# This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
# considered fatal - meaning the rpm is still successfully installed. These issue
@@ -1027,7 +1043,7 @@ class Chef
if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
"so running install again to verify.")
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
break
end
end
@@ -1087,23 +1103,20 @@ class Chef
end
end
- # Don't overwrite an existing arch
- unless arch
- parse_arch
- end
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
installed_version = []
@candidate_version = []
+ @arch = []
if @new_resource.source
unless ::File.exists?(@new_resource.source)
raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
case line
when /([\w\d_.-]+)\s([\w\d_.-]+)/
@current_resource.package_name($1)
@@ -1113,24 +1126,43 @@ class Chef
@candidate_version << @new_resource.version
installed_version << @yum.installed_version(@current_resource.package_name, arch)
else
- if @new_resource.version
- new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
- else
- new_resource = "#{@new_resource.package_name}#{yum_arch}"
- end
- Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ package_name_array.each_with_index do |pkg, idx|
+ # Don't overwrite an existing arch
+ if arch
+ name, parch = pkg, arch
+ else
+ name, parch = parse_arch(pkg)
+ # if we parsed an arch from the name, update the name
+ # to be just the package name.
+ if parch
+ if @new_resource.package_name.is_a?(Array)
+ @new_resource.package_name[idx] = name
+ else
+ @new_resource.package_name(name)
+ # only set the arch if it's a single package
+ set_arch(parch)
+ end
+ end
+ end
- package_name_array.each do |pkg|
- installed_version << @yum.installed_version(pkg, arch)
- @candidate_version << @yum.candidate_version(pkg, arch)
+ if @new_resource.version
+ new_resource =
+ "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
+ else
+ new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+ end
+ Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ installed_version << @yum.installed_version(name, parch)
+ @candidate_version << @yum.candidate_version(name, parch)
+ @arch << parch
end
-
end
if installed_version.size == 1
@current_resource.version(installed_version[0])
@candidate_version = @candidate_version[0]
+ @arch = @arch[0]
else
@current_resource.version(installed_version)
end
@@ -1145,7 +1177,7 @@ class Chef
# Work around yum not exiting with an error if a package doesn't exist
# for CHEF-2062
all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
- @yum.version_available?(n, v, arch)
+ @yum.version_available?(n, v, arch_for_name(n))
end
method = log_method = nil
methods = []
@@ -1187,16 +1219,16 @@ class Chef
repos = []
pkg_string_bits = []
- index = 0
as_array(name).zip(as_array(version)).each do |n, v|
+ idx = package_name_array.index(n)
+ a = arch_for_name(n)
s = ''
- unless v == current_version_array[index]
- s = "#{n}-#{v}#{yum_arch}"
- repo = @yum.package_repository(n, v, arch)
+ unless v == current_version_array[idx]
+ s = "#{n}-#{v}#{yum_arch(a)}"
+ repo = @yum.package_repository(n, v, a)
repos << "#{s} from #{repo} repository"
pkg_string_bits << s
end
- index += 1
end
pkg_string = pkg_string_bits.join(' ')
Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
@@ -1247,11 +1279,15 @@ class Chef
def remove_package(name, version)
if version
- remove_str = as_array(name).zip(as_array(version)).map do |x|
- "#{x.join('-')}#{yum_arch}"
+ remove_str = as_array(name).zip(as_array(version)).map do |n, v|
+ a = arch_for_name(n)
+ "#{[n, v].join('-')}#{yum_arch(a)}"
end.join(' ')
else
- remove_str = as_array(name).map { |n| "#{n}#{yum_arch}" }.join(' ')
+ remove_str = as_array(name).map do |n|
+ a = arch_for_name(n)
+ "#{n}#{yum_arch(a)}"
+ end.join(' ')
end
yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
@@ -1268,22 +1304,26 @@ class Chef
private
- def parse_arch
+ def parse_arch(package_name)
# Allow for foo.x86_64 style package_name like yum uses in it's output
#
- if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+ if package_name =~ %r{^(.*)\.(.*)$}
new_package_name = $1
new_arch = $2
# foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
# Ensure we don't have an existing package matching package_name, then ensure we at
# least have a match for the new_package+new_arch before we overwrite. If neither
# then fall through to standard package handling.
- if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
- (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
- @new_resource.package_name(new_package_name)
- @new_resource.arch(new_arch)
+ old_installed = @yum.installed_version(package_name)
+ old_candidate = @yum.candidate_version(package_name)
+ new_installed = @yum.installed_version(new_package_name, new_arch)
+ new_candidate = @yum.candidate_version(new_package_name, new_arch)
+ if (old_installed.nil? and old_candidate.nil?) and (new_installed or new_candidate)
+ Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
+ return new_package_name, new_arch
end
end
+ return package_name, nil
end
# If we don't have the package we could have been passed a 'whatprovides' feature
@@ -1328,7 +1368,7 @@ class Chef
new_package_name = packages.first.name
new_package_version = packages.first.version.to_s
debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} "
- debug_msg << packages.size == 1 ? "package" : "packages"
+ debug_msg << (packages.size == 1 ? "package" : "packages")
debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'"
Chef::Log.debug(debug_msg)
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 2cd321660b..c2a3ac4ba8 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -29,46 +29,48 @@ class Chef
class Package
class Zypper < Chef::Provider::Package
+ provides :zypper_package, os: "linux"
+
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
is_installed=false
is_out_of_date=false
version=''
oud_version=''
- Chef::Log.debug("#{@new_resource} checking zypper")
- status = shell_out("zypper --non-interactive info #{@new_resource.package_name}")
+ Chef::Log.debug("#{new_resource} checking zypper")
+ status = shell_out_with_timeout("zypper --non-interactive info #{new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /^Version: (.+)$/
version = $1
- Chef::Log.debug("#{@new_resource} version #{$1}")
+ Chef::Log.debug("#{new_resource} version #{$1}")
when /^Installed: Yes$/
is_installed=true
- Chef::Log.debug("#{@new_resource} is installed")
+ Chef::Log.debug("#{new_resource} is installed")
when /^Installed: No$/
is_installed=false
- Chef::Log.debug("#{@new_resource} is not installed")
+ Chef::Log.debug("#{new_resource} is not installed")
when /^Status: out-of-date \(version (.+) installed\)$/
is_out_of_date=true
oud_version=$1
- Chef::Log.debug("#{@new_resource} out of date version #{$1}")
+ Chef::Log.debug("#{new_resource} out of date version #{$1}")
end
end
if is_installed==false
@candidate_version=version
- @current_resource.version(nil)
+ current_resource.version(nil)
end
if is_installed==true
if is_out_of_date==true
- @current_resource.version(oud_version)
+ current_resource.version(oud_version)
@candidate_version=version
else
- @current_resource.version(version)
+ current_resource.version(version)
@candidate_version=version
end
end
@@ -77,7 +79,7 @@ class Chef
raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def zypper_version()
@@ -104,9 +106,9 @@ class Chef
def zypper_package(command, pkgname, version)
version = "=#{version}" unless version.nil? || version.empty?
if zypper_version < 1.0
- shell_out!("zypper#{gpg_checks} #{command} -y #{pkgname}")
+ shell_out_with_timeout!("zypper#{gpg_checks} #{command} -y #{pkgname}")
else
- shell_out!("zypper --non-interactive#{gpg_checks} "+
+ shell_out_with_timeout!("zypper --non-interactive#{gpg_checks} "+
"#{command} #{pkgname}#{version}")
end
end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index f9dcd6d80c..ed44dee6ae 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -24,71 +24,153 @@ class Chef
provides :powershell_script, os: "windows"
+ def initialize (new_resource, run_context)
+ super(new_resource, run_context, '.ps1')
+ add_exit_status_wrapper
+ end
+
+ def action_run
+ valid_syntax = validate_script_syntax!
+ super if valid_syntax
+ end
+
+ def flags
+ # Must use -File rather than -Command to launch the script
+ # file created by the base class that contains the script
+ # code -- otherwise, powershell.exe does not propagate the
+ # error status of a failed Windows process that ran at the
+ # end of the script, it gets changed to '1'.
+ interpreter_flags = [default_interpreter_flags, '-File'].join(' ')
+
+ if ! (@new_resource.flags.nil?)
+ interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
+ end
+
+ interpreter_flags
+ end
+
protected
- EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
- EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze
- EXIT_STATUS_RESET_SCRIPT = "\n$global:LASTEXITCODE=$null".freeze
- # Process exit codes are strange with PowerShell. Unless you
- # explicitly call exit in Powershell, the powershell.exe
- # interpreter returns only 0 for success or 1 for failure. Since
- # we'd like to get specific exit codes from executable tools run
- # with Powershell, we do some work using the automatic variables
- # $? and $LASTEXITCODE to return the process exit code of the
- # last process run in the script if it is the last command
- # executed, otherwise 0 or 1 based on whether $? is set to true
- # (success, where we return 0) or false (where we return 1).
- def normalize_script_exit_status( code )
- target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
- EXIT_STATUS_RESET_SCRIPT +
- "\n" +
- code.to_s +
- EXIT_STATUS_NORMALIZATION_SCRIPT )
- convert_boolean_return = @new_resource.convert_boolean_return
- self.code = <<EOH
-new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
-new-variable -name chefscriptresult -visibility private
-$chefscriptresult = {
-#{target_code}
-}.invokereturnasis()
-if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
-EOH
- Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n")
+ # Process exit codes are strange with PowerShell and require
+ # special handling to cover common use cases.
+ def add_exit_status_wrapper
+ self.code = wrapper_script
+ Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
end
- public
+ 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
- def initialize (new_resource, run_context)
- super(new_resource, run_context, '.ps1')
- normalize_script_exit_status(new_resource.code)
+ 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
+ end
end
- def flags
- default_flags = [
+ 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 = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
+
+ [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
- "-ExecutionPolicy Unrestricted",
+ "-ExecutionPolicy #{execution_policy}",
# Powershell will hang if STDIN is redirected
# http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
- "-InputFormat None",
- # Must use -File rather than -Command to launch the script
- # file created by the base class that contains the script
- # code -- otherwise, powershell.exe does not propagate the
- # error status of a failed Windows process that ran at the
- # end of the script, it gets changed to '1'.
- "-File"
+ "-InputFormat None"
]
+ end
- interpreter_flags = default_flags.join(' ')
+ # A wrapper script is used to launch user-supplied script while
+ # still obtaining useful process exit codes. Unless you
+ # explicitly call exit in Powershell, the powershell.exe
+ # interpreter returns only 0 for success or 1 for failure. Since
+ # we'd like to get specific exit codes from executable tools run
+ # with Powershell, we do some work using the automatic variables
+ # $? and $LASTEXITCODE to return the process exit code of the
+ # last process run in the script if it is the last command
+ # executed, otherwise 0 or 1 based on whether $? is set to true
+ # (success, where we return 0) or false (where we return 1).
+ def wrapper_script
+<<-EOH
+# Chef Client wrapper for powershell_script resources
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
- end
+# LASTEXITCODE can be uninitialized -- make it explictly 0
+# to avoid incorrect detection of failure (non-zero) codes
+$global:LASTEXITCODE = 0
- interpreter_flags
+# Catch any exceptions -- without this, exceptions will result
+# In a zero return code instead of the desired non-zero code
+# that indicates a failure
+trap [Exception] {write-error ($_.Exception.Message);exit 1}
+
+# Variable state that should not be accessible to the user code
+new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+
+# Initialize a variable we use to capture $? inside a block
+$global:lastcmdlet = $null
+
+# Execute the user's code in a script block --
+$chefscriptresult =
+{
+ #{@new_resource.code}
+
+ # This assignment doesn't affect the block's return value
+ $global:lastcmdlet = $?
+}.invokereturnasis()
+
+# Assume failure status of 1 -- success cases
+# will have to override this
+$exitstatus = 1
+
+# If convert_boolean_return is enabled, the block's return value
+# gets precedence in determining our exit status
+if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean')
+{
+ $exitstatus = [int32](!$chefscriptresult)
+}
+elseif ($lastcmdlet)
+{
+ # Otherwise, a successful cmdlet execution defines the status
+ $exitstatus = 0
+}
+elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 )
+{
+ # If the cmdlet status is failed, allow the Win32 status
+ # in $LASTEXITCODE to define exit status. This handles the case
+ # where no cmdlets, only Win32 processes have run since $?
+ # will be set to $false whenever a Win32 process returns a non-zero
+ # status.
+ $exitstatus = $LASTEXITCODE
+}
+
+# If this script is launched with -File, the process exit
+# status of PowerShell.exe will be $exitstatus. If it was
+# launched with -Command, it will be 0 if $exitstatus was 0,
+# 1 (i.e. failed) otherwise.
+exit $exitstatus
+EOH
end
+
end
end
end
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
index 8dde4653ec..22e77dcc13 100644
--- a/lib/chef/provider/reboot.rb
+++ b/lib/chef/provider/reboot.rb
@@ -22,6 +22,7 @@ require 'chef/provider'
class Chef
class Provider
class Reboot < Chef::Provider
+ provides :reboot
def whyrun_supported?
true
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 94f4e2655b..cd62f7c56f 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -31,6 +31,8 @@ class Chef
class Provider
class RegistryKey < Chef::Provider
+ provides :registry_key
+
include Chef::Mixin::Checksum
def whyrun_supported?
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
index da2573dacb..c4643edc0b 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -24,6 +24,7 @@ require 'chef/deprecation/warnings'
class Chef
class Provider
class RemoteFile < Chef::Provider::File
+ provides :remote_file
extend Chef::Deprecation::Warnings
include Chef::Deprecation::Provider::RemoteFile
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index ef55dd77cd..4f450ce333 100644
--- a/lib/chef/provider/remote_file/content.rb
+++ b/lib/chef/provider/remote_file/content.rb
@@ -20,6 +20,7 @@
require 'uri'
require 'tempfile'
require 'chef/file_content_management/content_base'
+require 'chef/mixin/uris'
class Chef
class Provider
@@ -28,6 +29,8 @@ class Chef
private
+ include Chef::Mixin::Uris
+
def file_for_provider
Chef::Log.debug("#{@new_resource} checking for changes")
@@ -45,7 +48,11 @@ class Chef
sources = sources.dup
source = sources.shift
begin
- uri = URI.parse(source)
+ uri = if Chef::Provider::RemoteFile::Fetcher.network_share?(source)
+ source
+ else
+ as_uri(source)
+ end
raw_file = grab_file_from_uri(uri)
rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e
Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e.to_s}")
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
index 249b29186f..53bfe9935c 100644
--- a/lib/chef/provider/remote_file/fetcher.rb
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -23,15 +23,29 @@ class Chef
class Fetcher
def self.for_resource(uri, new_resource, current_resource)
- case uri.scheme
- when "http", "https"
- Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
- when "ftp"
- Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
- when "file"
- Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+ if network_share?(uri)
+ Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource)
else
- raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+ case uri.scheme
+ when "http", "https"
+ Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
+ when "ftp"
+ Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
+ when "file"
+ Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+ else
+ raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+ end
+ end
+ end
+
+ # Windows network share: \\computername\share\file
+ def self.network_share?(source)
+ case source
+ when String
+ !!(%r{\A\\\\[A-Za-z0-9+\-\.]+} =~ source)
+ else
+ false
end
end
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index e78311f2c3..026206b64e 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -32,15 +32,21 @@ class Chef
@new_resource = new_resource
@uri = uri
end
-
+
# CHEF-4472: Remove the leading slash from windows paths that we receive from a file:// URI
- def fix_windows_path(path)
- path.gsub(/^\/([a-zA-Z]:)/,'\1')
+ def fix_windows_path(path)
+ path.gsub(/^\/([a-zA-Z]:)/,'\1')
+ end
+
+ def source_path
+ @source_path ||= begin
+ path = URI.unescape(uri.path)
+ Chef::Platform.windows? ? fix_windows_path(path) : path
+ end
end
# Fetches the file at uri, returning a Tempfile-like File handle
def fetch
- source_path = Chef::Platform.windows? ? fix_windows_path(uri.path) : uri.path
tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
FileUtils.cp(source_path, tempfile.path)
diff --git a/lib/chef/provider/remote_file/network_file.rb b/lib/chef/provider/remote_file/network_file.rb
new file mode 100644
index 0000000000..093a388d2a
--- /dev/null
+++ b/lib/chef/provider/remote_file/network_file.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Jesse Campbell (<hikeit@gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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 'uri'
+require 'tempfile'
+require 'chef/provider/remote_file'
+
+class Chef
+ class Provider
+ class RemoteFile
+ class NetworkFile
+
+ attr_reader :new_resource
+
+ def initialize(source, new_resource, current_resource)
+ @new_resource = new_resource
+ @source = source
+ end
+
+ # Fetches the file on a network share, returning a Tempfile-like File handle
+ # windows only
+ def fetch
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
+ Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}")
+ FileUtils.cp(@source, tempfile.path)
+ tempfile.close if tempfile
+ tempfile
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 75da2ddb31..9c523b5e66 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -168,6 +168,50 @@ class Chef
@new_resource.respond_to?(method_name) &&
!!@new_resource.send(method_name)
end
+
+ module ServicePriorityInit
+
+ #
+ # Platform-specific versions
+ #
+
+ #
+ # Linux
+ #
+
+ require 'chef/chef_class'
+ require 'chef/provider/service/systemd'
+ require 'chef/provider/service/insserv'
+ require 'chef/provider/service/redhat'
+ require 'chef/provider/service/arch'
+ require 'chef/provider/service/gentoo'
+ 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
+
+ end
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 0aef62c62e..09ed4bbf01 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -91,15 +91,18 @@ class Chef
protected
def determine_current_status!
- Chef::Log.debug "#{@new_resource} using lssrc to check the status "
+ Chef::Log.debug "#{@new_resource} using lssrc to check the status"
begin
- services = shell_out!("lssrc -a | grep -w #{@new_resource.service_name}").stdout.split("\n")
- is_resource_group?(services)
-
- if services.length == 1 && services[0].split(' ').last == "active"
- @current_resource.running true
- else
+ if is_resource_group?
+ # Groups as a whole have no notion of whether they're running
@current_resource.running false
+ else
+ service = shell_out!("lssrc -s #{@new_resource.service_name}").stdout
+ if service.split(' ').last == 'active'
+ @current_resource.running true
+ else
+ @current_resource.running false
+ end
end
Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
@@ -112,11 +115,9 @@ class Chef
end
end
- def is_resource_group? (services)
- if services.length > 1
- Chef::Log.debug("#{@new_resource.service_name} is a group")
- @is_resource_group = true
- elsif services[0].split(' ')[1] == @new_resource.service_name
+ def is_resource_group?
+ 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
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 9204e3ef92..6c78f86fe0 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -147,7 +147,7 @@ class Chef
# some scripts support multiple instances through symlinks such as openvpn.
# We should get the service name from rcvar.
Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
- sn = shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
else
# for why-run mode when the rcd_script is not there yet
new_resource.service_name
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index 0a219a69e1..355e98a0eb 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -18,6 +18,7 @@
require 'chef/provider/service/simple'
require 'chef/mixin/command'
+require 'chef/platform/service_helpers'
class Chef
class Provider
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 7cfe57a92a..7324822eff 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -28,8 +28,8 @@ class Chef
class Service
class Macosx < Chef::Provider::Service::Simple
- provides :service, os: "darwin"
provides :macosx_service, os: "darwin"
+ provides :service, os: "darwin"
def self.gather_plist_dirs
locations = %w{/Library/LaunchAgents
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index ba53f0a3c3..355ffafc2a 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -25,7 +25,6 @@ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
end
class Chef::Provider::Service::Windows < Chef::Provider::Service
-
provides :service, os: "windows"
provides :windows_service, os: "windows"
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index f6ac72448e..ad92a72a0a 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,6 +23,7 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
+ provides :user
include Chef::Mixin::Command
@@ -208,7 +209,6 @@ class Chef
def unlock_user
raise NotImplementedError
end
-
end
end
end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index af08ab4364..a575a41e54 100644
--- a/lib/chef/provider/user/aix.rb
+++ b/lib/chef/provider/user/aix.rb
@@ -18,9 +18,10 @@ class Chef
class Provider
class User
class Aix < Chef::Provider::User::Useradd
+ provides :user, platform: %w(aix)
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
-
+
def create_user
super
add_password
@@ -88,7 +89,7 @@ class Chef
end
end
end
-
+
end
end
end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index fe71e93561..810ffb9a8d 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class User
class Pw < Chef::Provider::User
+ provides :user, platform: %w(freebsd)
def load_current_resource
super
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index d480acaced..b242095f0c 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -22,6 +22,8 @@ class Chef
class Provider
class User
class Solaris < Chef::Provider::User::Useradd
+ provides :user, platform: %w(omnios solaris2)
+
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
attr_writer :password_file
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index cc770c0be2..a1b5b3459c 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -23,6 +23,7 @@ class Chef
class Provider
class User
class Useradd < Chef::Provider::User
+ provides :user
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 5e887225e4..5bfee343d1 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -20,6 +20,30 @@ require 'chef/exceptions'
require 'chef/platform/provider_priority_map'
class Chef
+ #
+ # Provider Resolution
+ # ===================
+ #
+ # Provider resolution is the process of taking a Resource object and an
+ # action, and determining the Provider class that should be instantiated to
+ # handle the action.
+ #
+ # If the resource has its `provider` set, that is used.
+ #
+ # Otherwise, we take the lists of Providers that have registered as
+ # providing the DSL through `provides :dsl_name, <filters>` or
+ # `Chef.set_resource_priority_array :dsl_name, <filters>`. We filter each
+ # list of Providers through:
+ #
+ # 1. The filters it was registered with (such as `os: 'linux'` or
+ # `platform_family: 'debian'`)
+ # 2. `provides?(node, resource)`
+ # 3. `supports?(resource, action)`
+ #
+ # Anything that passes the filter and returns `true` to provides and supports,
+ # is considered a match. The first matching Provider in the *most recently
+ # registered list* is selected and returned.
+ #
class ProviderResolver
attr_reader :node
@@ -32,33 +56,14 @@ class Chef
@action = action
end
- # return a deterministically sorted list of Chef::Provider subclasses
- def providers
- @providers ||= Chef::Provider.descendants
- end
-
def resolve
maybe_explicit_provider(resource) ||
maybe_dynamic_provider_resolution(resource, action) ||
maybe_chef_platform_lookup(resource)
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
+ def provided_by?(provider_class)
+ prioritized_handlers.include?(provider_class)
end
private
@@ -71,40 +76,37 @@ class Chef
# try dynamically finding a provider based on querying the providers to see what they support
def maybe_dynamic_provider_resolution(resource, action)
- # log this so we know what providers will work for the generic resource on the node (early cut)
- Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
-
- # what providers were excluded by machine state (late cut)
- Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}"
- Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}"
-
- # 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.
- handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers
- Chef::Log.debug "no providers supported the resource, falling back to enabled handlers" if supported_handlers.empty?
-
- if handlers.count >= 2
- # this magic stack ranks the providers by where they appear in the provider_priority_map, it is mostly used
- # to pick amongst N different ways to start init scripts on different debian/ubuntu systems.
- priority_list = [ get_priority_array(node, resource.resource_name) ].flatten.compact
- handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
- if priority_list.index(handlers.first).nil?
- # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
- # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
- Chef::Log.warn "Ambiguous provider precedence: #{handlers}, please use Chef.set_provider_priority_array to provide determinism"
- end
- handlers = [ handlers.first ]
+ 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
- Chef::Log.debug "providers that survived replacement include: #{handlers}"
-
- raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2
-
- Chef::Log.debug "dynamic provider resolver FAILED to resolve a provider" if handlers.empty?
-
- return nil if handlers.empty?
+ if handler
+ Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
+ else
+ Chef::Log.debug "Dynamic provider resolver FAILED to resolve a provider for action #{action} on resource #{resource}"
+ end
- handlers[0]
+ handler
end
# try the old static lookup of providers by platform
@@ -112,13 +114,51 @@ class Chef
Chef::Platform.find_provider_for_node(node, resource)
end
- # dep injection hooks
- def get_priority_array(node, resource_name)
- provider_priority_map.get_priority_array(node, resource_name)
- end
-
def provider_priority_map
Chef::Platform::ProviderPriorityMap.instance
end
+
+ def prioritized_handlers
+ @prioritized_handlers ||=
+ provider_priority_map.list_handlers(node, resource.resource_name).flatten(1).uniq
+ end
+
+ module Deprecated
+ # return a deterministically sorted list of Chef::Provider subclasses
+ def providers
+ @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.")
+ end
+ result
+ end
+ end
+ end
+ prepend Deprecated
end
end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index a5f5386de3..18500d4669 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -122,6 +122,7 @@ require 'chef/provider/deploy/timestamped'
require 'chef/provider/remote_file/ftp'
require 'chef/provider/remote_file/http'
require 'chef/provider/remote_file/local_file'
+require 'chef/provider/remote_file/network_file'
require 'chef/provider/remote_file/fetcher'
require "chef/provider/lwrp_base"
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index d934ec8c47..7fe8a52d95 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -22,6 +22,7 @@ require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
require 'chef/dsl/registry_helper'
require 'chef/dsl/reboot_pending'
+require 'chef/dsl/resources'
require 'chef/mixin/convert_to_class_name'
require 'chef/guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
@@ -31,9 +32,14 @@ require 'chef/node_map'
require 'chef/node'
require 'chef/platform'
require 'chef/resource/resource_notification'
+require 'chef/provider_resolver'
+require 'chef/resource_resolver'
+require 'set'
require 'chef/mixin/deprecation'
require 'chef/mixin/provides'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
class Chef
class Resource
@@ -48,6 +54,12 @@ class Chef
include Chef::DSL::RebootPending
extend Chef::Mixin::Provides
+ # This lets user code do things like `not_if { shell_out!("command") }`
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
+
+ NULL_ARG = Object.new
+
#
# The node the current Chef run is using.
#
@@ -79,7 +91,6 @@ class Chef
run_context.resource_collection.find(*args)
end
-
#
# Resource User Interface (for users)
#
@@ -98,8 +109,8 @@ class Chef
@before = nil
@params = Hash.new
@provider = nil
- @allowed_actions = [ :nothing ]
- @action = :nothing
+ @allowed_actions = self.class.allowed_actions.to_a
+ @action = self.class.default_action
@updated = false
@updated_by_last_action = false
@supports = {}
@@ -160,19 +171,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
- action_list = arg.kind_of?(Array) ? arg : [ arg ]
- action_list = action_list.collect { |a| a.to_sym }
- action_list.each do |action|
+ if arg.is_a?(Array)
+ arg = arg.map { |a| a.to_sym }
+ else
+ arg = arg.to_sym
+ end
+ Array(arg).each do |action|
validate(
{ action: action },
- { action: { kind_of: Symbol, equal_to: @allowed_actions } }
+ { action: { kind_of: Symbol, equal_to: allowed_actions } }
)
end
- @action = action_list
+ self.action = arg
else
- @action
+ # Pull the action from the class if it's not set
+ @action || self.class.default_action
end
end
@@ -180,8 +196,7 @@ class Chef
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
#
- # If the action does nothing--does not update this resource, the
- # notification never triggers.)
+ # If the action does not update this resource, the notification never triggers.
#
# Only one resource may be specified per notification.
#
@@ -467,7 +482,7 @@ class Chef
#
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
- def state
+ def state_for_resource_reporter
self.class.state_attrs.inject({}) do |state_attrs, attr_name|
state_attrs[attr_name] = send(attr_name)
state_attrs
@@ -475,6 +490,15 @@ class Chef
end
#
+ # Since there are collisions with LWRP parameters named 'state' this
+ # method is not used by the resource_reporter and is most likely unused.
+ # It certainly cannot be relied upon and cannot be fixed.
+ #
+ # @deprecated
+ #
+ 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.
#
@@ -588,14 +612,14 @@ class Chef
#
def to_s
- "#{@resource_name}[#{@name}]"
+ "#{resource_name}[#{name}]"
end
def to_text
return "suppressed sensitive resource output" if sensitive
ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
text = "# Declared in #{@source_line}\n\n"
- text << self.class.dsl_name + "(\"#{name}\") do\n"
+ text << "#{resource_name}(\"#{name}\") do\n"
ivars.each do |ivar|
if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
@@ -749,6 +773,12 @@ class Chef
# have.
#
attr_accessor :allowed_actions
+ def allowed_actions(value=NULL_ARG)
+ if value != NULL_ARG
+ self.allowed_actions = value
+ end
+ @allowed_actions
+ end
#
# Whether or not this resource was updated during an action. If multiple
@@ -807,19 +837,15 @@ class Chef
end
#
- # The DSL name of this resource (e.g. `package` or `yum_package`)
+ # The display name of this resource type, for printing purposes.
#
- # @return [String] The DSL name of this resource.
- def self.dsl_name
- convert_to_snake_case(name, 'Chef::Resource')
- end
-
+ # Will be used to print out the resource in messages, e.g. resource_name[name]
#
- # The name of this resource (e.g. `file`)
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- # @return [String] The name of this resource.
- #
- attr_reader :resource_name
+ def resource_name
+ @resource_name || self.class.resource_name
+ end
#
# Sets a list of capabilities of the real resource. For example, `:remount`
@@ -852,6 +878,66 @@ class Chef
end
#
+ # The DSL name of this resource (e.g. `package` or `yum_package`)
+ #
+ # @return [String] The DSL name of this resource.
+ #
+ # @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."
+ if name
+ name = self.name.split('::')[-1]
+ convert_to_snake_case(name)
+ end
+ end
+
+ #
+ # The display name of this resource type, for printing purposes.
+ #
+ # This also automatically calls "provides" to provide DSL with the given
+ # name.
+ #
+ # resource_name defaults to your class name.
+ #
+ # Call `resource_name nil` to remove the resource name (and any
+ # corresponding DSL).
+ #
+ # @param value [Symbol] The desired name of this resource type (e.g.
+ # `execute`), or `nil` if this class is abstract and has no resource_name.
+ #
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
+ #
+ def self.resource_name(name=NULL_ARG)
+ # Setter
+ if name != NULL_ARG
+ 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)
+ 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)
+ resource_name(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`.
@@ -865,11 +951,70 @@ class Chef
# # ...other stuff
# end
#
+ # @deprecated Use `provides` on the provider, or `provider` on the resource, instead.
+ #
def self.provider_base(arg=nil)
- @provider_base ||= arg
- @provider_base ||= Chef::Provider
+ 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.")
+ end
+ @provider_base ||= arg || Chef::Provider
+ end
+
+ #
+ # The list of allowed actions for the resource.
+ #
+ # @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
+ #
+ # @return [Arrau<Symbol>] The list of actions, as symbols.
+ #
+ def self.allowed_actions(*actions)
+ @allowed_actions ||=
+ if superclass.respond_to?(:allowed_actions)
+ superclass.allowed_actions.dup
+ else
+ [ :nothing ]
+ end
+ @allowed_actions |= actions
end
+ def self.allowed_actions=(value)
+ @allowed_actions = value
+ end
+
+ #
+ # The action that will be run if no other action is specified.
+ #
+ # Setting default_action will automatially add the action to
+ # allowed_actions, if it isn't already there.
+ #
+ # 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.
+ #
+ 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)
+ end
+
+ if @default_action
+ @default_action
+ elsif superclass.respond_to?(:default_action)
+ superclass.default_action
+ else
+ :nothing
+ end
+ end
+ def self.default_action=(action_name)
+ default_action(action_name)
+ end
#
# Internal Resource Interface (for Chef)
@@ -945,10 +1090,31 @@ class Chef
# NOTE: that we do not support unregistering classes as descendents like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
+ # @deprecated
+ # @api private
alias :resource_classes :descendants
+ # @deprecated
+ # @api private
alias :find_subclass_by_name :find_descendants_by_name
end
+ # @deprecated
+ # @api private
+ # We memoize a sorted version of descendants so that resource lookups don't
+ # have to sort all the things, all the time.
+ # This was causing performance issues in test runs, and probably in real
+ # life as well.
+ @@sorted_descendants = nil
+ def self.sorted_descendants
+ @@sorted_descendants ||= descendants.sort_by { |x| x.to_s }
+ end
+ def self.inherited(child)
+ super
+ @sorted_descendants = nil
+ child.resource_name
+ end
+
+
# If an unknown method is invoked, determine whether the enclosing Provider's
# lexical scope can fulfill the request. E.g. This happens when the Resource's
# block invokes new_resource.
@@ -960,6 +1126,32 @@ class Chef
end
end
+ #
+ # Mark this resource as providing particular DSL.
+ #
+ # Resources have an automatic DSL based on their resource_name, equivalent to
+ # `provides :resource_name` (providing the resource on all OS's). If you
+ # declare a `provides` with the given resource_name, it *replaces* that
+ # provides (so that you can provide your resource DSL only on certain OS's).
+ #
+ def self.provides(name, **options, &block)
+ name = name.to_sym
+
+ # `provides :resource_name, os: 'linux'`) needs to remove the old
+ # canonical DSL before adding the new one.
+ if @resource_name && name == @resource_name
+ remove_canonical_dsl
+ end
+
+ result = Chef.set_resource_priority_array(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)
+ end
+
# Helper for #notifies
def validate_resource_spec!(resource_spec)
run_context.resource_collection.validate_lookup_spec!(resource_spec)
@@ -1016,7 +1208,6 @@ class Chef
end
def provider_for_action(action)
- require 'chef/provider_resolver'
provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
provider.action = action
provider
@@ -1090,30 +1281,90 @@ class Chef
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
def self.resource_for_node(short_name, node)
- require 'chef/resource_resolver'
- klass = Chef::ResourceResolver.new(node, short_name).resolve
+ klass = Chef::ResourceResolver.resolve(short_name, node: node)
raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
klass
end
- # Returns the class of a Chef::Resource based on the short name
+ #
+ # Returns the class with the given resource_name.
+ #
# ==== Parameters
# short_name<Symbol>:: short_name of the resource (ie :directory)
#
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
+ #
def self.resource_matching_short_name(short_name)
- begin
- rname = convert_to_class_name(short_name.to_s)
- Chef::Resource.const_get(rname)
- rescue NameError
- nil
+ Chef::ResourceResolver.resolve(short_name, canonical: true)
+ end
+
+ # @api private
+ def self.register_deprecated_lwrp_class(resource_class, class_name)
+ if Chef::Resource.const_defined?(class_name, false)
+ Chef::Log.warn "#{class_name} already exists! Deprecation class overwrites #{resource_class}"
+ 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
+ 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
- private
+ def self.deprecated_constants
+ @deprecated_constants ||= {}
+ end
- def lookup_provider_constant(name)
+ # @api private
+ def lookup_provider_constant(name, action=:nothing)
begin
self.class.provider_base.const_get(convert_to_class_name(name.to_s))
rescue NameError => e
@@ -1124,5 +1375,19 @@ class Chef
end
end
end
+
+ private
+
+ def self.remove_canonical_dsl
+ if @resource_name
+ remaining = Chef.resource_priority_map.delete_canonical(@resource_name, self)
+ if !remaining
+ Chef::DSL::Resources.remove_resource_dsl(@resource_name)
+ end
+ end
+ end
end
end
+
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index f944825ac3..ca119b50c4 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -23,12 +23,10 @@ class Chef
class Resource
class AptPackage < Chef::Resource::Package
- provides :apt_package
provides :package, os: "linux", platform_family: [ "debian" ]
def initialize(name, run_context=nil)
super
- @resource_name = :apt_package
@default_release = nil
end
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 0add0ce501..025687e879 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -25,7 +25,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :bash
@interpreter = "bash"
end
diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb
index c091ec56b6..efe3f2205f 100644
--- a/lib/chef/resource/batch.rb
+++ b/lib/chef/resource/batch.rb
@@ -25,7 +25,7 @@ class Chef
provides :batch, os: "windows"
def initialize(name, run_context=nil)
- super(name, run_context, :batch, "cmd.exe")
+ super(name, run_context, nil, "cmd.exe")
end
end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
index 917f0d1d50..7c1496a46b 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/resource/bff_package.rb
@@ -22,14 +22,6 @@ require 'chef/provider/package/aix'
class Chef
class Resource
class BffPackage < Chef::Resource::Package
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :bff_package
- end
-
end
end
end
-
-
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index b2210262d2..69dbc48050 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -22,14 +22,12 @@ require 'chef/resource'
class Chef
class Resource
class Breakpoint < Chef::Resource
+ default_action :break
def initialize(action="break", *args)
- @name = caller.first
- super(@name, *args)
- @action = "break"
- @allowed_actions << :break
- @resource_name = :breakpoint
+ super(caller.first, *args)
end
+
end
end
end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 59f575a524..0c2fdfa819 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -23,11 +23,8 @@ class Chef
class Resource
class ChefGem < Chef::Resource::Package::GemPackage
- provides :chef_gem
-
def initialize(name, run_context=nil)
super
- @resource_name = :chef_gem
@compile_time = Chef::Config[:chef_gem_compile_time]
@gem_binary = RbConfig::CONFIG['bindir'] + "/gem"
end
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 7be353b648..42f16e6db6 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -27,13 +27,11 @@ class Chef
class CookbookFile < Chef::Resource::File
include Chef::Mixin::Securable
- provides :cookbook_file
+ default_action :create
def initialize(name, run_context=nil)
super
@provider = Chef::Provider::CookbookFile
- @resource_name = :cookbook_file
- @action = "create"
@source = ::File.basename(name)
@cookbook = nil
end
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
index cb16506012..93cf41bc37 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -27,13 +27,11 @@ class Chef
state_attrs :minute, :hour, :day, :month, :weekday, :user
- provides :cron
+ default_action :create
+ allowed_actions :create, :delete
def initialize(name, run_context=nil)
super
- @resource_name = :cron
- @action = :create
- @allowed_actions.push(:create, :delete)
@minute = "*"
@hour = "*"
@day = "*"
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 36659c349b..d5e9c910b1 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -25,7 +25,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :csh
@interpreter = "csh"
end
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index 4252aa230f..3e5255bced 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -51,15 +51,15 @@ class Chef
#
class Deploy < Chef::Resource
- provider_base Chef::Provider::Deploy
-
identity_attr :repository
state_attrs :deploy_to, :revision
+ default_action :deploy
+ allowed_actions :force_deploy, :deploy, :rollback
+
def initialize(name, run_context=nil)
super
- @resource_name = :deploy
@deploy_to = name
@environment = nil
@repository_cache = 'cached-copy'
@@ -69,7 +69,6 @@ class Chef
@symlink_before_migrate = {"config/database.yml" => "config/database.yml"}
@symlinks = {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
@revision = 'HEAD'
- @action = :deploy
@migrate = false
@rollback_on_error = false
@remote = "origin"
@@ -77,7 +76,6 @@ class Chef
@shallow_clone = false
@scm_provider = Chef::Provider::Git
@svn_force_export = false
- @allowed_actions.push(:force_deploy, :deploy, :rollback)
@additional_remotes = Hash[]
@keep_releases = 5
@enable_checkout = true
@@ -281,6 +279,12 @@ class Chef
)
end
+ # This is to support "provider :revision" without deprecation warnings.
+ # Do NOT copy this.
+ def self.provider_base
+ Chef::Provider::Deploy
+ end
+
def svn_force_export(arg=nil)
set_or_return(
:svn_force_export,
diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb
index e144ce2162..1397359ac8 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/lib/chef/resource/deploy_revision.rb
@@ -22,23 +22,9 @@ class Chef
# Convenience class for using the deploy resource with the revision
# deployment strategy (provider)
class DeployRevision < Chef::Resource::Deploy
-
- provides :deploy_revision
-
- def initialize(*args, &block)
- super
- @resource_name = :deploy_revision
- end
end
class DeployBranch < Chef::Resource::DeployRevision
-
- provides :deploy_branch
-
- def initialize(*args, &block)
- super
- @resource_name = :deploy_branch
- end
end
end
diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb
index 1ab7f0d16d..9cac2ce243 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -32,15 +32,13 @@ class Chef
include Chef::Mixin::Securable
- provides :directory
+ default_action :create
+ allowed_actions :create, :delete
def initialize(name, run_context=nil)
super
- @resource_name = :directory
@path = name
- @action = :create
@recursive = false
- @allowed_actions.push(:create, :delete)
end
def recursive(arg=nil)
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 35a47e8a82..38adf24cf6 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -25,11 +25,6 @@ class Chef
provides :dpkg_package, os: "linux"
- def initialize(name, run_context=nil)
- super
- @resource_name = :dpkg_package
- end
-
end
end
end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 912b683434..5db00f49ca 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -25,13 +25,12 @@ class Chef
include Chef::DSL::Powershell
+ default_action :run
+
def initialize(name, run_context)
super
@properties = {}
- @resource_name = :dsc_resource
@resource = nil
- @allowed_actions.push(:run)
- @action = :run
end
def resource(value=nil)
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index cf96ef6b7f..2fcf183375 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -24,11 +24,10 @@ class Chef
provides :dsc_script, platform: "windows"
+ default_action :run
+
def initialize(name, run_context=nil)
super
- @allowed_actions.push(:run)
- @action = :run
- @resource_name = :dsc_script
@imports = {}
end
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb
index 5286e9a289..df4cee1ab3 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/easy_install_package.rb
@@ -22,13 +22,6 @@ class Chef
class Resource
class EasyInstallPackage < Chef::Resource::Package
- provides :easy_install_package
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :easy_install_package
- end
-
def easy_install_binary(arg=nil)
set_or_return(
:easy_install_binary,
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
index 2072ae5d80..025bfc72b7 100644
--- a/lib/chef/resource/env.rb
+++ b/lib/chef/resource/env.rb
@@ -27,14 +27,14 @@ class Chef
provides :env, os: "windows"
+ default_action :create
+ allowed_actions :create, :delete, :modify
+
def initialize(name, run_context=nil)
super
- @resource_name = :env
@key_name = name
@value = nil
- @action = :create
@delim = nil
- @allowed_actions.push(:create, :delete, :modify)
end
def key_name(arg=nil)
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
index 24009d51c7..1976c54c45 100644
--- a/lib/chef/resource/erl_call.rb
+++ b/lib/chef/resource/erl_call.rb
@@ -28,18 +28,16 @@ class Chef
identity_attr :code
+ default_action :run
+
def initialize(name, run_context=nil)
super
- @resource_name = :erl_call
@code = "q()." # your erlang code goes here
@cookie = nil # cookie of the erlang node
@distributed = false # if you want to have a distributed erlang node
@name_type = "sname" # type of erlang hostname name or sname
@node_name = "chef@localhost" # the erlang node hostname
-
- @action = "run"
- @allowed_actions.push(:run)
end
def code(arg=nil)
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 9f8b629fb8..ec669a75d3 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -32,12 +32,12 @@ class Chef
# Only execute resources (and subclasses) can be guard interpreters.
attr_accessor :is_guard_interpreter
+ default_action :run
+
def initialize(name, run_context=nil)
super
- @resource_name = :execute
@command = name
@backup = 5
- @action = "run"
@creates = nil
@cwd = nil
@environment = nil
@@ -46,7 +46,6 @@ class Chef
@returns = 0
@timeout = nil
@user = nil
- @allowed_actions.push(:run)
@umask = nil
@default_guard_interpreter = :execute
@is_guard_interpreter = false
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 53a6a160af..d278652cc3 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -38,15 +38,22 @@ class Chef
attr_writer :checksum
- provides :file
+ #
+ # The checksum of the rendered file. This has to be saved on the
+ # new_resource for the 'after' state for reporting but we cannot
+ # mutate the new_resource.checksum which would change the
+ # user intent in the new_resource if the resource is reused.
+ #
+ # @returns [String] Checksum of the file we actually rendered
+ attr_accessor :final_checksum
+
+ default_action :create
+ allowed_actions :create, :delete, :touch, :create_if_missing
def initialize(name, run_context=nil)
super
- @resource_name = :file
@path = name
@backup = 5
- @action = "create"
- @allowed_actions.push(:create, :delete, :touch, :create_if_missing)
@atomic_update = Chef::Config[:file_atomic_update]
@force_unlink = false
@manage_symlink_source = nil
@@ -129,6 +136,15 @@ class Chef
@verifications
end
end
+
+ def state_for_resource_reporter
+ state_attrs = super()
+ # fix up checksum state with final_checksum saved by the provider
+ if checksum.nil? && final_checksum
+ state_attrs[:checksum] = final_checksum
+ end
+ state_attrs
+ end
end
end
end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 9c8db506f8..c7c43450ba 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -31,11 +31,6 @@ class Chef
provides :package, platform: "freebsd"
- def initialize(name, run_context=nil)
- super
- @resource_name = :freebsd_package
- end
-
def after_created
assign_provider
end
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index 0e838ca040..b981797876 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -22,11 +22,8 @@ class Chef
class Resource
class GemPackage < Chef::Resource::Package
- provides :gem_package
-
def initialize(name, run_context=nil)
super
- @resource_name = :gem_package
@clear_sources = false
end
diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb
index 7156873315..393a0689fe 100644
--- a/lib/chef/resource/git.rb
+++ b/lib/chef/resource/git.rb
@@ -22,11 +22,8 @@ class Chef
class Resource
class Git < Chef::Resource::Scm
- provides :git
-
def initialize(name, run_context=nil)
super
- @resource_name = :git
@additional_remotes = Hash[]
end
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index 9e8f1309b0..2e80f32fea 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -25,19 +25,17 @@ class Chef
state_attrs :members
- provides :group
+ allowed_actions :create, :remove, :modify, :manage
+ default_action :create
def initialize(name, run_context=nil)
super
- @resource_name = :group
@group_name = name
@gid = nil
@members = []
@excluded_members = []
- @action = :create
@append = false
@non_unique = false
- @allowed_actions.push(:create, :remove, :modify, :manage)
end
def group_name(arg=nil)
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 73409b13ac..048ba6b3aa 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -25,12 +25,10 @@ class Chef
class Resource
class HomebrewPackage < Chef::Resource::Package
- provides :homebrew_package
provides :package, os: "darwin"
def initialize(name, run_context=nil)
super
- @resource_name = :homebrew_package
@homebrew_user = nil
end
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index ccb0a26629..f9f056325a 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -26,14 +26,14 @@ class Chef
identity_attr :url
+ default_action :get
+ allowed_actions :get, :put, :post, :delete, :head, :options
+
def initialize(name, run_context=nil)
super
- @resource_name = :http_request
@message = name
@url = nil
- @action = :get
@headers = {}
- @allowed_actions.push(:get, :put, :post, :delete, :head, :options)
end
def url(args=nil)
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index c289ddadbe..527eb0e515 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -27,12 +27,12 @@ class Chef
state_attrs :inet_addr, :mask
+ default_action :add
+ allowed_actions :add, :delete, :enable, :disable
+
def initialize(name, run_context=nil)
super
- @resource_name = :ifconfig
@target = name
- @action = :add
- @allowed_actions.push(:add, :delete, :enable, :disable)
@hwaddr = nil
@mask = nil
@inet_addr = nil
@@ -145,5 +145,3 @@ class Chef
end
end
-
-
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index c0e699e31a..8d720dd411 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -25,10 +25,10 @@ class Chef
provides :ips_package, os: "solaris2"
+ allowed_actions :install, :remove, :upgrade
+
def initialize(name, run_context = nil)
super(name, run_context)
- @resource_name = :ips_package
- @allowed_actions.push(:install, :remove, :upgrade)
@accept_license = false
end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 30f8ec86d1..f932383cc1 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -25,21 +25,19 @@ class Chef
class Link < Chef::Resource
include Chef::Mixin::Securable
- provides :link
-
identity_attr :target_file
state_attrs :to, :owner, :group
+ default_action :create
+ allowed_actions :create, :delete
+
def initialize(name, run_context=nil)
verify_links_supported!
super
- @resource_name = :link
@to = nil
- @action = :create
@link_type = :symbolic
@target_file = name
- @allowed_actions.push(:create, :delete)
end
def to(arg=nil)
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 7f970a87a4..9adffb26bb 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -26,6 +26,8 @@ class Chef
identity_attr :message
+ default_action :write
+
# Sends a string from a recipe to a log provider
#
# log "some string to log" do
@@ -48,10 +50,7 @@ class Chef
# node<Chef::Node>:: Node where resource will be used
def initialize(name, run_context=nil)
super
- @resource_name = :log
@level = :info
- @action = :write
- @allowed_actions.push(:write)
@message = name
end
@@ -75,5 +74,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index ce72e98028..c486233020 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -19,6 +19,13 @@
#
require 'chef/resource'
+require 'chef/resource_resolver'
+require 'chef/node'
+require 'chef/log'
+require 'chef/exceptions'
+require 'chef/mixin/convert_to_class_name'
+require 'chef/mixin/from_file'
+require 'chef/mixin/params_validate' # for DelayedEvaluator
class Chef
class Resource
@@ -28,138 +35,99 @@ class Chef
# so attributes, default action, etc. can be defined with pleasing syntax.
class LWRPBase < Resource
- NULL_ARG = Object.new
+ # Class methods
+ class <<self
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
- # Evaluates the LWRP resource file and instantiates a new Resource class.
- def self.build_from_file(cookbook_name, filename, run_context)
- resource_class = nil
- rname = filename_to_qualified_string(cookbook_name, filename)
+ attr_accessor :loaded_lwrps
- class_name = convert_to_class_name(rname)
- if Resource.const_defined?(class_name, false)
- Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!")
- Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
- resource_class = Resource.const_get(class_name)
- else
- resource_class = Class.new(self)
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.info("LWRP resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ return loaded_lwrps[filename]
+ end
- Chef::Resource.const_set(class_name, resource_class)
- resource_class.resource_name = rname
+ resource_name = filename_to_qualified_string(cookbook_name, filename)
+
+ # We load the class first to give it a chance to set its own name
+ resource_class = Class.new(self)
+ resource_class.resource_name resource_name.to_sym
resource_class.run_context = run_context
resource_class.class_from_file(filename)
- Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
- end
+ # Make a useful string for the class (rather than <Class:312894723894>)
+ resource_class.instance_eval do
+ define_singleton_method(:to_s) do
+ "LWRP resource #{resource_name} from cookbook #{cookbook_name}"
+ end
+ define_singleton_method(:inspect) { to_s }
+ end
- resource_class
- end
+ Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
- # Set the resource name for this LWRP
- def self.resource_name(arg = NULL_ARG)
- if arg.equal?(NULL_ARG)
- @resource_name
- else
- @resource_name = arg
- end
- end
-
- class << self
- alias_method :resource_name=, :resource_name
- end
+ LWRPBase.loaded_lwrps[filename] = true
- # Define an attribute on this resource, including optional validation
- # parameters.
- def self.attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
+ # Create the deprecated Chef::Resource::LwrpFoo class
+ Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name))
+ resource_class
end
- end
- # Sets the default action
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
- @actions ||= []
- if action_name.is_a?(Array)
- action = action_name.map { |arg| arg.to_sym }
- @actions = actions | action
- @default_action = action
- else
- action = action_name.to_sym
- @actions.push(action) unless @actions.include?(action)
- @default_action = action
+ # 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
- @default_action ||= from_superclass(:default_action)
- end
-
- # Adds +action_names+ to the list of valid actions for this resource.
- def self.actions(*action_names)
- if action_names.empty?
- defined?(@actions) ? @actions : from_superclass(:actions, []).dup
- else
- # BC-compat way for checking if actions have already been defined
- if defined?(@actions)
- @actions.push(*action_names)
+ # 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)
+ if !action_names.empty? && !@allowed_actions
+ self.allowed_actions = action_names
else
- @actions = action_names
+ allowed_actions(*action_names)
end
end
- end
-
- # @deprecated
- def self.valid_actions(*args)
- Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!")
- actions(*args)
- end
+ alias :actions= :allowed_actions=
- # Set the run context on the class. Used to provide access to the node
- # during class definition.
- def self.run_context=(run_context)
- @run_context = run_context
- end
+ # @deprecated
+ def valid_actions(*args)
+ Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!")
+ allowed_actions(*args)
+ end
- def self.run_context
- @run_context
- end
+ # Set the run context on the class. Used to provide access to the node
+ # during class definition.
+ attr_accessor :run_context
- def self.node
- run_context.node
- end
+ def node
+ run_context ? run_context.node : nil
+ end
- def self.lazy(&block)
- DelayedEvaluator.new(&block)
- end
+ def lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
- private
+ protected
- # Get the value from the superclass, if it responds, otherwise return
- # +nil+. Since class instance variables are **not** inherited upon
- # subclassing, this is a required check to ensure Chef pulls the
- # +default_action+ and other DSL-y methods when extending LWRP::Base.
- def self.from_superclass(m, default = nil)
- return default if superclass == Chef::Resource::LWRPBase
- superclass.respond_to?(m) ? superclass.send(m) : default
- end
+ def loaded_lwrps
+ @loaded_lwrps ||= {}
+ end
- # Default initializer. Sets the default action and allowed actions.
- def initialize(name, run_context=nil)
- super(name, run_context)
+ private
- # Raise an exception if the resource_name was not defined
- if self.class.resource_name.nil?
- raise Chef::Exceptions::InvalidResourceSpecification,
- "You must specify `resource_name'!"
+ # Get the value from the superclass, if it responds, otherwise return
+ # +nil+. Since class instance variables are **not** inherited upon
+ # subclassing, this is a required check to ensure Chef pulls the
+ # +default_action+ and other DSL-y methods when extending LWRP::Base.
+ def from_superclass(m, default = nil)
+ return default if superclass == Chef::Resource::LWRPBase
+ superclass.respond_to?(m) ? superclass.send(m) : default
end
-
- @resource_name = self.class.resource_name.to_sym
- @action = self.class.default_action
- allowed_actions.push(self.class.actions).flatten!
end
-
end
end
end
diff --git a/lib/chef/resource/macosx_service.rb b/lib/chef/resource/macosx_service.rb
index 879ea99cf8..f1ed4051cb 100644
--- a/lib/chef/resource/macosx_service.rb
+++ b/lib/chef/resource/macosx_service.rb
@@ -22,8 +22,8 @@ class Chef
class Resource
class MacosxService < Chef::Resource::Service
- provides :service, os: "darwin"
provides :macosx_service, os: "darwin"
+ provides :service, os: "darwin"
identity_attr :service_name
@@ -31,7 +31,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :macosx_service
@plist = nil
@session_type = nil
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 0d4e5dec65..937839b6e1 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -19,14 +19,7 @@
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
-
- provides :macports_package
provides :package, os: "darwin"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :macports_package
- end
end
end
end
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 971b6c51b4..b789fab155 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -27,11 +27,11 @@ class Chef
state_attrs :devices, :level, :chunk
- provides :mdadm
+ default_action :create
+ allowed_actions :create, :assemble, :stop
def initialize(name, run_context=nil)
super
- @resource_name = :mdadm
@chunk = 16
@devices = []
@@ -40,9 +40,6 @@ class Chef
@metadata = "0.90"
@bitmap = nil
@raid_device = name
-
- @action = :create
- @allowed_actions.push(:create, :assemble, :stop)
end
def chunk(arg=nil)
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 142dec87f7..79986d127f 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -27,11 +27,11 @@ class Chef
state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain
- provides :mount
+ default_action :mount
+ allowed_actions :mount, :umount, :remount, :enable, :disable
def initialize(name, run_context=nil)
super
- @resource_name = :mount
@mount_point = name
@device = nil
@device_type = :device
@@ -42,9 +42,7 @@ class Chef
@pass = 2
@mounted = false
@enabled = false
- @action = :mount
@supports = { :remount => false }
- @allowed_actions.push(:mount, :umount, :remount, :enable, :disable)
@username = nil
@password = nil
@domain = nil
diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb
index b567db40f9..9425e55c0c 100644
--- a/lib/chef/resource/ohai.rb
+++ b/lib/chef/resource/ohai.rb
@@ -25,12 +25,11 @@ class Chef
state_attrs :plugin
+ default_action :reload
+
def initialize(name, run_context=nil)
super
- @resource_name = :ohai
@name = name
- @allowed_actions.push(:reload)
- @action = :reload
@plugin = nil
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index 20a2523e3a..f91fdb37e0 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -30,11 +30,6 @@ class Chef
provides :package, os: "openbsd"
- def initialize(name, run_context=nil)
- super
- @resource_name = :openbsd_package
- end
-
def after_created
assign_provider
end
@@ -48,4 +43,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index f4f49b543b..1c6da75678 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -22,24 +22,23 @@ require 'chef/resource'
class Chef
class Resource
class Package < Chef::Resource
-
identity_attr :package_name
state_attrs :version, :options
+ default_action :install
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig
+
def initialize(name, run_context=nil)
super
- @action = :install
- @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
@candidate_version = nil
@options = nil
@package_name = name
- @resource_name = :package
@response_file = nil
@response_file_variables = Hash.new
@source = nil
@version = nil
- @timeout = 900
+ @timeout = nil
end
def package_name(arg=nil)
@@ -101,3 +100,8 @@ 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/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index 4c45dd004f..54b8efc4c2 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -21,14 +21,7 @@ require 'chef/resource/package'
class Chef
class Resource
class PacmanPackage < Chef::Resource::Package
-
provides :pacman_package, os: "linux"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :pacman_package
- end
-
end
end
end
diff --git a/lib/chef/resource/paludis_package.rb b/lib/chef/resource/paludis_package.rb
index 552c96857a..56c47bc141 100644
--- a/lib/chef/resource/paludis_package.rb
+++ b/lib/chef/resource/paludis_package.rb
@@ -22,13 +22,12 @@ require 'chef/provider/package/paludis'
class Chef
class Resource
class PaludisPackage < Chef::Resource::Package
-
provides :paludis_package, os: "linux"
+ allowed_actions :install, :remove, :upgrade
+
def initialize(name, run_context=nil)
super(name, run_context)
- @resource_name = :paludis_package
- @allowed_actions.push(:install, :remove, :upgrade)
@timeout = 3600
end
end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index c4bdb6e130..773eba6571 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -22,10 +22,8 @@ require 'chef/provider/script'
class Chef
class Resource
class Perl < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :perl
@interpreter = "perl"
end
diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb
index 42c03560b6..1af48702fa 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -21,10 +21,8 @@ require 'chef/resource/package'
class Chef
class Resource
class PortagePackage < Chef::Resource::Package
-
def initialize(name, run_context=nil)
super
- @resource_name = :portage_package
@provider = Chef::Provider::Package::Portage
end
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index 43aafe4df2..7d432883e4 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -20,11 +20,10 @@ require 'chef/resource/windows_script'
class Chef
class Resource
class PowershellScript < Chef::Resource::WindowsScript
-
provides :powershell_script, os: "windows"
def initialize(name, run_context=nil)
- super(name, run_context, :powershell_script, "powershell.exe")
+ super(name, run_context, nil, "powershell.exe")
@convert_boolean_return = false
end
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index b1f23d13ce..432ee46b85 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -21,10 +21,8 @@ require 'chef/provider/script'
class Chef
class Resource
class Python < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :python
@interpreter = "python"
end
diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb
index c111b23d2e..401f2f338f 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -24,11 +24,11 @@ require 'chef/resource'
class Chef
class Resource
class Reboot < Chef::Resource
+ allowed_actions :request_reboot, :reboot_now, :cancel
+
def initialize(name, run_context=nil)
super
- @resource_name = :reboot
@provider = Chef::Provider::Reboot
- @allowed_actions.push(:request_reboot, :reboot_now, :cancel)
@reason = "Reboot by Chef"
@delay_mins = 0
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index 8126ccf126..4ed0d4a4e0 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -22,10 +22,12 @@ require 'chef/digester'
class Chef
class Resource
class RegistryKey < Chef::Resource
-
identity_attr :key
state_attrs :values
+ default_action :create
+ allowed_actions :create, :create_if_missing, :delete, :delete_key
+
# Some registry key data types may not be safely reported as json.
# Example (CHEF-5323):
#
@@ -59,13 +61,10 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :registry_key
- @action = :create
@architecture = :machine
@recursive = false
@key = name
@values, @unscrubbed_values = [], []
- @allowed_actions.push(:create, :create_if_missing, :delete, :delete_key)
end
def key(arg=nil)
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index d4108da47a..b731f7b201 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -26,19 +26,18 @@ class Chef
class RemoteDirectory < Chef::Resource::Directory
include Chef::Mixin::Securable
- provides :remote_directory
-
identity_attr :path
state_attrs :files_owner, :files_group, :files_mode
+ default_action :create
+ allowed_actions :create, :create_if_missing, :delete
+
def initialize(name, run_context=nil)
super
- @resource_name = :remote_directory
@path = name
@source = ::File.basename(name)
@delete = false
- @action = :create
@recursive = true
@purge = false
@files_backup = 5
@@ -46,7 +45,6 @@ class Chef
@files_group = nil
@files_mode = 0644 unless Chef::Platform.windows?
@overwrite = true
- @allowed_actions.push(:create, :create_if_missing, :delete)
@cookbook = nil
end
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index e56f69941d..b7a553cbe8 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -21,18 +21,15 @@ require 'uri'
require 'chef/resource/file'
require 'chef/provider/remote_file'
require 'chef/mixin/securable'
+require 'chef/mixin/uris'
class Chef
class Resource
class RemoteFile < Chef::Resource::File
include Chef::Mixin::Securable
- provides :remote_file
-
def initialize(name, run_context=nil)
super
- @resource_name = :remote_file
- @action = "create"
@source = []
@use_etag = true
@use_last_modified = true
@@ -127,6 +124,8 @@ class Chef
private
+ include Chef::Mixin::Uris
+
def validate_source(source)
source = Array(source).flatten
raise ArgumentError, "#{resource_name} has an empty source" if source.empty?
@@ -140,7 +139,7 @@ class Chef
end
def absolute_uri?(source)
- source.kind_of?(String) and URI.parse(source).absolute?
+ Chef::Provider::RemoteFile::Fetcher.network_share?(source) or (source.kind_of?(String) and as_uri(source).absolute?)
rescue URI::InvalidURIError
false
end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 942905d138..3ba8f6215b 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -22,17 +22,16 @@ require 'chef/resource'
class Chef
class Resource
class Route < Chef::Resource
-
identity_attr :target
state_attrs :netmask, :gateway
+ default_action :add
+ allowed_actions :add, :delete
+
def initialize(name, run_context=nil)
super
- @resource_name = :route
@target = name
- @action = [:add]
- @allowed_actions.push(:add, :delete)
@netmask = nil
@gateway = nil
@metric = nil
@@ -136,5 +135,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index f00121dd69..b8b5144a42 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -22,12 +22,10 @@ require 'chef/provider/package/rpm'
class Chef
class Resource
class RpmPackage < Chef::Resource::Package
-
provides :rpm_package, os: [ "linux", "aix" ]
def initialize(name, run_context=nil)
super
- @resource_name = :rpm_package
@allow_downgrade = false
end
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 2b2aa0249d..3c3909043d 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -22,13 +22,10 @@ require 'chef/provider/script'
class Chef
class Resource
class Ruby < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :ruby
@interpreter = "ruby"
end
-
end
end
end
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index a9cbf234cf..ae8e4cb7cd 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -23,14 +23,13 @@ require 'chef/provider/ruby_block'
class Chef
class Resource
class RubyBlock < Chef::Resource
+ default_action :run
+ allowed_actions :create, :run
identity_attr :block_name
def initialize(name, run_context=nil)
super
- @resource_name = :ruby_block
- @action = "run"
- @allowed_actions << :create << :run
@block_name = name
end
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 87c217b4cc..85028c266b 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -22,23 +22,22 @@ require 'chef/resource'
class Chef
class Resource
class Scm < Chef::Resource
-
identity_attr :destination
state_attrs :revision
+ default_action :sync
+ allowed_actions :checkout, :export, :sync, :diff, :log
+
def initialize(name, run_context=nil)
super
@destination = name
- @resource_name = :scm
@enable_submodules = false
@enable_checkout = true
@revision = "HEAD"
@remote = "origin"
@ssh_wrapper = nil
@depth = nil
- @allowed_actions.push(:checkout, :export, :sync, :diff, :log)
- @action = [:sync]
@checkout_branch = "deploy"
@environment = nil
end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index fd0fd5a7fd..30bed367cb 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -23,13 +23,11 @@ require 'chef/provider/script'
class Chef
class Resource
class Script < Chef::Resource::Execute
-
# Chef-13: go back to using :name as the identity attr
identity_attr :command
def initialize(name, run_context=nil)
super
- @resource_name = :script
# Chef-13: the command variable should be initialized to nil
@command = name
@code = nil
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c859a..aa59b543be 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -22,14 +22,15 @@ require 'chef/resource'
class Chef
class Resource
class Service < Chef::Resource
-
identity_attr :service_name
state_attrs :enabled, :running
+ default_action :nothing
+ allowed_actions :enable, :disable, :start, :stop, :restart, :reload
+
def initialize(name, run_context=nil)
super
- @resource_name = :service
@service_name = name
@enabled = nil
@running = nil
@@ -43,9 +44,7 @@ class Chef
@init_command = nil
@priority = nil
@timeout = nil
- @action = "nothing"
@supports = { :restart => false, :reload => false, :status => false }
- @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload)
end
def service_name(arg=nil)
diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb
index 99b3b953b0..b8bd940c24 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -22,16 +22,7 @@ require 'chef/provider/package/smartos'
class Chef
class Resource
class SmartosPackage < Chef::Resource::Package
-
- provides :smartos_package
provides :package, os: "solaris2", platform_family: "smartos"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :smartos_package
- end
-
end
end
end
-
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 94be4693b6..2dc72d5c47 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -23,21 +23,11 @@ require 'chef/provider/package/solaris'
class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
-
- provides :solaris_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
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :solaris_package
- end
-
end
end
end
-
-
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
index 3afbe0baaf..ae6a37caa2 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -22,13 +22,12 @@ require "chef/resource/scm"
class Chef
class Resource
class Subversion < Chef::Resource::Scm
+ allowed_actions :force_export
def initialize(name, run_context=nil)
super
@svn_arguments = '--no-auth-cache'
@svn_info_args = '--no-auth-cache'
- @resource_name = :subversion
- allowed_actions << :force_export
end
# Override exception to strip password if any, so it won't appear in logs and different Chef notifications
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 67a9e6a418..5a7f7efd6f 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -27,15 +27,11 @@ class Chef
class Template < Chef::Resource::File
include Chef::Mixin::Securable
- provides :template
-
attr_reader :inline_helper_blocks
attr_reader :inline_helper_modules
def initialize(name, run_context=nil)
super
- @resource_name = :template
- @action = "create"
@source = "#{::File.basename(name)}.erb"
@cookbook = nil
@local = false
diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb
index b2109db85c..344f8b0a5e 100644
--- a/lib/chef/resource/timestamped_deploy.rb
+++ b/lib/chef/resource/timestamped_deploy.rb
@@ -21,10 +21,6 @@ class Chef
# Convenience class for using the deploy resource with the timestamped
# deployment strategy (provider)
class TimestampedDeploy < Chef::Resource::Deploy
- provides :timestamped_deploy
- def initialize(*args, &block)
- super(*args, &block)
- end
end
end
end
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 7d2ec25596..b85b648c92 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -21,16 +21,15 @@ require 'chef/resource'
class Chef
class Resource
class User < Chef::Resource
-
identity_attr :username
state_attrs :uid, :gid, :home
- provides :user
+ default_action :create
+ allowed_actions :create, :remove, :modify, :manage, :lock, :unlock
def initialize(name, run_context=nil)
super
- @resource_name = :user
@username = name
@comment = nil
@uid = nil
@@ -42,14 +41,12 @@ class Chef
@manage_home = false
@force = false
@non_unique = false
- @action = :create
@supports = {
:manage_home => false,
:non_unique => false
}
@iterations = 27855
@salt = nil
- @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
end
def username(arg=nil)
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb
index 6fa5383f5d..f289f15001 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/whyrun_safe_ruby_block.rb
@@ -19,12 +19,6 @@
class Chef
class Resource
class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :whyrun_safe_ruby_block
- end
-
end
end
end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 16cfcf865e..a76765cc36 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/package'
require 'chef/provider/package/windows'
require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
@@ -23,14 +24,15 @@ require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
class Chef
class Resource
class WindowsPackage < Chef::Resource::Package
+ include Chef::Mixin::Uris
- provides :package, os: "windows"
provides :windows_package, os: "windows"
+ provides :package, os: "windows"
+
+ allowed_actions :install, :remove
def initialize(name, run_context=nil)
super
- @allowed_actions.push(:install, :remove)
- @resource_name = :windows_package
@source ||= source(@package_name)
# Unique to this resource
@@ -69,10 +71,30 @@ class Chef
@source
else
raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
- Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
- @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+ if uri_scheme?(arg)
+ @source = arg
+ else
+ @source = Chef::Util::PathHelper.canonical_path(arg, false)
+ end
end
end
+
+ def checksum(arg=nil)
+ set_or_return(
+ :checksum,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def remote_file_attributes(arg=nil)
+ set_or_return(
+ :remote_file_attributes,
+ arg,
+ :kind_of => [ Hash ]
+ )
+ end
+
end
end
end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 6b0827b77c..48e2b535a8 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -22,6 +22,7 @@ require 'chef/mixin/windows_architecture_helper'
class Chef
class Resource
class WindowsScript < Chef::Resource::Script
+ # This is an abstract resource meant to be subclasses; thus no 'provides'
set_guard_inherited_attributes(:architecture)
@@ -30,8 +31,8 @@ class Chef
def initialize(name, run_context, resource_name, interpreter_command)
super(name, run_context)
@interpreter = interpreter_command
- @resource_name = resource_name
- @default_guard_interpreter = resource_name
+ @resource_name = resource_name if resource_name
+ @default_guard_interpreter = self.resource_name
end
include Chef::Mixin::WindowsArchitectureHelper
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 8090adceb0..a77690652e 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -25,8 +25,10 @@ class Chef
# Until #1773 is resolved, you need to manually specify the windows_service resource
# to use action :configure_startup and attribute startup_type
- provides :service, os: "windows"
provides :windows_service, os: "windows"
+ provides :service, os: "windows"
+
+ allowed_actions :configure_startup
identity_attr :service_name
@@ -34,8 +36,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :windows_service
- @allowed_actions.push(:configure_startup)
@startup_type = :automatic
@run_as_user = ""
@run_as_password = ""
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 8fbca9b097..4d54f6051f 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -22,13 +22,10 @@ require 'chef/provider/package/yum'
class Chef
class Resource
class YumPackage < Chef::Resource::Package
-
- provides :yum_package
provides :package, os: "linux", platform_family: [ "rhel", "fedora" ]
def initialize(name, run_context=nil)
super
- @resource_name = :yum_package
@flush_cache = { :before => false, :after => false }
@allow_downgrade = false
end
@@ -38,7 +35,7 @@ class Chef
set_or_return(
:arch,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String, Array ]
)
end
diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb
new file mode 100644
index 0000000000..f09a20e2c6
--- /dev/null
+++ b/lib/chef/resource/zypper_package.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/package'
+
+class Chef
+ class Resource
+ class ZypperPackage < Chef::Resource::Package
+ provides :package, platform_family: "suse"
+ end
+ end
+end
diff --git a/lib/chef/resource_builder.rb b/lib/chef/resource_builder.rb
index bb0962d128..9e9f7047a4 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -18,6 +18,10 @@
# NOTE: this was extracted from the Recipe DSL mixin, relevant specs are in spec/unit/recipe_spec.rb
+require 'chef/exceptions'
+require 'chef/resource'
+require 'chef/log'
+
class Chef
class ResourceBuilder
attr_reader :type
@@ -46,6 +50,9 @@ class Chef
raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?
@resource = resource_class.new(name, run_context)
+ if resource.resource_name.nil?
+ raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`! Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?"
+ end
resource.source_line = created_at
resource.declared_type = type
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index 9d6844129c..cffabb6786 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -50,6 +50,7 @@ class Chef
else
raise ArgumentError, "You must pass a block to a definition."
end
+ Chef::DSL::Definitions.add_definition(name)
true
end
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 1816fc857d..7d13a5a5ce 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -59,11 +59,11 @@ class Chef
# attrs.
def for_json
as_hash = {}
- as_hash["type"] = new_resource.class.dsl_name
+ as_hash["type"] = new_resource.resource_name.to_sym
as_hash["name"] = new_resource.name.to_s
as_hash["id"] = new_resource.identity.to_s
- as_hash["after"] = state(new_resource)
- as_hash["before"] = current_resource ? state(current_resource) : {}
+ as_hash["after"] = new_resource.state_for_resource_reporter
+ as_hash["before"] = current_resource ? current_resource.state_for_resource_reporter : {}
as_hash["duration"] = (elapsed_time * 1000).to_i.to_s
as_hash["delta"] = new_resource.diff if new_resource.respond_to?("diff")
as_hash["delta"] = "" if as_hash["delta"].nil?
@@ -89,13 +89,6 @@ class Chef
def success?
!self.exception
end
-
- def state(r)
- r.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = r.send(attr_name)
- state_attrs
- end
- end
end # End class ResouceReport
attr_reader :updated_resources
@@ -220,7 +213,7 @@ class Chef
# If we failed before we received the run_started callback, there's not much we can do
# in terms of reporting
if @run_status
- post_reporting_data
+ post_reporting_data
end
end
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index ff9d7aeb9f..31b39f7e24 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -21,81 +21,137 @@ require 'chef/platform/resource_priority_map'
class Chef
class ResourceResolver
+ #
+ # Resolve a resource by name.
+ #
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ #
+ def self.resolve(resource_name, node: nil, canonical: nil)
+ new(node, resource_name, canonical: canonical).resolve
+ end
+
+ #
+ # Resolve a list of all resources that implement the given DSL (in order of
+ # preference).
+ #
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality.
+ #
+ def self.list(resource_name, node: nil, canonical: nil)
+ new(node, resource_name, canonical: canonical).list
+ end
+
+ include Chef::Mixin::ConvertToClassName
+
+ # @api private
attr_reader :node
- attr_reader :resource
+ # @api private
+ attr_reader :resource_name
+ # @api private
+ def resource
+ Chef::Log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
+ resource_name
+ end
+ # @api private
attr_reader :action
-
- def initialize(node, resource)
+ # @api private
+ attr_reader :canonical
+
+ #
+ # Create a resolver.
+ #
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
+ # @api private use Chef::ResourceResolver.resolve or .list instead.
+ def initialize(node, resource_name, canonical: nil)
@node = node
- @resource = resource
- end
-
- # return a deterministically sorted list of Chef::Resource subclasses
- def resources
- @resources ||= Chef::Resource.descendants
+ @resource_name = resource_name.to_sym
+ @canonical = canonical
end
+ # @api private use Chef::ResourceResolver.resolve instead.
def resolve
- maybe_dynamic_resource_resolution(resource) ||
- maybe_chef_platform_lookup(resource)
- end
-
- # this cut looks at if the resource can handle the resource type on the node
- def enabled_handlers
- @enabled_handlers ||=
- resources.select do |klass|
- klass.provides?(node, resource)
- end.sort {|a,b| a.to_s <=> b.to_s }
- @enabled_handlers
- end
+ # log this so we know what resources will work for the generic resource on the node (early cut)
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
- private
+ handler = prioritized_handlers.first
- # try dynamically finding a resource based on querying the resources to see what they support
- def maybe_dynamic_resource_resolution(resource)
- # log this so we know what resources will work for the generic resource on the node (early cut)
- Chef::Log.debug "resources for generic #{resource} resource enabled on node include: #{enabled_handlers}"
-
- # if none of the resources specifically support the resource, we still need to pick one of the resources that are
- # enabled on the node to handle the why-run use case.
- handlers = enabled_handlers
-
- if handlers.count >= 2
- # this magic stack ranks the resources by where they appear in the resource_priority_map
- priority_list = [ get_priority_array(node, resource) ].flatten.compact
- handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
- if priority_list.index(handlers.first).nil?
- # if we had more than one and we picked one with a precidence of infinity that means that the resource_priority_map
- # entry for this resource is missing -- we should probably raise here and force resolution of the ambiguity.
- Chef::Log.warn "Ambiguous resource precedence: #{handlers}, please use Chef.set_resource_priority_array to provide determinism"
- end
- handlers = [ handlers.first ]
+ if handler
+ Chef::Log.debug "Resource for #{resource_name} is #{handler}"
+ else
+ Chef::Log.debug "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}"
end
- Chef::Log.debug "resources that survived replacement include: #{handlers}"
+ handler
+ end
- raise Chef::Exceptions::AmbiguousResourceResolution.new(resource, handlers) if handlers.count >= 2
+ # @api private
+ def list
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+ prioritized_handlers
+ end
- Chef::Log.debug "dynamic resource resolver FAILED to resolve a resouce" if handlers.empty?
+ #
+ # Whether this DSL is provided by the given resource_class.
+ #
+ # @api private
+ def provided_by?(resource_class)
+ !prioritized_handlers.include?(resource_class)
+ end
- return nil if handlers.empty?
+ protected
- handlers[0]
+ def priority_map
+ Chef::Platform::ResourcePriorityMap.instance
end
- # try the old static lookup of resources by mangling name to resource klass
- def maybe_chef_platform_lookup(resource)
- Chef::Resource.resource_matching_short_name(resource)
+ def prioritized_handlers
+ @prioritized_handlers ||=
+ priority_map.list_handlers(node, resource_name, canonical: canonical)
end
- # dep injection hooks
- def get_priority_array(node, resource_name)
- resource_priority_map.get_priority_array(node, resource_name)
- 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
- def resource_priority_map
- Chef::Platform::ResourcePriorityMap.instance
+ # 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
+ end
+ end
end
+ prepend Deprecated
end
end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 40b12a7c5f..af4dd8a642 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -80,6 +80,7 @@ require 'chef/resource/windows_package'
require 'chef/resource/yum_package'
require 'chef/resource/lwrp_base'
require 'chef/resource/bff_package'
+require 'chef/resource/zypper_package'
begin
# Optional resources chef_node, chef_client, machine, machine_image, etc.
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 2612714a19..f87cec9b76 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -64,6 +64,7 @@ class Chef
options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
+
super(url, options)
@decompressor = Decompressor.new(options)
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 4f0215bfd4..44b05f0cc0 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -86,6 +86,7 @@ class Chef
@reboot_info = {}
@node.run_context = self
+ @node.set_cookbook_attribute
@cookbook_compiler = nil
end
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 0eefded964..7cce6fa48c 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -63,6 +63,24 @@ class Chef
end
end
end
+
+ # Get an array of strings of the fully-qualified recipe names (with ::default appended) and
+ # with the versions in "NAME@VERSION" format.
+ #
+ # @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?('::')
+ recipe_name
+ else
+ "#{recipe_name}::default"
+ end
+ if @versions[recipe_name]
+ ret << "@#{@versions[recipe_name]}"
+ end
+ ret
+ end
+ end
end
end
end
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 0f181426b0..ce8ff296f4 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -39,15 +39,13 @@ class Chef::RunStatus
attr_accessor :run_id
+ attr_accessor :node
+
def initialize(node, events)
@node = node
@events = events
end
- def node
- @node
- end
-
# sets +start_time+ to the current time.
def start_clock
@start_time = Time.now
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index ec4a864cb3..764296f8c8 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -42,3 +42,5 @@ class Chef
use Chef::HTTP::RemoteRequestID
end
end
+
+require 'chef/config'
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index ee4fe78808..3a68785ce9 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -110,7 +110,7 @@ module Shell
conf.prompt_c = "chef#{leader(m)} > "
conf.return_format = " => %s \n"
- conf.prompt_i = "chef#{leader(m)} > "
+ conf.prompt_i = "chef#{leader(m)} (#{Chef::VERSION})> "
conf.prompt_n = "chef#{leader(m)} ?> "
conf.prompt_s = "chef#{leader(m)}%l> "
conf.use_tracer = false
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 42fa6b5fa1..717deb63c3 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,29 +21,85 @@ 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::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.
+#
+# Exception: self.list is backwards compatible with OSC 11
class Chef
class User
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
def initialize
- @name = ''
+ @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
- @admin = false
end
- def name(arg=nil)
- set_or_return(:name, arg,
+ 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 admin(arg=nil)
- set_or_return(:admin,
- arg, :kind_of => [TrueClass, FalseClass])
+ 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)
@@ -63,12 +119,17 @@ class Chef
def to_hash
result = {
- "name" => @name,
- "public_key" => @public_key,
- "admin" => @admin
+ "username" => @username
}
- result["private_key"] = @private_key if @private_key
- result["password"] = @password if @password
+ 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
@@ -77,21 +138,86 @@ class Chef
end
def destroy
- Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}")
+ # 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
- 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)
+ # 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::User.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)
+ 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::User.from_hash(self.to_hash.merge(updated_user))
end
@@ -107,31 +233,47 @@ class Chef
end
end
+ # Note: remove after API v0 no longer supported by client (and knife command).
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"])
+ 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[#{@name}]"
- end
-
- def inspect
- "Chef::User name:'#{name}' admin:'#{admin.inspect}'" +
- "public_key:'#{public_key}' private_key:#{private_key}"
+ "user[#{@username}]"
end
# Class Methods
def self.from_hash(user_hash)
user = Chef::User.new
- user.name user_hash['name']
- user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+ 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']
- user.admin user_hash['admin']
+ 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
@@ -144,12 +286,19 @@ class Chef
end
def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
users = if response.is_a?(Array)
- transform_ohc_list_response(response) # OHC/OPC
- else
- response # OSC
- end
+ # 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::User.load(name)
@@ -160,8 +309,9 @@ class Chef
end
end
- def self.load(name)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
+ 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::User.from_hash(response)
end
@@ -169,7 +319,7 @@ class Chef
# [ { "user" => { "username" => USERNAME }}, ...]
# into the form
# { "USERNAME" => "URI" }
- def self.transform_ohc_list_response(response)
+ def self.transform_list_response(response)
new_response = Hash.new
response.each do |u|
name = u['user']['username']
@@ -178,6 +328,7 @@ class Chef
new_response
end
- private_class_method :transform_ohc_list_response
+ private_class_method :transform_list_response
+
end
end
diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb
index 0cc009ca1f..6c95cedad7 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -78,8 +78,16 @@ class Chef
Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
end
+ def unsorted_backup_files
+ # If you replace this with Dir[], you will probably break Windows.
+ fn = Regexp.escape(::File.basename(path))
+ Dir.entries(::File.dirname(backup_path)).select do |f|
+ !!(f =~ /\A#{fn}.chef-[0-9.]*\B/)
+ end.map {|f| ::File.join(::File.dirname(backup_path), f)}
+ end
+
def sorted_backup_files
- Dir[Chef::Util::PathHelper.escape_glob(prefix, ".#{path}") + ".chef-*"].sort { |a,b| b <=> a }
+ unsorted_backup_files.sort { |a,b| b <=> a }
end
end
end
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 66c2e3f19f..9ebc9319b8 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -16,212 +16,10 @@
# limitations under the License.
#
+require 'chef-config/path_helper'
+
class Chef
class Util
- class PathHelper
- # Maximum characters in a standard Windows path (260 including drive letter and NUL)
- WIN_MAX_PATH = 259
-
- def self.dirname(path)
- if Chef::Platform.windows?
- # Find the first slash, not counting trailing slashes
- end_slash = path.size
- loop do
- slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
- if !slash
- return end_slash == path.size ? '.' : path_separator
- elsif slash == end_slash - 1
- end_slash = slash
- else
- return path[0..slash-1]
- end
- end
- else
- ::File.dirname(path)
- end
- end
-
- BACKSLASH = '\\'.freeze
-
- def self.path_separator
- if Chef::Platform.windows?
- File::ALT_SEPARATOR || BACKSLASH
- else
- File::SEPARATOR
- end
- end
-
- def self.join(*args)
- args.flatten.inject do |joined_path, component|
- # Joined path ends with /
- joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
- component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
- joined_path += "#{path_separator}#{component}"
- end
- end
-
- def self.validate_path(path)
- if Chef::Platform.windows?
- unless printable?(path)
- msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
- Chef::Log.error(msg)
- raise Chef::Exceptions::ValidationFailed, msg
- end
-
- if windows_max_length_exceeded?(path)
- Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
- path.insert(0, "\\\\?\\")
- end
- end
-
- path
- end
-
- def self.windows_max_length_exceeded?(path)
- # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
- unless path =~ /^\\\\?\\/
- if path.length > WIN_MAX_PATH
- return true
- end
- end
-
- false
- end
-
- def self.printable?(string)
- # returns true if string is free of non-printable characters (escape sequences)
- # this returns false for whitespace escape sequences as well, e.g. \n\t
- if string =~ /[^[:print:]]/
- false
- else
- true
- end
- end
-
- # Produces a comparable path.
- def self.canonical_path(path, add_prefix=true)
- # First remove extra separators and resolve any relative paths
- abs_path = File.absolute_path(path)
-
- if Chef::Platform.windows?
- # Add the \\?\ API prefix on Windows unless add_prefix is false
- # Downcase on Windows where paths are still case-insensitive
- abs_path.gsub!(::File::SEPARATOR, path_separator)
- if add_prefix && abs_path !~ /^\\\\?\\/
- abs_path.insert(0, "\\\\?\\")
- end
-
- abs_path.downcase!
- end
-
- abs_path
- end
-
- def self.cleanpath(path)
- path = Pathname.new(path).cleanpath.to_s
- # ensure all forward slashes are backslashes
- if Chef::Platform.windows?
- path = path.gsub(File::SEPARATOR, path_separator)
- end
- path
- end
-
- def self.paths_eql?(path1, path2)
- canonical_path(path1) == canonical_path(path2)
- end
-
- # Paths which may contain glob-reserved characters need
- # to be escaped before globbing can be done.
- # http://stackoverflow.com/questions/14127343
- def self.escape_glob(*parts)
- path = cleanpath(join(*parts))
- path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
- end
-
- def self.relative_path_from(from, to)
- pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
- end
-
- # Retrieves the "home directory" of the current user while trying to ascertain the existence
- # of said directory. The path returned uses / for all separators (the ruby standard format).
- # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
- #
- # If a set of path elements is provided, they are appended as-is to the home path if the
- # homepath exists.
- #
- # If an optional block is provided, the joined path is passed to that block if the home path is
- # valid and the result of the block is returned instead.
- #
- # Home-path discovery is performed once. If a path is discovered, that value is memoized so
- # that subsequent calls to home_dir don't bounce around.
- #
- # See self.all_homes.
- def self.home(*args)
- @@home_dir ||= self.all_homes { |p| break p }
- if @@home_dir
- path = File.join(@@home_dir, *args)
- block_given? ? (yield path) : path
- end
- end
-
- # See self.home. This method performs a similar operation except that it yields all the different
- # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
- # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
- # This method goes out and checks the existence of each location at the time of the call.
- #
- # The return is a list of all the returned values from each block invocation or a list of paths
- # if no block is provided.
- def self.all_homes(*args)
- paths = []
- if Chef::Platform.windows?
- # By default, Ruby uses the the following environment variables to determine Dir.home:
- # HOME
- # HOMEDRIVE HOMEPATH
- # USERPROFILE
- # Ruby only checks to see if the variable is specified - not if the directory actually exists.
- # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
- # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
- # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
- # HOMESHARE instead of HOMEDRIVE.
- #
- # We instead walk down the following and only include paths that actually exist.
- # HOME
- # HOMEDRIVE HOMEPATH
- # HOMESHARE HOMEPATH
- # USERPROFILE
-
- paths << ENV['HOME']
- paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
- paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
- paths << ENV['USERPROFILE']
- end
- paths << Dir.home if ENV['HOME']
-
- # Depending on what environment variables we're using, the slashes can go in any which way.
- # Just change them all to / to keep things consistent.
- # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
- # the particular brand of kool-aid you consume. This code assumes that \ and / are both
- # path separators on any system being used.
- paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
-
- # Filter out duplicate paths and paths that don't exist.
- valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
- valid_paths = valid_paths.uniq
-
- # Join all optional path elements at the end.
- # If a block is provided, invoke it - otherwise just return what we've got.
- joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
- if block_given?
- joined_paths.each { |p| yield p }
- else
- joined_paths
- end
- end
- end
+ PathHelper = ChefConfig::PathHelper
end
end
-
-# Break a require loop when require chef/util/path_helper
-require 'chef/platform'
-require 'chef/exceptions'
diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 5df1a8aaa4..26fbe53db6 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -18,98 +18,69 @@
require 'chef/util/windows'
require 'chef/exceptions'
+require 'chef/win32/net'
+require 'chef/win32/security'
#wrapper around a subset of the NetUser* APIs.
#nothing Chef specific, but not complete enough to be its own gem, so util for now.
class Chef::Util::Windows::NetUser < Chef::Util::Windows
private
-
- LogonUser = Windows::API.new('LogonUser', 'SSSLLP', 'I', 'advapi32')
-
- DOMAIN_GROUP_RID_USERS = 0x00000201
-
- UF_SCRIPT = 0x000001
- UF_ACCOUNTDISABLE = 0x000002
- UF_PASSWD_CANT_CHANGE = 0x000040
- UF_NORMAL_ACCOUNT = 0x000200
- UF_DONT_EXPIRE_PASSWD = 0x010000
-
- #[:symbol_name, default_val]
- #default_val duals as field type
- #array index duals as structure offset
-
- #OC-8391
- #Changing [:password, nil], to [:password, ""],
- #if :password is set to nil, windows user creation api ignores the password policy applied
- #thus initializing it with empty string value.
- USER_INFO_3 = [
- [:name, nil],
- [:password, ""],
- [:password_age, 0],
- [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member"
- [:home_dir, nil],
- [:comment, nil],
- [:flags, UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT],
- [:script_path, nil],
- [:auth_flags, 0],
- [:full_name, nil],
- [:user_comment, nil],
- [:parms, nil],
- [:workstations, nil],
- [:last_logon, 0],
- [:last_logoff, 0],
- [:acct_expires, -1],
- [:max_storage, -1],
- [:units_per_week, 0],
- [:logon_hours, nil],
- [:bad_pw_count, 0],
- [:num_logons, 0],
- [:logon_server, nil],
- [:country_code, 0],
- [:code_page, 0],
- [:user_id, 0],
- [:primary_group_id, DOMAIN_GROUP_RID_USERS],
- [:profile, nil],
- [:home_dir_drive, nil],
- [:password_expired, 0]
- ]
-
- USER_INFO_3_TEMPLATE =
- USER_INFO_3.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
- SIZEOF_USER_INFO_3 = #sizeof(USER_INFO_3)
- USER_INFO_3.inject(0){|sum,item|
- sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
- }
-
- def user_info_3(args)
- USER_INFO_3.collect { |field|
- args.include?(field[0]) ? args[field[0]] : field[1]
- }
- end
-
- def user_info_3_pack(user)
- user.collect { |v|
- v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
- }.pack(USER_INFO_3_TEMPLATE)
+ NetUser = Chef::ReservedNames::Win32::NetUser
+ Security = Chef::ReservedNames::Win32::Security
+
+ USER_INFO_3_TRANSFORM = {
+ name: :usri3_name,
+ password: :usri3_password,
+ password_age: :usri3_password_age,
+ priv: :usri3_priv,
+ home_dir: :usri3_home_dir,
+ comment: :usri3_comment,
+ flags: :usri3_flags,
+ script_path: :usri3_script_path,
+ auth_flags: :usri3_auth_flags,
+ full_name: :usri3_full_name,
+ user_comment: :usri3_usr_comment,
+ parms: :usri3_parms,
+ workstations: :usri3_workstations,
+ last_logon: :usri3_last_logon,
+ last_logoff: :usri3_last_logoff,
+ acct_expires: :usri3_acct_expires,
+ max_storage: :usri3_max_storage,
+ units_per_week: :usri3_units_per_week,
+ logon_hours: :usri3_logon_hours,
+ bad_pw_count: :usri3_bad_pw_count,
+ num_logons: :usri3_num_logons,
+ logon_server: :usri3_logon_server,
+ country_code: :usri3_country_code,
+ code_page: :usri3_code_page,
+ user_id: :usri3_user_id,
+ primary_group_id: :usri3_primary_group_id,
+ profile: :usri3_profile,
+ home_dir_drive: :usri3_home_dir_drive,
+ password_expired: :usri3_password_expired,
+ }
+
+ def transform_usri3(args)
+ args.inject({}) do |memo, (k,v)|
+ memo[USER_INFO_3_TRANSFORM[k]] = v
+ memo
+ end
end
- def user_info_3_unpack(buffer)
- user = Hash.new
- USER_INFO_3.each_with_index do |field,offset|
- user[field[0]] = field[1].class == Fixnum ?
- dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+ def usri3_to_hash(usri3)
+ t = USER_INFO_3_TRANSFORM.invert
+ usri3.inject({}) do |memo, (k,v)|
+ memo[t[k]] = v
+ memo
end
- user
end
def set_info(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
- rc = NetUserSetInfo.call(nil, @name, 3, buffer, nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ rc = NetUser::net_user_set_info_l3(nil, @username, transform_usri3(args))
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
@@ -120,49 +91,32 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
@name = multi_to_wide(username)
end
- LOGON32_PROVIDER_DEFAULT = 0
- LOGON32_LOGON_NETWORK = 3
+ LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT
+ LOGON32_LOGON_NETWORK = Security::LOGON32_LOGON_NETWORK
#XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548
def validate_credentials(passwd)
- token = 0.chr * PTR_SIZE
- res = LogonUser.call(@username, nil, passwd,
- LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token)
- if res == 0
+ begin
+ token = Security::logon_user(@username, nil, passwd,
+ LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
+ return true
+ rescue Chef::Exceptions::Win32APIError
return false
end
- ::Windows::Handle::CloseHandle.call(token.unpack('L')[0])
- return true
end
def get_info
- ptr = 0.chr * PTR_SIZE
- rc = NetUserGetInfo.call(nil, @name, 3, ptr)
-
- if rc == NERR_UserNotFound
- raise Chef::Exceptions::UserIDNotFound, get_last_error(rc)
- elsif rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ ui3 = NetUser::net_user_get_info_l3(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
-
- ptr = ptr.unpack('L')[0]
- buffer = 0.chr * SIZEOF_USER_INFO_3
- memcpy(buffer, ptr, buffer.size)
- NetApiBufferFree(ptr)
- user_info_3_unpack(buffer)
+ usri3_to_hash(ui3)
end
def add(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
-
- rc = NetUserAdd.call(nil, 3, buffer, rc)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
-
- #usri3_primary_group_id:
- #"When you call the NetUserAdd function, this member must be DOMAIN_GROUP_RID_USERS"
- NetLocalGroupAddMembers(nil, multi_to_wide("Users"), 3, buffer[0,PTR_SIZE], 1)
+ transformed_args = transform_usri3(args)
+ NetUser::net_user_add_l3(nil, transformed_args)
+ NetUser::net_local_group_add_member(nil, "Users", args[:name])
end
def user_modify(&proc)
@@ -182,15 +136,16 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def delete
- rc = NetUserDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ NetUser::net_user_del(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def disable_account
user_modify do |user|
- user[:flags] |= UF_ACCOUNTDISABLE
+ user[:flags] |= NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -200,7 +155,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def enable_account
user_modify do |user|
- user[:flags] &= ~UF_ACCOUNTDISABLE
+ user[:flags] &= ~NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -209,6 +164,6 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def check_enabled
- (get_info()[:flags] & UF_ACCOUNTDISABLE) != 0
+ (get_info()[:flags] & NetUser::UF_ACCOUNTDISABLE) != 0
end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 30ede54095..f4cf71c002 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -1,6 +1,4 @@
-
-# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# NOTE: This file is generated by running `rake version` in the top level of
+# this repo. Do not edit this manually. Edit the VERSION file and run the rake
+# task instead.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.dev.0'
+ VERSION = '12.4.0.rc.0'
end
#
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index efa632f454..e9d273808a 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -67,7 +67,7 @@ class Chef
# BaseTsd.h: #ifdef (_WIN64) host.typedef int HALF_PTR; #else host.typedef short HALF_PTR;
host.typedef :ulong, :HACCEL # (L) Handle to an accelerator table. WinDef.h: #host.typedef HANDLE HACCEL;
# See http://msdn.microsoft.com/en-us/library/ms645526%28VS.85%29.aspx
- host.typedef :ulong, :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
+ host.typedef :size_t, :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
# todo: Platform-dependent! Need to change to :uint64 for Win64
host.typedef :ulong, :HBITMAP # (L) Handle to a bitmap: http://msdn.microsoft.com/en-us/library/dd183377%28VS.85%29.aspx
host.typedef :ulong, :HBRUSH # (L) Handle to a brush. http://msdn.microsoft.com/en-us/library/dd183394%28VS.85%29.aspx
@@ -117,6 +117,7 @@ class Chef
host.typedef :uint32, :LCID # Locale identifier. For more information, see Locales.
host.typedef :uint32, :LCTYPE # Locale information type. For a list, see Locale Information Constants.
host.typedef :uint32, :LGRPID # Language group identifier. For a list, see EnumLanguageGroupLocales.
+ host.typedef :pointer, :LMSTR # Pointer to null termiated string of unicode characters
host.typedef :long, :LONG # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int32, :LONG32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int64, :LONG64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb
index 6864a26e7d..b4851eccf1 100644
--- a/lib/chef/win32/api/installer.rb
+++ b/lib/chef/win32/api/installer.rb
@@ -158,7 +158,7 @@ UINT MsiCloseHandle(
raise Chef::Exceptions::Package, msg
end
- version
+ version.chomp(0.chr)
end
end
end
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index eeb2b078a4..72caf46628 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -32,8 +32,24 @@ class Chef
MAX_PREFERRED_LENGTH = 0xFFFF
- NERR_Success = 0
- NERR_UserNotFound = 2221
+ DOMAIN_GROUP_RID_USERS = 0x00000201
+
+ UF_SCRIPT = 0x000001
+ UF_ACCOUNTDISABLE = 0x000002
+ UF_PASSWD_CANT_CHANGE = 0x000040
+ UF_NORMAL_ACCOUNT = 0x000200
+ UF_DONT_EXPIRE_PASSWD = 0x010000
+
+ NERR_Success = 0
+ NERR_InvalidComputer = 2351
+ NERR_NotPrimary = 2226
+ NERR_SpeGroupOp = 2234
+ NERR_LastAdmin = 2452
+ NERR_BadUsername = 2202
+ NERR_BadPassword = 2203
+ NERR_PasswordTooShort = 2245
+ NERR_UserNotFound = 2221
+ ERROR_ACCESS_DENIED = 5
ffi_lib "netapi32"
@@ -67,6 +83,57 @@ class Chef
:usri3_profile, :LPWSTR,
:usri3_home_dir_drive, :LPWSTR,
:usri3_password_expired, :DWORD
+
+ def set(key, val)
+ val = if val.is_a? String
+ encoded = if val.encoding == Encoding::UTF_16LE
+ val
+ else
+ val.to_wstring
+ end
+ FFI::MemoryPointer.from_string(encoded)
+ else
+ val
+ end
+ self[key] = val
+ end
+
+ def get(key)
+ if respond_to? key
+ send(key)
+ else
+ val = self[key]
+ if val.is_a? FFI::Pointer
+ if val.null?
+ nil
+ else
+ val.read_wstring
+ end
+ else
+ val
+ end
+ end
+ end
+
+ def usri3_logon_hours
+ val = self[:usri3_logon_hours]
+ if !val.nil? && !val.null?
+ val.read_bytes(21)
+ else
+ nil
+ end
+ end
+
+ def as_ruby
+ members.inject({}) do |memo, key|
+ memo[key] = get(key)
+ memo
+ end
+ end
+ end
+
+ class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
+ layout :lgrmi3_domainandname, :LPWSTR
end
# NET_API_STATUS NetUserEnum(
@@ -85,6 +152,52 @@ class Chef
# _In_ LPVOID Buffer
# );
safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD
+
+#NET_API_STATUS NetUserAdd(
+ #_In_ LMSTR servername,
+ #_In_ DWORD level,
+ #_In_ LPBYTE buf,
+ #_Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetUserAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD
+
+#NET_API_STATUS NetLocalGroupAddMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupAddMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD
+
+#NET_API_STATUS NetUserGetInfo(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username,
+# _In_ DWORD level,
+# _Out_ LPBYTE *bufptr
+#);
+ safe_attach_function :NetUserGetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE], :DWORD
+
+#NET_API_STATUS NetApiBufferFree(
+# _In_ LPVOID Buffer
+#);
+ safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
+
+#NET_API_STATUS NetUserSetInfo(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetUserSetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
+
+#NET_API_STATUS NetUserDel(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username
+#);
+ safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
end
end
end
diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb
index 229f2ace10..4c352a3554 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -193,6 +193,20 @@ class Chef
MAXDWORD = 0xffffffff
+ # LOGON32 constants for LogonUser
+ LOGON32_LOGON_INTERACTIVE = 2;
+ LOGON32_LOGON_NETWORK = 3;
+ LOGON32_LOGON_BATCH = 4;
+ LOGON32_LOGON_SERVICE = 5;
+ LOGON32_LOGON_UNLOCK = 7;
+ LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
+ LOGON32_LOGON_NEW_CREDENTIALS = 9;
+
+ LOGON32_PROVIDER_DEFAULT = 0;
+ LOGON32_PROVIDER_WINNT35 = 1;
+ LOGON32_PROVIDER_WINNT40 = 2;
+ LOGON32_PROVIDER_WINNT50 = 3;
+
###############################################
# Win32 API Bindings
###############################################
@@ -270,6 +284,14 @@ class Chef
:MaxTokenInfoClass
]
+ class TOKEN_OWNER < FFI::Struct
+ layout :Owner, :pointer
+ end
+
+ class TOKEN_PRIMARY_GROUP < FFI::Struct
+ layout :PrimaryGroup, :pointer
+ end
+
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572%28v=vs.85%29.aspx
SECURITY_IMPERSONATION_LEVEL = enum :SECURITY_IMPERSONATION_LEVEL, [
:SecurityAnonymous,
@@ -405,6 +427,8 @@ class Chef
safe_attach_function :SetSecurityDescriptorOwner, [ :pointer, :pointer, :BOOL ], :BOOL
safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL
safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL
+ safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL
+
end
end
end
diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb
index 0b2cb09a6b..2e3a599f0a 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -139,7 +139,7 @@ int WideCharToMultiByte(
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[-1].chr != "\000"
+ 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
diff --git a/lib/chef/win32/eventlog.rb b/lib/chef/win32/eventlog.rb
new file mode 100644
index 0000000000..24af2da0d6
--- /dev/null
+++ b/lib/chef/win32/eventlog.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: 2015, Chef Software, Inc.
+#
+# 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.
+#
+
+if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+ if !defined? Chef::Win32EventLogLoaded
+ if defined? Windows::Constants
+ [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
+ # These are redefined in 'win32/eventlog'
+ Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
+ end
+ end
+
+ require 'win32/eventlog'
+ Chef::Win32EventLogLoaded = true
+ end
+end
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
new file mode 100644
index 0000000000..1349091eb9
--- /dev/null
+++ b/lib/chef/win32/net.rb
@@ -0,0 +1,190 @@
+#
+# 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.
+#
+
+require 'chef/win32/api/net'
+require 'chef/win32/error'
+require 'chef/mixin/wstring'
+
+class Chef
+ module ReservedNames::Win32
+ class NetUser
+ include Chef::ReservedNames::Win32::API::Error
+ extend Chef::ReservedNames::Win32::API::Error
+
+ include Chef::ReservedNames::Win32::API::Net
+ extend Chef::ReservedNames::Win32::API::Net
+
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
+ def self.default_user_info_3
+ ui3 = USER_INFO_3.new.tap do |s|
+ { usri3_name: nil,
+ usri3_password: nil,
+ usri3_password_age: 0,
+ usri3_priv: 0,
+ usri3_home_dir: nil,
+ usri3_comment: nil,
+ usri3_flags: UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT,
+ usri3_script_path: nil,
+ usri3_auth_flags: 0,
+ usri3_full_name: nil,
+ usri3_usr_comment: nil,
+ usri3_parms: nil,
+ usri3_workstations: nil,
+ usri3_last_logon: 0,
+ usri3_last_logoff: 0,
+ usri3_acct_expires: -1,
+ usri3_max_storage: -1,
+ usri3_units_per_week: 0,
+ usri3_logon_hours: nil,
+ usri3_bad_pw_count: 0,
+ usri3_num_logons: 0,
+ usri3_logon_server: nil,
+ usri3_country_code: 0,
+ usri3_code_page: 0,
+ usri3_user_id: 0,
+ usri3_primary_group_id: DOMAIN_GROUP_RID_USERS,
+ usri3_profile: nil,
+ usri3_home_dir_drive: nil,
+ usri3_password_expired: 0
+ }.each do |(k,v)|
+ s.set(k, v)
+ end
+ end
+ end
+
+ def self.net_api_error!(code)
+ msg = case code
+ when NERR_InvalidComputer
+ "The user does not have access to the requested information."
+ when NERR_NotPrimary
+ "The operation is allowed only on the primary domain controller of the domain."
+ when NERR_SpeGroupOp
+ "This operation is not allowed on this special group."
+ when NERR_LastAdmin
+ "This operation is not allowed on the last administrative account."
+ when NERR_BadUsername
+ "The user name or group name parameter is invalid."
+ when NERR_BadPassword
+ "The password parameter is invalid."
+ when NERR_UserNotFound
+ raise Chef::Exceptions::UserIDNotFound, code
+ when NERR_PasswordTooShort
+ <<END
+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 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::Win32APIError, msg + "\n" + formatted_message
+ end
+
+ def self.net_user_add_l3(server_name, args)
+ buf = default_user_info_3
+
+ args.each do |k, v|
+ buf.set(k, v)
+ end
+
+ server_name = wstring(server_name)
+
+ rc = NetUserAdd(server_name, 3, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_user_get_info_l3(server_name, user_name)
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ ui3_p = FFI::MemoryPointer.new(:pointer)
+
+ rc = NetUserGetInfo(server_name, user_name, 3, ui3_p)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui3 = USER_INFO_3.new(ui3_p.read_pointer).as_ruby
+
+ rc = NetApiBufferFree(ui3_p.read_pointer)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui3
+ end
+
+ def self.net_user_set_info_l3(server_name, user_name, info)
+ buf = default_user_info_3
+
+ info.each do |k, v|
+ buf.set(k, v)
+ end
+
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ rc = NetUserSetInfo(server_name, user_name, 3, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_user_del(server_name, user_name)
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ rc = NetUserDel(server_name, user_name)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_add_member(server_name, group_name, domain_user)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+ domain_user = wstring(domain_user)
+
+ buf = LOCALGROUP_MEMBERS_INFO_3.new
+ buf[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(domain_user)
+
+ rc = NetLocalGroupAddMembers(server_name, group_name, 3, buf, 1)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 3902d8caaf..5c83180bc0 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -22,6 +22,7 @@ require 'chef/win32/memory'
require 'chef/win32/process'
require 'chef/win32/unicode'
require 'chef/win32/security/token'
+require 'chef/mixin/wstring'
class Chef
module ReservedNames::Win32
@@ -31,6 +32,8 @@ class Chef
include Chef::ReservedNames::Win32::API::Security
extend Chef::ReservedNames::Win32::API::Security
extend Chef::ReservedNames::Win32::API::Macros
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
def self.access_check(security_descriptor, token, desired_access, generic_mapping)
token_handle = token.handle.handle
@@ -270,6 +273,36 @@ class Chef
[ present.read_char != 0, acl.null? ? nil : ACL.new(acl, security_descriptor), defaulted.read_char != 0 ]
end
+ def self.get_token_information_owner(token)
+ owner_result_size = FFI::MemoryPointer.new(:ulong)
+ if GetTokenInformation(token.handle.handle, :TokenOwner, nil, 0, owner_result_size)
+ raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ owner_result_storage = FFI::MemoryPointer.new owner_result_size.read_ulong
+ unless GetTokenInformation(token.handle.handle, :TokenOwner, owner_result_storage, owner_result_size.read_ulong, owner_result_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ owner_result = TOKEN_OWNER.new owner_result_storage
+ SID.new(owner_result[:Owner], owner_result_storage)
+ end
+
+ def self.get_token_information_primary_group(token)
+ group_result_size = FFI::MemoryPointer.new(:ulong)
+ if GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, nil, 0, group_result_size)
+ raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ group_result_storage = FFI::MemoryPointer.new group_result_size.read_ulong
+ unless GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, group_result_storage, group_result_size.read_ulong, group_result_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ group_result = TOKEN_PRIMARY_GROUP.new group_result_storage
+ SID.new(group_result[:PrimaryGroup], group_result_storage)
+ end
+
def self.initialize_acl(acl_size)
acl = FFI::MemoryPointer.new acl_size
unless InitializeAcl(acl, acl_size, ACL_REVISION)
@@ -415,6 +448,10 @@ class Chef
[ SecurityDescriptor.new(absolute_sd), SID.new(owner), SID.new(group), ACL.new(dacl), ACL.new(sacl) ]
end
+ def self.open_current_process_token(desired_access = TOKEN_READ)
+ open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, desired_access)
+ end
+
def self.open_process_token(process, desired_access)
process = process.handle if process.respond_to?(:handle)
process = process.handle if process.respond_to?(:handle)
@@ -513,7 +550,7 @@ class Chef
def self.with_privileges(*privilege_names)
# Set privileges
- token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
+ token = open_current_process_token(TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
old_privileges = token.enable_privileges(*privilege_names)
# Let the caller do their privileged stuff
@@ -533,7 +570,7 @@ class Chef
true
else
- process_token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ)
+ process_token = open_current_process_token(TOKEN_READ)
elevation_result = FFI::Buffer.new(:ulong)
elevation_result_size = FFI::MemoryPointer.new(:uint32)
success = GetTokenInformation(process_token.handle.handle, :TokenElevation, elevation_result, 4, elevation_result_size)
@@ -543,6 +580,18 @@ class Chef
success && (elevation_result.read_ulong != 0)
end
end
+
+ def self.logon_user(username, domain, password, logon_type, logon_provider)
+ username = wstring(username)
+ domain = wstring(domain)
+ password = wstring(password)
+
+ token = FFI::Buffer.new(:pointer)
+ unless LogonUserW(username, domain, password, logon_type, logon_provider, token)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ Token.new(Handle.new(token.read_pointer))
+ end
end
end
end
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index 8e9407dc80..f8bd934876 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -203,6 +203,23 @@ class Chef
SID.from_account("#{::ENV['USERDOMAIN']}\\#{::ENV['USERNAME']}")
end
+ # See https://technet.microsoft.com/en-us/library/cc961992.aspx
+ # In practice, this is SID.Administrators if the current_user is an admin (even if not
+ # running elevated), and is current_user otherwise. On win2k3, it technically can be
+ # current_user in all cases if a certain group policy is set.
+ def self.default_security_object_owner
+ token = Chef::ReservedNames::Win32::Security.open_current_process_token
+ Chef::ReservedNames::Win32::Security.get_token_information_owner(token)
+ end
+
+ # See https://technet.microsoft.com/en-us/library/cc961996.aspx
+ # In practice, this seems to be SID.current_user for Microsoft Accounts, the current
+ # user's Domain Users group for domain accounts, and SID.None otherwise.
+ def self.default_security_object_group
+ token = Chef::ReservedNames::Win32::Security.open_current_process_token
+ Chef::ReservedNames::Win32::Security.get_token_information_primary_group(token)
+ end
+
def self.admin_account_name
@admin_account_name ||= begin
admin_account_name = nil