summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/application.rb72
-rw-r--r--lib/chef/application/apply.rb18
-rw-r--r--lib/chef/application/client.rb91
-rw-r--r--lib/chef/application/solo.rb45
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb2
-rw-r--r--lib/chef/chef_fs/command_line.rb2
-rw-r--r--lib/chef/chef_fs/file_system/organization_invites_entry.rb3
-rw-r--r--lib/chef/chef_fs/file_system/organization_members_entry.rb3
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb3
-rw-r--r--lib/chef/client.rb58
-rw-r--r--lib/chef/config.rb28
-rw-r--r--lib/chef/cookbook/cookbook_version_loader.rb14
-rw-r--r--lib/chef/cookbook/metadata.rb119
-rw-r--r--lib/chef/cookbook/syntax_check.rb19
-rw-r--r--lib/chef/cookbook_version.rb97
-rw-r--r--lib/chef/dsl/data_query.rb31
-rw-r--r--lib/chef/dsl/recipe.rb29
-rw-r--r--lib/chef/encrypted_data_bag_item/assertions.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/check_encrypted.rb56
-rw-r--r--lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb2
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb21
-rw-r--r--lib/chef/http.rb33
-rw-r--r--lib/chef/json_compat.rb10
-rw-r--r--lib/chef/knife.rb3
-rw-r--r--lib/chef/knife/bootstrap.rb16
-rw-r--r--lib/chef/knife/bootstrap/archlinux-gems.erb4
-rw-r--r--lib/chef/knife/bootstrap/chef-aix.erb4
-rw-r--r--lib/chef/knife/bootstrap/chef-full.erb4
-rw-r--r--lib/chef/knife/cookbook_create.rb2
-rw-r--r--lib/chef/knife/cookbook_site_share.rb51
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb13
-rw-r--r--lib/chef/knife/core/status_presenter.rb156
-rw-r--r--lib/chef/knife/core/ui.rb2
-rw-r--r--lib/chef/knife/data_bag_create.rb39
-rw-r--r--lib/chef/knife/data_bag_edit.rb67
-rw-r--r--lib/chef/knife/data_bag_from_file.rb34
-rw-r--r--lib/chef/knife/data_bag_secret_options.rb142
-rw-r--r--lib/chef/knife/data_bag_show.rb52
-rw-r--r--lib/chef/knife/status.rb69
-rw-r--r--lib/chef/mixin/command/unix.rb4
-rw-r--r--lib/chef/mixin/homebrew_owner.rb58
-rw-r--r--lib/chef/mixin/homebrew_user.rb68
-rw-r--r--lib/chef/mixin/shell_out.rb4
-rw-r--r--lib/chef/mixin/windows_env_helper.rb56
-rw-r--r--lib/chef/platform/provider_mapping.rb8
-rw-r--r--lib/chef/provider/cron/unix.rb22
-rw-r--r--lib/chef/provider/deploy.rb2
-rw-r--r--lib/chef/provider/dsc_script.rb37
-rw-r--r--lib/chef/provider/env/windows.rb19
-rw-r--r--lib/chef/provider/execute.rb5
-rw-r--r--lib/chef/provider/git.rb72
-rw-r--r--lib/chef/provider/lwrp_base.rb20
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb2
-rw-r--r--lib/chef/provider/package/freebsd/port.rb10
-rw-r--r--lib/chef/provider/package/homebrew.rb12
-rw-r--r--lib/chef/provider/package/pacman.rb6
-rw-r--r--lib/chef/provider/package/paludis.rb2
-rw-r--r--lib/chef/provider/package/rubygems.rb1
-rw-r--r--lib/chef/provider/remote_directory.rb32
-rw-r--r--lib/chef/recipe.rb2
-rw-r--r--lib/chef/resource.rb44
-rw-r--r--lib/chef/resource/conditional.rb32
-rw-r--r--lib/chef/resource/cookbook_file.rb2
-rw-r--r--lib/chef/resource/dsc_script.rb18
-rw-r--r--lib/chef/resource/execute.rb34
-rw-r--r--lib/chef/resource/freebsd_package.rb31
-rw-r--r--lib/chef/resource/homebrew_package.rb11
-rw-r--r--lib/chef/resource/lwrp_base.rb39
-rw-r--r--lib/chef/resource/script.rb26
-rw-r--r--lib/chef/resource/template.rb2
-rw-r--r--lib/chef/resource_collection.rb279
-rw-r--r--lib/chef/resource_collection/resource_collection_serialization.rb59
-rw-r--r--lib/chef/resource_collection/resource_list.rb101
-rw-r--r--lib/chef/resource_collection/resource_set.rb170
-rw-r--r--lib/chef/shell/ext.rb2
-rw-r--r--lib/chef/util/dsc/local_configuration_manager.rb6
-rw-r--r--lib/chef/util/path_helper.rb4
-rw-r--r--lib/chef/util/powershell/cmdlet_result.rb4
-rw-r--r--lib/chef/win32/api/system.rb9
-rw-r--r--lib/chef/win32/version.rb2
80 files changed, 1720 insertions, 1013 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 0430d4acfa..abcc81c290 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -62,6 +62,10 @@ class Chef::Application
Chef::Application.fatal!("SIGINT received, stopping", 2)
end
+ trap("TERM") do
+ Chef::Application.fatal!("SIGTERM received, stopping", 3)
+ end
+
unless Chef::Platform.windows?
trap("QUIT") do
Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n "))
@@ -206,12 +210,78 @@ class Chef::Application
)
@chef_client_json = nil
- @chef_client.run
+ if can_fork?
+ fork_chef_client # allowed to run client in forked process
+ else
+ # Unforked interval runs are disabled, so this runs chef-client
+ # once and then exits. If TERM signal is received, will "ignore"
+ # the signal to finish converge.
+ run_with_graceful_exit_option
+ end
@chef_client = nil
end
end
private
+ def can_fork?
+ # win32-process gem exposes some form of :fork for Process
+ # class. So we are seperately ensuring that the platform we're
+ # running on is not windows before forking.
+ Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows?
+ end
+
+ # Run chef-client once and then exit. If TERM signal is received, ignores the
+ # signal to finish the converge and exists.
+ def run_with_graceful_exit_option
+ # Override the TERM signal.
+ trap('TERM') do
+ Chef::Log.debug("SIGTERM received during converge," +
+ " finishing converge to exit normally (send SIGINT to terminate immediately)")
+ end
+
+ @chef_client.run
+ true
+ end
+
+ def fork_chef_client
+ Chef::Log.info "Forking chef instance to converge..."
+ pid = fork do
+ # Want to allow forked processes to finish converging when
+ # TERM singal is received (exit gracefully)
+ trap('TERM') do
+ Chef::Log.debug("SIGTERM received during converge," +
+ " finishing converge to exit normally (send SIGINT to terminate immediately)")
+ end
+
+ client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client"
+ $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};"
+ begin
+ Chef::Log.debug "Forked instance now converging"
+ @chef_client.run
+ rescue Exception => e
+ Chef::Log.error(e.to_s)
+ exit 1
+ else
+ exit 0
+ end
+ end
+ Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}"
+ result = Process.waitpid2(pid)
+ handle_child_exit(result)
+ Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})"
+ true
+ end
+
+ def handle_child_exit(pid_and_status)
+ status = pid_and_status[1]
+ return true if status.success?
+ message = if status.signaled?
+ "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
+ else
+ "Chef run process exited unsuccessfully (exit code #{status.exitstatus})"
+ end
+ raise Exceptions::ChildConvergeError, message
+ end
def apply_config(config_content, config_file_path)
Chef::Config.from_string(config_content, config_file_path)
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index ea9154c6f2..22d835e876 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -31,7 +31,6 @@ class Chef::Application::Apply < Chef::Application
banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]"
-
option :execute,
:short => "-e RECIPE_TEXT",
:long => "--execute RECIPE_TEXT",
@@ -92,14 +91,17 @@ class Chef::Application::Apply < Chef::Application
end
def read_recipe_file(file_name)
- recipe_path = file_name
- unless File.exist?(recipe_path)
- Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
+ if file_name.nil?
+ Chef::Application.fatal!("No recipe file was provided", 1)
+ else
+ recipe_path = File.expand_path(file_name)
+ unless File.exist?(recipe_path)
+ Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
+ end
+ recipe_fh = open(recipe_path)
+ recipe_text = recipe_fh.read
+ [recipe_text, recipe_fh]
end
- recipe_path = File.expand_path(recipe_path)
- recipe_fh = open(recipe_path)
- recipe_text = recipe_fh.read
- [recipe_text, recipe_fh]
end
def get_recipe_and_run_context
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index 6c06ad656d..5463f504bc 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -238,8 +238,12 @@ class Chef::Application::Client < Chef::Application
:boolean => true
end
+ option :audit_mode,
+ :long => "--[no-]audit-mode",
+ :description => "If not specified, run converge and audit phase. If true, run only audit phase. If false, run only converge phase.",
+ :boolean => true
+
IMMEDIATE_RUN_SIGNAL = "1".freeze
- GRACEFUL_EXIT_SIGNAL = "2".freeze
attr_reader :chef_client_json
@@ -268,6 +272,8 @@ class Chef::Application::Client < Chef::Application
Chef::Config[:splay] = nil
end
+ Chef::Application.fatal!(unforked_interval_error_message) if !Chef::Config[:client_fork] && Chef::Config[:interval]
+
if Chef::Config[:json_attribs]
config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
@chef_client_json = config_fetcher.fetch_json
@@ -295,8 +301,9 @@ class Chef::Application::Client < Chef::Application
Chef::Daemon.change_privilege
end
- # Run the chef client, optionally daemonizing or looping at intervals.
- def run_application
+ def setup_signal_handlers
+ super
+
unless Chef::Platform.windows?
SELF_PIPE.replace IO.pipe
@@ -304,52 +311,54 @@ class Chef::Application::Client < Chef::Application
Chef::Log.info("SIGUSR1 received, waking up")
SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select
end
-
- # see CHEF-5172
- if Chef::Config[:daemonize] || Chef::Config[:interval]
- trap("TERM") do
- Chef::Log.info("SIGTERM received, exiting gracefully")
- SELF_PIPE[1].putc(GRACEFUL_EXIT_SIGNAL)
- end
- end
end
+ end
+ # Run the chef client, optionally daemonizing or looping at intervals.
+ def run_application
if Chef::Config[:version]
puts "Chef version: #{::Chef::VERSION}"
end
+ if !Chef::Config[:client_fork] || Chef::Config[:once]
+ begin
+ # run immediately without interval sleep, or splay
+ run_chef_client(Chef::Config[:specific_recipes])
+ rescue SystemExit
+ raise
+ rescue Exception => e
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ end
+ else
+ interval_run_chef_client
+ end
+ end
+
+ private
+ def interval_run_chef_client
if Chef::Config[:daemonize]
Chef::Daemon.daemonize("chef-client")
end
- signal = nil
-
loop do
begin
- Chef::Application.exit!("Exiting", 0) if signal == GRACEFUL_EXIT_SIGNAL
-
- if Chef::Config[:splay] and signal != IMMEDIATE_RUN_SIGNAL
- splay = rand Chef::Config[:splay]
- Chef::Log.debug("Splay sleep #{splay} seconds")
- sleep splay
+ @signal = test_signal
+ if @signal != IMMEDIATE_RUN_SIGNAL
+ sleep_sec = time_to_sleep
+ Chef::Log.debug("Sleeping for #{sleep_sec} seconds")
+ interval_sleep(sleep_sec)
end
- signal = nil
+ @signal = nil
run_chef_client(Chef::Config[:specific_recipes])
- if Chef::Config[:interval]
- Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
- signal = interval_sleep
- else
- Chef::Application.exit! "Exiting", 0
- end
+ Chef::Application.exit!("Exiting", 0) if !Chef::Config[:interval]
rescue SystemExit => e
raise
rescue Exception => e
if Chef::Config[:interval]
Chef::Log.error("#{e.class}: #{e}")
- Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
- signal = interval_sleep
+ Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
retry
else
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
@@ -358,19 +367,35 @@ class Chef::Application::Client < Chef::Application
end
end
- private
+ def test_signal
+ @signal = interval_sleep(0)
+ end
- def interval_sleep
+ def time_to_sleep
+ duration = 0
+ duration += rand(Chef::Config[:splay]) if Chef::Config[:splay]
+ duration += Chef::Config[:interval] if Chef::Config[:interval]
+ duration
+ end
+
+ def interval_sleep(sec)
unless SELF_PIPE.empty?
- client_sleep Chef::Config[:interval]
+ client_sleep(sec)
else
# Windows
- sleep Chef::Config[:interval]
+ sleep(sec)
end
end
def client_sleep(sec)
IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
- SELF_PIPE[0].getc.chr
+ @signal = SELF_PIPE[0].getc.chr
+ end
+
+ def unforked_interval_error_message
+ "Unforked chef-client interval runs are disabled in Chef 12." +
+ "\nConfiguration settings:" +
+ "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" +
+ "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
end
end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index f0e578d5ef..474bbf3f6c 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -185,6 +185,8 @@ class Chef::Application::Solo < Chef::Application
Chef::Config[:interval] ||= 1800
end
+ Chef::Application.fatal!(unforked_interval_error_message) if !Chef::Config[:client_fork] && Chef::Config[:interval]
+
if Chef::Config[:recipe_url]
cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ }
recipes_path = File.expand_path(File.join(cookbooks_path, '..'))
@@ -209,23 +211,39 @@ class Chef::Application::Solo < Chef::Application
end
def run_application
+ if !Chef::Config[:client_fork] || Chef::Config[:once]
+ # Run immediately without interval sleep or splay
+ begin
+ run_chef_client(Chef::Config[:specific_recipes])
+ rescue SystemExit
+ raise
+ rescue Exception => e
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ end
+ else
+ interval_run_chef_client
+ end
+ end
+
+ private
+ def interval_run_chef_client
if Chef::Config[:daemonize]
Chef::Daemon.daemonize("chef-client")
end
loop do
begin
- if Chef::Config[:splay]
- splay = rand Chef::Config[:splay]
- Chef::Log.debug("Splay sleep #{splay} seconds")
- sleep splay
+
+ sleep_sec = 0
+ sleep_sec += rand(Chef::Config[:splay]) if Chef::Config[:splay]
+ sleep_sec += Chef::Config[:interval] if Chef::Config[:interval]
+ if sleep_sec != 0
+ Chef::Log.debug("Sleeping for #{sleep_sec} seconds")
+ sleep(sleep_sec)
end
run_chef_client
- if Chef::Config[:interval]
- Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
- sleep Chef::Config[:interval]
- else
+ if !Chef::Config[:interval]
Chef::Application.exit! "Exiting", 0
end
rescue SystemExit => e
@@ -234,8 +252,6 @@ class Chef::Application::Solo < Chef::Application
if Chef::Config[:interval]
Chef::Log.error("#{e.class}: #{e}")
Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
- Chef::Log.fatal("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
- sleep Chef::Config[:interval]
retry
else
Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
@@ -244,8 +260,6 @@ class Chef::Application::Solo < Chef::Application
end
end
- private
-
def fetch_recipe_tarball(url, path)
Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
File.open(path, 'wb') do |f|
@@ -254,4 +268,11 @@ class Chef::Application::Solo < Chef::Application
end
end
end
+
+ def unforked_interval_error_message
+ "Unforked chef-client interval runs are disabled in Chef 12." +
+ "\nConfiguration settings:" +
+ "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" +
+ "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
+ end
end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index 484ab07390..3813d0edb4 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -168,7 +168,7 @@ class Chef
end
end
end
- JSON.pretty_generate(result)
+ Chef::JSONCompat.to_json_pretty(result)
else
begin
diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb
index 43e8b276e0..8a205eef78 100644
--- a/lib/chef/chef_fs/command_line.rb
+++ b/lib/chef/chef_fs/command_line.rb
@@ -253,7 +253,7 @@ class Chef
def self.canonicalize_json(json_text)
parsed_json = Chef::JSONCompat.parse(json_text)
sorted_json = sort_keys(parsed_json)
- JSON.pretty_generate(sorted_json)
+ Chef::JSONCompat.to_json_pretty(sorted_json)
end
def self.diff_text(old_path, new_path, old_value, new_value)
diff --git a/lib/chef/chef_fs/file_system/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
index cb26326050..5df37085cb 100644
--- a/lib/chef/chef_fs/file_system/organization_invites_entry.rb
+++ b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
@@ -1,5 +1,6 @@
require 'chef/chef_fs/file_system/rest_list_entry'
require 'chef/chef_fs/data_handler/organization_invites_data_handler'
+require 'chef/json_compat'
class Chef
module ChefFS
@@ -34,7 +35,7 @@ class Chef
end
def write(contents)
- desired_invites = minimize_value(JSON.parse(contents, :create_additions => false))
+ desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h }
invites = actual_invites.keys
(desired_invites - invites).each do |invite|
diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb
index eb524d5ea2..94393b341f 100644
--- a/lib/chef/chef_fs/file_system/organization_members_entry.rb
+++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb
@@ -1,5 +1,6 @@
require 'chef/chef_fs/file_system/rest_list_entry'
require 'chef/chef_fs/data_handler/organization_members_data_handler'
+require 'chef/json_compat'
class Chef
module ChefFS
@@ -34,7 +35,7 @@ class Chef
end
def write(contents)
- desired_members = minimize_value(JSON.parse(contents, :create_additions => false))
+ desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
members = minimize_value(_read_json)
(desired_members - members).each do |member|
begin
diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
index ac47ff4f25..f68794cb0d 100644
--- a/lib/chef/chef_fs/file_system/rest_list_entry.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb
@@ -21,6 +21,7 @@ require 'chef/chef_fs/file_system/not_found_error'
require 'chef/chef_fs/file_system/operation_failed_error'
require 'chef/role'
require 'chef/node'
+require 'chef/json_compat'
class Chef
module ChefFS
@@ -172,7 +173,7 @@ class Chef
def api_error_text(response)
begin
- JSON.parse(response.body)['error'].join("\n")
+ Chef::JSONCompat.parse(response.body)['error'].join("\n")
rescue
response.body
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 161ecddb0f..e8ff352116 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -191,54 +191,6 @@ class Chef
end
end
- # Do a full run for this Chef::Client. Calls:
- # * do_run
- #
- # This provides a wrapper around #do_run allowing the
- # run to be optionally forked.
- # === Returns
- # boolean:: Return value from #do_run. Should always returns true.
- def run
- # win32-process gem exposes some form of :fork for Process
- # class. So we are seperately ensuring that the platform we're
- # running on is not windows before forking.
- if(Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows?)
- Chef::Log.info "Forking chef instance to converge..."
- pid = fork do
- [:INT, :TERM].each {|s| trap(s, "EXIT") }
- client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client"
- $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};"
- begin
- Chef::Log.debug "Forked instance now converging"
- do_run
- rescue Exception => e
- Chef::Log.error(e.to_s)
- exit 1
- else
- exit 0
- end
- end
- Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}"
- result = Process.waitpid2(pid)
- handle_child_exit(result)
- Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})"
- true
- else
- do_run
- end
- end
-
- def handle_child_exit(pid_and_status)
- status = pid_and_status[1]
- return true if status.success?
- message = if status.signaled?
- "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
- else
- "Chef run process exited unsuccessfully (exit code #{status.exitstatus})"
- end
- raise Exceptions::ChildConvergeError, message
- end
-
# Instantiates a Chef::Node object, possibly loading the node's prior state
# when using chef-client. Delegates to policy_builder
#
@@ -330,7 +282,7 @@ class Chef
rescue Exception => e
# TODO: munge exception so a semantic failure message can be given to the
# user
- @events.registration_failed(node_name, e, config)
+ @events.registration_failed(client_name, e, config)
raise
end
@@ -383,8 +335,6 @@ class Chef
end
end
- private
-
# Do a full run for this Chef::Client. Calls:
#
# * run_ohai - Collect information about the system
@@ -395,7 +345,7 @@ class Chef
#
# === Returns
# true:: Always returns true.
- def do_run
+ 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
@@ -428,7 +378,7 @@ class Chef
run_context = setup_run_context
- catch (:end_client_run_early) do
+ catch(:end_client_run_early) do
converge(run_context)
end
@@ -466,6 +416,8 @@ class Chef
true
end
+ private
+
def empty_directory?(path)
!File.exists?(path) || (Dir.entries(path).size <= 2)
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 107b50ee85..9a8117d2c2 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -25,11 +25,13 @@ require 'mixlib/config'
require 'chef/util/selinux'
require 'chef/util/path_helper'
require 'pathname'
+require 'chef/mixin/shell_out'
class Chef
class Config
extend Mixlib::Config
+ extend Chef::Mixin::ShellOut
PathHelper = Chef::Util::PathHelper
@@ -604,25 +606,37 @@ class Chef
# available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
default :internal_locale do
begin
- locales = `locale -a`.split
+ # 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')
+ 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'
- when guesses = locales.select { |l| l =~ /^en_.*UTF-8$'/ }
- guesses.first
else
- Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
- 'C'
+ # 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.warn "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+ Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
end
'en_US.UTF-8'
end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index 5c9de6b8ca..bcbfcbeec8 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -81,7 +81,7 @@ class Chef
load_as(:attribute_filenames, 'attributes', '*.rb')
load_as(:definition_filenames, 'definitions', '*.rb')
load_as(:recipe_filenames, 'recipes', '*.rb')
- load_as(:library_filenames, 'libraries', '*.rb')
+ load_recursively_as(:library_filenames, 'libraries', '*.rb')
load_recursively_as(:template_filenames, "templates", "*")
load_recursively_as(:file_filenames, "files", "*")
load_recursively_as(:resource_filenames, "resources", "*.rb")
@@ -214,23 +214,29 @@ class Chef
def load_root_files
Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '*'), File::FNM_DOTMATCH).each do |file|
+ file = Chef::Util::PathHelper.cleanpath(file)
next if File.directory?(file)
next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE
- cookbook_settings[:root_filenames][file[@relative_path, 1]] = file
+ name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
+ cookbook_settings[:root_filenames][name] = file
end
end
def load_recursively_as(category, category_dir, glob)
file_spec = File.join(Chef::Util::PathHelper.escape_glob(cookbook_path, category_dir), '**', glob)
Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file|
+ file = Chef::Util::PathHelper.cleanpath(file)
next if File.directory?(file)
- cookbook_settings[category][file[@relative_path, 1]] = file
+ name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
+ cookbook_settings[category][name] = file
end
end
def load_as(category, *path_glob)
Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), *path_glob)].each do |file|
- cookbook_settings[category][file[@relative_path, 1]] = file
+ file = Chef::Util::PathHelper.cleanpath(file)
+ name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file)
+ cookbook_settings[category][name] = file
end
end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 3964354d50..54e930135d 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -35,28 +35,31 @@ class Chef
# about Chef Cookbooks.
class Metadata
- NAME = 'name'.freeze
- DESCRIPTION = 'description'.freeze
- LONG_DESCRIPTION = 'long_description'.freeze
- MAINTAINER = 'maintainer'.freeze
- MAINTAINER_EMAIL = 'maintainer_email'.freeze
- LICENSE = 'license'.freeze
- PLATFORMS = 'platforms'.freeze
- DEPENDENCIES = 'dependencies'.freeze
- RECOMMENDATIONS = 'recommendations'.freeze
- SUGGESTIONS = 'suggestions'.freeze
- CONFLICTING = 'conflicting'.freeze
- PROVIDING = 'providing'.freeze
- REPLACING = 'replacing'.freeze
- ATTRIBUTES = 'attributes'.freeze
- GROUPINGS = 'groupings'.freeze
- RECIPES = 'recipes'.freeze
- VERSION = 'version'.freeze
+ NAME = 'name'.freeze
+ DESCRIPTION = 'description'.freeze
+ LONG_DESCRIPTION = 'long_description'.freeze
+ MAINTAINER = 'maintainer'.freeze
+ MAINTAINER_EMAIL = 'maintainer_email'.freeze
+ LICENSE = 'license'.freeze
+ PLATFORMS = 'platforms'.freeze
+ DEPENDENCIES = 'dependencies'.freeze
+ RECOMMENDATIONS = 'recommendations'.freeze
+ SUGGESTIONS = 'suggestions'.freeze
+ CONFLICTING = 'conflicting'.freeze
+ PROVIDING = 'providing'.freeze
+ REPLACING = 'replacing'.freeze
+ ATTRIBUTES = 'attributes'.freeze
+ GROUPINGS = 'groupings'.freeze
+ RECIPES = 'recipes'.freeze
+ VERSION = 'version'.freeze
+ SOURCE_URL = 'source_url'.freeze
+ ISSUES_URL = 'issues_url'.freeze
COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
:maintainer_email, :license, :platforms, :dependencies,
:recommendations, :suggestions, :conflicting, :providing,
- :replacing, :attributes, :groupings, :recipes, :version]
+ :replacing, :attributes, :groupings, :recipes, :version,
+ :source_url, :issues_url ]
VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
:recommends => RECOMMENDATIONS,
@@ -111,6 +114,8 @@ class Chef
@groupings = Mash.new
@recipes = Mash.new
@version = Version.new("0.0.0")
+ @source_url = ''
+ @issues_url = ''
@errors = []
end
@@ -413,7 +418,7 @@ class Chef
end
end
- # Adds an attribute )hat a user needs to configure for this cookbook. Takes
+ # Adds an attribute that a user needs to configure for this cookbook. Takes
# a name (with the / notation for a nested attribute), followed by any of
# these options
#
@@ -443,7 +448,9 @@ class Chef
:type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" },
:required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" },
:recipes => { :kind_of => [ Array ], :default => [] },
- :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }
+ :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] },
+ :source_url => { :kind_of => String },
+ :issues_url => { :kind_of => String }
}
)
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
@@ -469,23 +476,25 @@ class Chef
def to_hash
{
- NAME => self.name,
- DESCRIPTION => self.description,
- LONG_DESCRIPTION => self.long_description,
- MAINTAINER => self.maintainer,
- MAINTAINER_EMAIL => self.maintainer_email,
- LICENSE => self.license,
- PLATFORMS => self.platforms,
- DEPENDENCIES => self.dependencies,
- RECOMMENDATIONS => self.recommendations,
- SUGGESTIONS => self.suggestions,
- CONFLICTING => self.conflicting,
- PROVIDING => self.providing,
- REPLACING => self.replacing,
- ATTRIBUTES => self.attributes,
- GROUPINGS => self.groupings,
- RECIPES => self.recipes,
- VERSION => self.version
+ NAME => self.name,
+ DESCRIPTION => self.description,
+ LONG_DESCRIPTION => self.long_description,
+ MAINTAINER => self.maintainer,
+ MAINTAINER_EMAIL => self.maintainer_email,
+ LICENSE => self.license,
+ PLATFORMS => self.platforms,
+ DEPENDENCIES => self.dependencies,
+ RECOMMENDATIONS => self.recommendations,
+ SUGGESTIONS => self.suggestions,
+ CONFLICTING => self.conflicting,
+ PROVIDING => self.providing,
+ REPLACING => self.replacing,
+ ATTRIBUTES => self.attributes,
+ GROUPINGS => self.groupings,
+ RECIPES => self.recipes,
+ VERSION => self.version,
+ SOURCE_URL => self.source_url,
+ ISSUES_URL => self.issues_url
}
end
@@ -517,6 +526,8 @@ class Chef
@groupings = o[GROUPINGS] if o.has_key?(GROUPINGS)
@recipes = o[RECIPES] if o.has_key?(RECIPES)
@version = o[VERSION] if o.has_key?(VERSION)
+ @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
+ @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
self
end
@@ -531,7 +542,9 @@ class Chef
VERSION_CONSTRAINTS.each do |dependency_type, hash_key|
if dependency_group = o[hash_key]
dependency_group.each do |cb_name, constraints|
- metadata.send(method_name, cb_name, *Array(constraints))
+ if metadata.respond_to?(method_name)
+ metadata.public_send(method_name, cb_name, *Array(constraints))
+ end
end
end
end
@@ -543,6 +556,36 @@ class Chef
from_hash(o)
end
+ # Sets the cookbook's source URL, or returns it.
+ #
+ # === Parameters
+ # maintainer<String>:: The source URL
+ #
+ # === Returns
+ # source_url<String>:: Returns the current source URL.
+ def source_url(arg=nil)
+ set_or_return(
+ :source_url,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # Sets the cookbook's issues URL, or returns it.
+ #
+ # === Parameters
+ # issues_url<String>:: The issues URL
+ #
+ # === Returns
+ # issues_url<String>:: Returns the current issues URL.
+ def issues_url(arg=nil)
+ set_or_return(
+ :issues_url,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
private
def run_validation
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index 69c194516b..96fc7e7b84 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -101,17 +101,24 @@ class Chef
end
def remove_ignored_files(file_list)
- return file_list unless chefignore.ignores.length > 0
+ return file_list if chefignore.ignores.empty?
+
file_list.reject do |full_path|
- cookbook_pn = Pathname.new cookbook_path
- full_pn = Pathname.new full_path
- relative_pn = full_pn.relative_path_from cookbook_pn
- chefignore.ignored? relative_pn.to_s
+ relative_pn = Chef::Util::PathHelper.relative_path_from(cookbook_path, full_path)
+ chefignore.ignored?(relative_pn.to_s)
end
end
+ def remove_uninteresting_ruby_files(file_list)
+ file_list.reject { |f| f =~ %r{#{cookbook_path}/(files|templates)/} }
+ end
+
def ruby_files
- remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '**', '*.rb')]
+ path = Chef::Util::PathHelper.escape_glob(cookbook_path)
+ files = Dir[File.join(path, '**', '*.rb')]
+ files = remove_ignored_files(files)
+ files = remove_uninteresting_ruby_files(files)
+ files
end
def untested_ruby_files
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 76e6d152b2..505b403e65 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -315,13 +315,20 @@ class Chef
else
if segment == :files || segment == :templates
error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n"
- error_locations = [
- " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
- " #{segment}/#{node[:platform]}/#{filename}",
- " #{segment}/default/#{filename}",
- ]
+ error_locations = if filename.is_a?(Array)
+ filename.map{|name| " #{File.join(segment.to_s, name)}"}
+ else
+ [
+ " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
+ " #{segment}/#{node[:platform]}/#{filename}",
+ " #{segment}/default/#{filename}",
+ " #{segment}/#{filename}",
+ ]
+ end
error_message << error_locations.join("\n")
existing_files = segment_filenames(segment)
+ # Strip the root_dir prefix off all files for readability
+ existing_files.map!{|path| path[root_dir.length+1..-1]} if root_dir
# Show the files that the cookbook does have. If the user made a typo,
# hopefully they'll see it here.
unless existing_files.empty?
@@ -421,38 +428,44 @@ class Chef
def preferences_for_path(node, segment, path)
# only files and templates can be platform-specific
if segment.to_sym == :files || segment.to_sym == :templates
- begin
- platform, version = Chef::Platform.find_platform_and_version(node)
- rescue ArgumentError => e
- # Skip platform/version if they were not found by find_platform_and_version
- if e.message =~ /Cannot find a (?:platform|version)/
- platform = "/unknown_platform/"
- version = "/unknown_platform_version/"
- else
- raise
+ relative_search_path = if path.is_a?(Array)
+ path
+ else
+ begin
+ platform, version = Chef::Platform.find_platform_and_version(node)
+ rescue ArgumentError => e
+ # Skip platform/version if they were not found by find_platform_and_version
+ if e.message =~ /Cannot find a (?:platform|version)/
+ platform = "/unknown_platform/"
+ version = "/unknown_platform_version/"
+ else
+ raise
+ end
end
- end
- fqdn = node[:fqdn]
+ fqdn = node[:fqdn]
- # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ]
- search_versions = []
- parts = version.to_s.split('.')
+ # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ]
+ search_versions = []
+ parts = version.to_s.split('.')
- parts.size.times do
- search_versions << parts.join('.')
- parts.pop
- end
+ parts.size.times do
+ search_versions << parts.join('.')
+ parts.pop
+ end
- # Most specific to least specific places to find the path
- search_path = [ File.join(segment.to_s, "host-#{fqdn}", path) ]
- search_versions.each do |v|
- search_path << File.join(segment.to_s, "#{platform}-#{v}", path)
- end
- search_path << File.join(segment.to_s, platform.to_s, path)
- search_path << File.join(segment.to_s, "default", path)
+ # Most specific to least specific places to find the path
+ search_path = [ File.join("host-#{fqdn}", path) ]
+ search_versions.each do |v|
+ search_path << File.join("#{platform}-#{v}", path)
+ end
+ search_path << File.join(platform.to_s, path)
+ search_path << File.join("default", path)
+ search_path << path
- search_path
+ search_path
+ end
+ relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)}
else
[File.join(segment, path)]
end
@@ -479,7 +492,7 @@ class Chef
cookbook_version.manifest = o
# We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
- cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(cookbook_version.metadata.to_json)
+ cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(cookbook_version.metadata))
cookbook_version.freeze_version if o["frozen?"]
cookbook_version
@@ -552,6 +565,11 @@ class Chef
chef_server_rest.get_rest('cookbooks')
end
+ # Alias latest_cookbooks as list
+ class << self
+ alias :latest_cookbooks :list
+ end
+
def self.list_all_versions
chef_server_rest.get_rest('cookbooks?num_versions=all')
end
@@ -575,11 +593,6 @@ class Chef
end
end
- # Get the newest version of all cookbooks
- def self.latest_cookbooks
- chef_server_rest.get_rest('cookbooks/_latest')
- end
-
def <=>(o)
raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
# FIXME: can we change the interface to the Metadata class such
@@ -660,13 +673,19 @@ class Chef
def parse_segment_file_from_root_paths(segment, segment_file)
root_paths.each do |root_path|
- pathname = Pathname.new(segment_file).relative_path_from(Pathname.new(root_path))
+ pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file)
parts = pathname.each_filename.take(2)
# Check if path is actually under root_path
next if parts[0] == '..'
if segment == :templates || segment == :files
- return [ pathname.to_s, parts[1] ]
+ # Check if pathname looks like files/foo or templates/foo (unscoped)
+ if pathname.each_filename.to_a.length == 2
+ # Use root_default in case the same path exists at root_default and default
+ return [ pathname.to_s, 'root_default' ]
+ else
+ return [ pathname.to_s, parts[1] ]
+ end
else
return [ pathname.to_s, 'default' ]
end
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
index 3dafbca6bf..e36784271a 100644
--- a/lib/chef/dsl/data_query.rb
+++ b/lib/chef/dsl/data_query.rb
@@ -20,6 +20,7 @@ require 'chef/search/query'
require 'chef/data_bag'
require 'chef/data_bag_item'
require 'chef/encrypted_data_bag_item'
+require 'chef/encrypted_data_bag_item/check_encrypted'
class Chef
module DSL
@@ -28,6 +29,7 @@ class Chef
# Provides DSL for querying data from the chef-server via search or data
# bag.
module DataQuery
+ include Chef::EncryptedDataBagItem::CheckEncrypted
def search(*args, &block)
# If you pass a block, or have at least the start argument, do raw result parsing
@@ -78,35 +80,6 @@ class Chef
raise
end
- private
-
- # Tries to autodetect if the item's raw hash appears to be encrypted.
- def encrypted?(raw_data)
- data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key.
- # Assume hashes containing only the "id" key are not encrypted.
- # Otherwise, remove the keys that don't appear to be encrypted and compare
- # the result with the hash. If some entry has been removed, then some entry
- # doesn't appear to be encrypted and we assume the entire hash is not encrypted.
- data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data
- end
-
- # Checks if data looks like it has been encrypted by
- # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns
- # true only when there is an exact match between the VersionXEncryptor
- # keys and the hash's keys.
- def looks_like_encrypted?(data)
- return false unless data.is_a?(Hash) && data.has_key?("version")
- case data["version"]
- when 1
- Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
- when 2
- Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort
- when 3
- Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort
- else
- false # version means something else... assume not encrypted.
- end
- end
end
end
end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index 3282320b8c..94b0d2d18b 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -19,6 +19,7 @@
require 'chef/resource_platform_map'
require 'chef/mixin/convert_to_class_name'
+require 'chef/exceptions'
class Chef
module DSL
@@ -52,9 +53,7 @@ class Chef
end
def has_resource_definition?(name)
- yes_or_no = run_context.definitions.has_key?(name)
-
- yes_or_no
+ run_context.definitions.has_key?(name)
end
# Processes the arguments and block as a resource definition.
@@ -68,12 +67,10 @@ class Chef
# 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)
- new_recipe
end
# Instantiates a resource (via #build_resource), then adds it to the
@@ -85,21 +82,7 @@ class Chef
resource = build_resource(type, name, created_at, &resource_attrs_block)
- # Some resources (freebsd_package) can be invoked with multiple names
- # (package || freebsd_package).
- # https://github.com/opscode/chef/issues/1773
- # For these resources we want to make sure
- # their key in resource collection is same as the name they are declared
- # as. Since this might be a breaking change for resources that define
- # customer to_s methods, we are working around the issue by letting
- # resources know of their created_as_type until this issue is fixed in
- # Chef 12:
- # https://github.com/opscode/chef/issues/1817
- if resource.respond_to?(:created_as_type=)
- resource.created_as_type = type
- end
-
- run_context.resource_collection.insert(resource)
+ run_context.resource_collection.insert(resource, resource_type:type, instance_name:name)
resource
end
@@ -123,7 +106,7 @@ class Chef
# This behavior is very counter-intuitive and should be removed.
# See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694
# Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052
- resource.load_prior_resource
+ resource.load_prior_resource(type, name)
resource.cookbook_name = cookbook_name
resource.recipe_name = recipe_name
# Determine whether this resource is being created in the context of an enclosing Provider
@@ -162,6 +145,10 @@ class Chef
end
end
+ def exec(args)
+ 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
+
end
end
end
diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb
index e9acebbd29..0f9416e7b6 100644
--- a/lib/chef/encrypted_data_bag_item/assertions.rb
+++ b/lib/chef/encrypted_data_bag_item/assertions.rb
@@ -45,7 +45,7 @@ class Chef::EncryptedDataBagItem
def assert_aead_requirements_met!(algorithm)
unless OpenSSL::Cipher.method_defined?(:auth_data=)
- raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9"
+ raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 2.0"
end
unless OpenSSL::Cipher.ciphers.include?(algorithm)
raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support"
diff --git a/lib/chef/encrypted_data_bag_item/check_encrypted.rb b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
new file mode 100644
index 0000000000..b7cb5841b3
--- /dev/null
+++ b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2010-2014 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/encrypted_data_bag_item/encryptor'
+
+class Chef::EncryptedDataBagItem
+ # Common code for checking if a data bag appears encrypted
+ module CheckEncrypted
+
+ # Tries to autodetect if the item's raw hash appears to be encrypted.
+ def encrypted?(raw_data)
+ data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key.
+ # Assume hashes containing only the "id" key are not encrypted.
+ # Otherwise, remove the keys that don't appear to be encrypted and compare
+ # the result with the hash. If some entry has been removed, then some entry
+ # doesn't appear to be encrypted and we assume the entire hash is not encrypted.
+ data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data
+ end
+
+ private
+
+ # Checks if data looks like it has been encrypted by
+ # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns
+ # true only when there is an exact match between the VersionXEncryptor
+ # keys and the hash's keys.
+ def looks_like_encrypted?(data)
+ return false unless data.is_a?(Hash) && data.has_key?("version")
+ case data["version"]
+ when 1
+ Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
+ when 2
+ Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort
+ when 3
+ Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort
+ else
+ false # version means something else... assume not encrypted.
+ end
+ end
+
+ end
+end
diff --git a/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb
index 3567925844..9a54396174 100644
--- a/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb
+++ b/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb
@@ -25,7 +25,7 @@ class Chef::EncryptedDataBagItem
def assert_requirements_met!
unless OpenSSL::Cipher.method_defined?(:auth_data=)
- raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 1.9"
+ raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 2.0"
end
unless OpenSSL::Cipher.ciphers.include?(algorithm)
raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support"
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index 229a8502c7..346b585d8c 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -33,10 +33,19 @@ class Chef
# to the resource
merge_inherited_attributes
- # Script resources have a code attribute, which is
- # what is used to execute the command, so include
- # that with attributes specified by caller in opts
- block_attributes = @command_opts.merge({:code => @command})
+ # Only execute and script resources and use guard attributes.
+ # The command to be executed on them are passed via different attributes.
+ # Script resources use code attribute and execute resources use
+ # command attribute. Moreover script resources are also execute
+ # resources. Here we make sure @command is assigned to the right
+ # attribute by checking the type of the resources.
+ # We need to make sure we check for Script first because any resource
+ # that can get to here is an Execute resource.
+ if @parent_resource.is_a? Chef::Resource::Script
+ block_attributes = @command_opts.merge({:code => @command})
+ else
+ block_attributes = @command_opts.merge({:command => @command})
+ end
# Handles cases like powershell_script where default
# attributes are different when used in a guard vs. not. For
@@ -79,8 +88,8 @@ class Chef
raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform"
end
- if ! resource_class.ancestors.include?(Chef::Resource::Script)
- raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Script resource"
+ if ! resource_class.ancestors.include?(Chef::Resource::Execute)
+ raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource"
end
empty_events = Chef::EventDispatch::Dispatcher.new
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index abc47f636e..ee951bd675 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -271,7 +271,7 @@ class Chef
elsif redirect_location = redirected_to(response)
if [:GET, :HEAD].include?(method)
follow_redirect do
- send_http_request(method, create_url(redirect_location), headers, body, &response_handler)
+ send_http_request(method, url+redirect_location, headers, body, &response_handler)
end
else
raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects."
@@ -289,11 +289,26 @@ class Chef
def retrying_http_errors(url)
http_attempts = 0
begin
- http_attempts += 1
-
- yield
-
+ loop do
+ http_attempts += 1
+ response, request, return_value = yield
+ # handle HTTP 50X Error
+ if response.kind_of?(Net::HTTPServerError)
+ if http_retry_count - http_attempts + 1 > 0
+ sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
+ Chef::Log.error("Server returned error #{response.code} for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
+ sleep(sleep_time)
+ redo
+ end
+ end
+ return [response, request, return_value]
+ end
rescue SocketError, Errno::ETIMEDOUT => e
+ if http_retry_count - http_attempts + 1 > 0
+ Chef::Log.error("Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
+ sleep(http_retry_delay)
+ retry
+ end
e.message.replace "Error connecting to #{url} - #{e.message}"
raise e
rescue Errno::ECONNREFUSED
@@ -310,14 +325,6 @@ class Chef
retry
end
raise Timeout::Error, "Timeout connecting to #{url}, giving up"
- rescue Net::HTTPFatalError => e
- if http_retry_count - http_attempts + 1 > 0
- sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
- Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
- sleep(sleep_time)
- retry
- end
- raise
end
end
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index e92d5c36ae..0796984ab2 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -18,9 +18,9 @@
# Wrapper class for interacting with JSON.
require 'ffi_yajl'
-require 'json'
-require 'ffi_yajl/json_gem' # XXX: parts of chef require JSON gem's Hash#to_json monkeypatch
require 'chef/exceptions'
+# We're requiring this to prevent breaking consumers using Hash.to_json
+require 'json'
class Chef
class JSONCompat
@@ -39,6 +39,8 @@ class Chef
CHEF_SANDBOX = "Chef::Sandbox".freeze
CHEF_RESOURCE = "Chef::Resource".freeze
CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
+ CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze
+ CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze
class <<self
@@ -145,6 +147,10 @@ class Chef
Chef::Resource
when CHEF_RESOURCECOLLECTION
Chef::ResourceCollection
+ when CHEF_RESOURCESET
+ Chef::ResourceCollection::ResourceSet
+ when CHEF_RESOURCELIST
+ Chef::ResourceCollection::ResourceList
when /^Chef::Resource/
Chef::Resource.find_subclass_by_name(json_class)
else
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 6421384f01..0c079792a4 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -164,7 +164,6 @@ class Chef
def self.load_config(explicit_config_file)
config_loader = WorkstationConfigLoader.new(explicit_config_file, Chef::Log)
- Chef::Log.debug("Using configuration from #{config_loader.config_location}")
config_loader.load
ui.warn("No knife configuration file found") if config_loader.no_config_found?
@@ -393,6 +392,8 @@ class Chef
merge_configs
apply_computed_config
+ # This has to be after apply_computed_config so that Mixlib::Log is configured
+ Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file]
end
def show_usage
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 36a0fc1e47..a992cf5779 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -17,11 +17,13 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
require 'erubis'
class Chef
class Knife
class Bootstrap < Knife
+ include DataBagSecretOptions
deps do
require 'chef/knife/core/bootstrap_context'
@@ -157,17 +159,6 @@ class Chef
Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
}
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
option :bootstrap_url,
:long => "--bootstrap-url URL",
:description => "URL to a custom installation script",
@@ -248,7 +239,8 @@ class Chef
def render_template
template_file = find_template
template = IO.read(template_file).chomp
- context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config)
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret)
Erubis::Eruby.new(template).evaluate(context)
end
diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb
index eb134b90d5..581293daa3 100644
--- a/lib/chef/knife/bootstrap/archlinux-gems.erb
+++ b/lib/chef/knife/bootstrap/archlinux-gems.erb
@@ -34,7 +34,7 @@ mkdir -p /etc/chef/ohai/hints
<% @chef_config[:knife][:hints].each do |name, hash| -%>
cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
+<%= Chef::JSONCompat.to_json(hash) %>
EOP
<% end -%>
<% end -%>
@@ -61,7 +61,7 @@ https_proxy "<%= knife_config[:bootstrap_proxy] %>"
EOP
cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
+<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/chef-aix.erb b/lib/chef/knife/bootstrap/chef-aix.erb
index 3a031ee738..013ad1decb 100644
--- a/lib/chef/knife/bootstrap/chef-aix.erb
+++ b/lib/chef/knife/bootstrap/chef-aix.erb
@@ -47,7 +47,7 @@ mkdir -p /etc/chef/ohai/hints
<% @chef_config[:knife][:hints].each do |name, hash| -%>
cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
+<%= Chef::JSONCompat.to_json(hash) %>
EOP
<% end -%>
<% end -%>
@@ -57,7 +57,7 @@ cat > /etc/chef/client.rb <<'EOP'
EOP
cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
+<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb
index 6edb485f44..dfd5df0071 100644
--- a/lib/chef/knife/bootstrap/chef-full.erb
+++ b/lib/chef/knife/bootstrap/chef-full.erb
@@ -61,7 +61,7 @@ mkdir -p /etc/chef/ohai/hints
<% @chef_config[:knife][:hints].each do |name, hash| -%>
cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= hash.to_json %>
+<%= Chef::JSONCompat.to_json(hash) %>
EOP
<% end -%>
<% end -%>
@@ -71,7 +71,7 @@ cat > /etc/chef/client.rb <<'EOP'
EOP
cat > /etc/chef/first-boot.json <<'EOP'
-<%= first_boot.to_json %>
+<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
echo "Starting first Chef Client run..."
diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb
index f4183a7245..e17a54079f 100644
--- a/lib/chef/knife/cookbook_create.rb
+++ b/lib/chef/knife/cookbook_create.rb
@@ -80,7 +80,7 @@ class Chef
end
def create_cookbook(dir, cookbook_name, copyright, license)
- msg("** Creating cookbook #{cookbook_name}")
+ msg("** Creating cookbook #{cookbook_name} in #{dir}")
FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "attributes")}"
FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "recipes")}"
FileUtils.mkdir_p "#{File.join(dir, cookbook_name, "definitions")}"
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
index 7204ccdb1c..b4a7873c71 100644
--- a/lib/chef/knife/cookbook_site_share.rb
+++ b/lib/chef/knife/cookbook_site_share.rb
@@ -31,7 +31,7 @@ class Chef
require 'chef/cookbook_site_streaming_uploader'
end
- banner "knife cookbook site share COOKBOOK CATEGORY (options)"
+ banner "knife cookbook site share COOKBOOK [CATEGORY] (options)"
category "cookbook site"
option :cookbook_path,
@@ -40,17 +40,28 @@ class Chef
:description => "A colon-separated path to look for cookbooks in",
:proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") }
+ option :dry_run,
+ :long => '--dry-run',
+ :short => '-n',
+ :boolean => true,
+ :default => false,
+ :description => "Don't take action, only print what files will be upload to SuperMarket."
+
def run
- if @name_args.length < 2
+ config[:cookbook_path] ||= Chef::Config[:cookbook_path]
+
+ if @name_args.length < 1
show_usage
- ui.fatal("You must specify the cookbook name and the category you want to share this cookbook to.")
- exit 1
+ ui.fatal("You must specify the cookbook name.")
+ exit(1)
+ elsif @name_args.length < 2
+ cookbook_name = @name_args[0]
+ category = get_category(cookbook_name)
+ else
+ cookbook_name = @name_args[0]
+ category = @name_args[1]
end
- config[:cookbook_path] ||= Chef::Config[:cookbook_path]
-
- cookbook_name = @name_args[0]
- category = @name_args[1]
cl = Chef::CookbookLoader.new(config[:cookbook_path])
if cl.cookbook_exists?(cookbook_name)
cookbook = cl[cookbook_name]
@@ -66,6 +77,14 @@ class Chef
exit(1)
end
+ if config[:dry_run]
+ ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.")
+ result = shell_out!("tar -tzf #{cookbook_name}.tgz", :cwd => tmp_cookbook_dir)
+ ui.info(result.stdout)
+ FileUtils.rm_rf tmp_cookbook_dir
+ return
+ end
+
begin
do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key])
ui.info("Upload complete!")
@@ -84,6 +103,22 @@ class Chef
end
+ def get_category(cookbook_name)
+ begin
+ data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
+ if !data["category"] && data["error_code"]
+ ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.")
+ exit(1)
+ else
+ data['category']
+ end
+ rescue => e
+ ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.")
+ Chef::Log.debug("\n#{e.backtrace.join("\n")}")
+ exit(1)
+ end
+ end
+
def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
uri = "https://supermarket.getchef.com/api/v1/cookbooks"
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 03e3a42e4a..e681d7a49b 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -30,10 +30,11 @@ class Chef
#
class BootstrapContext
- def initialize(config, run_list, chef_config)
+ def initialize(config, run_list, chef_config, secret)
@config = config
@run_list = run_list
@chef_config = chef_config
+ @secret = secret
end
def bootstrap_environment
@@ -45,15 +46,7 @@ class Chef
end
def encrypted_data_bag_secret
- knife_config[:secret] || begin
- secret_file_path = knife_config[:secret_file]
- expanded_secret_file_path = File.expand_path(secret_file_path.to_s)
- if secret_file_path && File.exist?(expanded_secret_file_path)
- IO.read(expanded_secret_file_path)
- else
- nil
- end
- end
+ @secret
end
def trusted_certs
diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb
new file mode 100644
index 0000000000..3298d5e4ac
--- /dev/null
+++ b/lib/chef/knife/core/status_presenter.rb
@@ -0,0 +1,156 @@
+#
+# Author:: Nicolas DUPEUX (<nicolas.dupeux@arkea.com>)
+# Copyright:: Copyright (c) 2011 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/core/text_formatter'
+require 'chef/knife/core/generic_presenter'
+
+class Chef
+ class Knife
+ module Core
+
+ # This module may be included into a knife subcommand class to automatically
+ # add configuration options used by the StatusPresenter
+ module StatusFormattingOptions
+ # :nodoc:
+ # Would prefer to do this in a rational way, but can't be done b/c of
+ # Mixlib::CLI's design :(
+ def self.included(includer)
+ includer.class_eval do
+ option :medium_output,
+ :short => '-m',
+ :long => '--medium',
+ :boolean => true,
+ :default => false,
+ :description => 'Include normal attributes in the output'
+
+ option :long_output,
+ :short => '-l',
+ :long => '--long',
+ :boolean => true,
+ :default => false,
+ :description => 'Include all attributes in the output'
+ end
+ end
+ end
+
+ #==Chef::Knife::Core::StatusPresenter
+ # A customized presenter for Chef::Node objects. Supports variable-length
+ # output formats for displaying node data
+ class StatusPresenter < GenericPresenter
+
+ def format(data)
+ if parse_format_option == :json
+ summarize_json(data)
+ else
+ super
+ end
+ end
+
+ def summarize_json(list)
+ result_list = []
+ list.each do |node|
+ result = {}
+
+ result["name"] = node.name
+ result["chef_environment"] = node.chef_environment
+ ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress]
+ fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn]
+ result["ip"] = ip if ip
+ result["fqdn"] = fqdn if fqdn
+ result["run_list"] = node.run_list if config[:run_list]
+ result["ohai_time"] = node[:ohai_time]
+ result["platform"] = node[:platform] if node[:platform]
+ result["platform_version"] = node[:platform_version] if node[:platform_version]
+
+ if config[:long_output]
+ result["default"] = node.default_attrs
+ result["override"] = node.override_attrs
+ result["automatic"] = node.automatic_attrs
+ end
+ result_list << result
+ end
+
+ Chef::JSONCompat.to_json_pretty(result_list)
+ end
+
+ # Converts a Chef::Node object to a string suitable for output to a
+ # terminal. If config[:medium_output] or config[:long_output] are set
+ # the volume of output is adjusted accordingly. Uses colors if enabled
+ # in the ui object.
+ def summarize(list)
+ summarized=''
+ list.each do |data|
+ node = data
+ # special case ec2 with their split horizon whatsis.
+ ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress]
+ fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn]
+
+ hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
+ hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}"
+ minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}"
+ run_list = "#{node.run_list}" if config[:run_list]
+ if hours > 24
+ color = :red
+ text = hours_text
+ elsif hours >= 1
+ color = :yellow
+ text = hours_text
+ else
+ color = :green
+ text = minutes_text
+ end
+
+ line_parts = Array.new
+ line_parts << @ui.color(text, color) + ' ago' << node.name
+ line_parts << fqdn if fqdn
+ line_parts << ip if ip
+ line_parts << run_list if run_list
+
+ if node['platform']
+ platform = node['platform']
+ if node['platform_version']
+ platform << " #{node['platform_version']}"
+ end
+ line_parts << platform
+ end
+
+ summarized=summarized + line_parts.join(', ') + ".\n"
+ end
+ summarized
+ end
+
+ def key(key_text)
+ ui.color(key_text, :cyan)
+ end
+
+ # :nodoc:
+ # TODO: this is duplicated from StatusHelper in the Webui. dedup.
+ def time_difference_in_hms(unix_time)
+ now = Time.now.to_i
+ difference = now - unix_time.to_i
+ hours = (difference / 3600).to_i
+ difference = difference % 3600
+ minutes = (difference / 60).to_i
+ seconds = (difference % 60)
+ return [hours, minutes, seconds]
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index 0007480ea2..f3ecfbcae8 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -113,7 +113,7 @@ class Chef
# determined by the value of `config[:color]`. When output is not to a
# terminal, colored output is never used
def color?
- Chef::Config[:color] && stdout.tty? && !Chef::Platform.windows?
+ Chef::Config[:color] && stdout.tty?
end
def ask(*args, &block)
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
index bc49c68448..f8a7619a8a 100644
--- a/lib/chef/knife/data_bag_create.rb
+++ b/lib/chef/knife/data_bag_create.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagCreate < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -31,33 +33,6 @@ class Chef
banner "knife data bag create BAG [ITEM] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- ui.fatal("please specify only one of --secret, --secret-file")
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def run
@data_bag_name, @data_bag_item_name = @name_args
@@ -87,11 +62,11 @@ class Chef
if @data_bag_item_name
create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output|
item = Chef::DataBagItem.from_hash(
- if use_encryption
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
- else
- output
- end)
+ if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
+ else
+ output
+ end)
item.data_bag(@data_bag_name)
rest.post_rest("data/#{@data_bag_name}", item)
end
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
index b3f53af919..6ef4b33f59 100644
--- a/lib/chef/knife/data_bag_edit.rb
+++ b/lib/chef/knife/data_bag_edit.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagEdit < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag_item'
@@ -31,48 +33,17 @@ class Chef
banner "knife data bag edit BAG ITEM (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- stdout.puts "please specify only one of --secret, --secret-file"
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def load_item(bag, item_name)
item = Chef::DataBagItem.load(bag, item_name)
- if use_encryption
- Chef::EncryptedDataBagItem.new(item, read_secret).to_hash
+ if encrypted?(item.raw_data)
+ if encryption_secret_provided_ignore_encrypt_flag?
+ return Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true
+ else
+ ui.fatal("You cannot edit an encrypted data bag without providing the secret.")
+ exit(1)
+ end
else
- item
- end
- end
-
- def edit_item(item)
- output = edit_data(item)
- if use_encryption
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
- else
- output
+ return item, false
end
end
@@ -82,11 +53,21 @@ class Chef
stdout.puts opt_parser
exit 1
end
- item = load_item(@name_args[0], @name_args[1])
- output = edit_item(item)
- rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", output)
+
+ item, was_encrypted = load_item(@name_args[0], @name_args[1])
+ edited_item = edit_data(item)
+
+ if was_encrypted || encryption_secret_provided?
+ ui.info("Encrypting data bag using provided secret.")
+ item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret)
+ else
+ ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.")
+ item_to_save = edited_item
+ end
+
+ rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
- ui.output(output) if config[:print_after]
+ ui.output(edited_item) if config[:print_after]
end
end
end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
index 2ff79b6256..d1b7daa4a2 100644
--- a/lib/chef/knife/data_bag_from_file.rb
+++ b/lib/chef/knife/data_bag_from_file.rb
@@ -19,10 +19,12 @@
require 'chef/knife'
require 'chef/util/path_helper'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagFromFile < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -35,38 +37,11 @@ class Chef
banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
option :all,
:short => "-a",
:long => "--all",
:description => "Upload all data bags or all items for specified data bags"
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- ui.fatal("please specify only one of --secret, --secret-file")
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def loader
@loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui)
end
@@ -109,9 +84,8 @@ class Chef
item_paths = normalize_item_paths(items)
item_paths.each do |item_path|
item = loader.load_from("#{data_bags_path}", data_bag, item_path)
- item = if use_encryption
- secret = read_secret
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, secret)
+ item = if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret)
else
item
end
diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb
new file mode 100644
index 0000000000..766006089e
--- /dev/null
+++ b/lib/chef/knife/data_bag_secret_options.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Tyler Ball (<tball@opscode.com>)
+# Copyright:: Copyright (c) 2014 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 'mixlib/cli'
+require 'chef/config'
+require 'chef/encrypted_data_bag_item/check_encrypted'
+
+class Chef
+ class Knife
+ module DataBagSecretOptions
+ include Mixlib::CLI
+ include Chef::EncryptedDataBagItem::CheckEncrypted
+
+ # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do
+ # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file`
+ # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate
+ # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret`
+ # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they
+ # are provided.
+
+ def self.included(base)
+ base.option :secret,
+ :short => "-s SECRET",
+ :long => "--secret ",
+ :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'",
+ # Need to store value from command line in separate variable - knife#merge_configs populates same keys
+ # on config object from
+ :proc => Proc.new { |s| set_cl_secret(s) }
+
+ base.option :secret_file,
+ :long => "--secret-file SECRET_FILE",
+ :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'",
+ :proc => Proc.new { |sf| set_cl_secret_file(sf) }
+
+ base.option :encrypt,
+ :long => "--encrypt",
+ :description => "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it",
+ :boolean => true,
+ :default => false
+ end
+
+ def encryption_secret_provided?
+ base_encryption_secret_provided?
+ end
+
+ def encryption_secret_provided_ignore_encrypt_flag?
+ base_encryption_secret_provided?(false)
+ end
+
+ def read_secret
+ # Moving the non 'compile-time' requires into here to speed up knife command loading
+ # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
+ require 'chef/encrypted_data_bag_item'
+
+ if has_cl_secret?
+ config[:secret]
+ elsif has_cl_secret_file?
+ Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
+ elsif secret = knife_config[:secret]
+ secret
+ else
+ secret_file = knife_config[:secret_file]
+ Chef::EncryptedDataBagItem.load_secret(secret_file)
+ end
+ end
+
+ def validate_secrets
+ if has_cl_secret? && has_cl_secret_file?
+ ui.fatal("Please specify only one of --secret, --secret-file")
+ exit(1)
+ end
+
+ if knife_config[:secret] && knife_config[:secret_file]
+ ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
+ exit(1)
+ end
+ end
+
+ private
+
+ ##
+ # Determine if the user has specified an appropriate secret for encrypting data bag items.
+ # @returns boolean
+ def base_encryption_secret_provided?(need_encrypt_flag = true)
+ validate_secrets
+
+ return true if has_cl_secret? || has_cl_secret_file?
+
+ if need_encrypt_flag
+ if config[:encrypt]
+ unless knife_config[:secret] || knife_config[:secret_file]
+ ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
+ exit(1)
+ end
+ return true
+ end
+ return false
+ elsif knife_config[:secret] || knife_config[:secret_file]
+ # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
+ return true
+ end
+ return false
+ end
+
+ def has_cl_secret?
+ Chef::Config[:knife].has_key?(:cl_secret)
+ end
+
+ def self.set_cl_secret(s)
+ Chef::Config[:knife][:cl_secret] = s
+ end
+
+ def has_cl_secret_file?
+ Chef::Config[:knife].has_key?(:cl_secret_file)
+ end
+
+ def self.set_cl_secret_file(sf)
+ Chef::Config[:knife][:cl_secret_file] = sf
+ end
+
+ def knife_config
+ Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
index 519859ca2d..36715286e8 100644
--- a/lib/chef/knife/data_bag_show.rb
+++ b/lib/chef/knife/data_bag_show.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagShow < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -31,45 +33,29 @@ class Chef
banner "knife data bag show BAG [ITEM] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to decrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to decrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- stdout.puts "please specify only one of --secret, --secret-file"
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def run
display = case @name_args.length
- when 2
- if use_encryption
+ when 2 # Bag and Item names provided
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
+ encrypted = encrypted?(raw_data)
+
+ if encrypted && secret
+ # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
+ ui.info("Encrypted data bag detected, decrypting with provided secret.")
raw = Chef::EncryptedDataBagItem.load(@name_args[0],
@name_args[1],
- read_secret)
+ secret)
format_for_display(raw.to_hash)
+ elsif encrypted && !secret
+ ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+ format_for_display(raw_data)
else
- format_for_display(Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data)
+ ui.info("Unencrypted data bag detected, ignoring any provided secret options.")
+ format_for_display(raw_data)
end
- when 1
+
+ when 1 # Only Bag name provided
format_list_for_display(Chef::DataBag.load(@name_args[0]))
else
stdout.puts opt_parser
@@ -77,7 +63,7 @@ class Chef
end
output(display)
end
+
end
end
end
-
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
index 5906a4a624..93e81f8f03 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -17,13 +17,13 @@
#
require 'chef/knife'
+require 'chef/knife/core/status_presenter'
class Chef
class Knife
class Status < Knife
deps do
- require 'highline'
require 'chef/search/query'
end
@@ -44,74 +44,27 @@ class Chef
:long => "--hide-healthy",
:description => "Hide nodes that have run chef in the last hour"
- def highline
- @h ||= HighLine.new
- end
-
def run
+ ui.use_presenter Knife::Core::StatusPresenter
all_nodes = []
q = Chef::Search::Query.new
- query = @name_args[0] || "*:*"
+ query = @name_args[0] ? @name_args[0].dup : '*:*'
+ if config[:hide_healthy]
+ time = Time.now.to_i
+ query_unhealthy = "NOT ohai_time:[" << (time - 60*60).to_s << " TO " << time.to_s << "]"
+ query << ' AND ' << query_unhealthy << @name_args[0] if @name_args[0]
+ query = query_unhealthy unless @name_args[0]
+ end
q.search(:node, query) do |node|
all_nodes << node
end
- all_nodes.sort { |n1, n2|
+ output(all_nodes.sort { |n1, n2|
if (config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse])
(n2["ohai_time"] or 0) <=> (n1["ohai_time"] or 0)
else
(n1["ohai_time"] or 0) <=> (n2["ohai_time"] or 0)
end
- }.each do |node|
- if node.has_key?("ec2")
- fqdn = node['ec2']['public_hostname']
- ipaddress = node['ec2']['public_ipv4']
- else
- fqdn = node['fqdn']
- ipaddress = node['ipaddress']
- end
- hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
- hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}"
- minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}"
- run_list = "#{node.run_list}" if config[:run_list]
- if hours > 24
- color = :red
- text = hours_text
- elsif hours >= 1
- color = :yellow
- text = hours_text
- else
- color = :green
- text = minutes_text
- end
-
- line_parts = Array.new
- line_parts << @ui.color(text, color) + " ago" << node.name
- line_parts << fqdn if fqdn
- line_parts << ipaddress if ipaddress
- line_parts << run_list if run_list
-
- if node['platform']
- platform = node['platform']
- if node['platform_version']
- platform << " #{node['platform_version']}"
- end
- line_parts << platform
- end
- highline.say(line_parts.join(', ') + '.') unless (config[:hide_healthy] && hours < 1)
- end
-
- end
-
- # :nodoc:
- # TODO: this is duplicated from StatusHelper in the Webui. dedup.
- def time_difference_in_hms(unix_time)
- now = Time.now.to_i
- difference = now - unix_time.to_i
- hours = (difference / 3600).to_i
- difference = difference % 3600
- minutes = (difference / 60).to_i
- seconds = (difference % 60)
- return [hours, minutes, seconds]
+ })
end
end
diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb
index b63a02663b..2bad4e6bcf 100644
--- a/lib/chef/mixin/command/unix.rb
+++ b/lib/chef/mixin/command/unix.rb
@@ -100,9 +100,9 @@ class Chef
begin
if cmd.kind_of?(Array)
- exec(*cmd)
+ Kernel.exec(*cmd)
else
- exec(cmd)
+ Kernel.exec(cmd)
end
raise 'forty-two'
rescue Exception => e
diff --git a/lib/chef/mixin/homebrew_owner.rb b/lib/chef/mixin/homebrew_owner.rb
deleted file mode 100644
index 73bb22ddf5..0000000000
--- a/lib/chef/mixin/homebrew_owner.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Author:: Joshua Timberman (<joshua@getchef.com>)
-# Author:: Graeme Mathieson (<mathie@woss.name>)
-#
-# Copyright 2011-2013, Opscode, Inc.
-# Copyright 2014, Chef Software, Inc <legal@getchef.com>
-#
-# 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.
-#
-# Ported from the homebrew cookbook's Homebrew::Mixin owner helpers
-#
-# This lives here in Chef::Mixin because Chef's namespacing makes it
-# awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner)
-
-class Chef
- module Mixin
- module HomebrewOwner
- def homebrew_owner(node)
- @homebrew_owner ||= calculate_owner(node)
- end
-
- private
-
- def calculate_owner(node)
- owner = homebrew_owner_attr(node) || sudo_user || current_user
- if owner == 'root'
- raise Chef::Exceptions::CannotDetermineHomebrewOwner,
- 'The homebrew owner is not specified and the current user is \"root\"' +
- 'Homebrew does not support root installs, please specify the homebrew' +
- 'owner by setting the attribute `node[\'homebrew\'][\'owner\']`.'
- end
- owner
- end
-
- def homebrew_owner_attr(node)
- node['homebrew']['owner'] if node.attribute?('homebrew') && node['homebrew'].attribute?('owner')
- end
-
- def sudo_user
- ENV['SUDO_USER']
- end
-
- def current_user
- ENV['USER']
- end
- end
- end
-end
diff --git a/lib/chef/mixin/homebrew_user.rb b/lib/chef/mixin/homebrew_user.rb
new file mode 100644
index 0000000000..ab6fb19563
--- /dev/null
+++ b/lib/chef/mixin/homebrew_user.rb
@@ -0,0 +1,68 @@
+#
+# Author:: Joshua Timberman (<joshua@getchef.com>)
+# Author:: Graeme Mathieson (<mathie@woss.name>)
+#
+# Copyright 2011-2013, Opscode, Inc.
+# Copyright 2014, Chef Software, Inc <legal@getchef.com>
+#
+# 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.
+#
+# Ported from the homebrew cookbook's Homebrew::Mixin owner helpers
+#
+# This lives here in Chef::Mixin because Chef's namespacing makes it
+# awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner)
+
+require 'chef/mixin/shell_out'
+require 'etc'
+
+class Chef
+ module Mixin
+ module HomebrewUser
+ include Chef::Mixin::ShellOut
+
+ ##
+ # This tries to find the user to execute brew as. If a user is provided, that overrides the brew
+ # executable user. It is an error condition if the brew executable owner is root or we cannot find
+ # the brew executable.
+ def find_homebrew_uid(provided_user = nil)
+ # They could provide us a user name or a UID
+ if provided_user
+ return provided_user if provided_user.is_a? Integer
+ return Etc.getpwnam(provided_user).uid
+ end
+
+ @homebrew_owner ||= calculate_owner
+ @homebrew_owner
+ end
+
+ private
+
+ def calculate_owner
+ default_brew_path = '/usr/local/bin/brew'
+ if ::File.exist?(default_brew_path)
+ # By default, this follows symlinks which is what we want
+ owner = ::File.stat(default_brew_path).uid
+ elsif (brew_path = shell_out("which brew").stdout.strip) && !brew_path.empty?
+ owner = ::File.stat(brew_path).uid
+ else
+ raise Chef::Exceptions::CannotDetermineHomebrewOwner,
+ 'Could not find the "brew" executable in /usr/local/bin or anywhere on the path.'
+ end
+
+ Chef::Log.debug "Found Homebrew owner #{Etc.getpwuid(owner).name}; executing `brew` commands as them"
+ owner
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index 82772b584a..5b05e788db 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -15,10 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# chef/shell_out has been deprecated in favor of mixlib/shellout
-# chef/shell_out is still required here to ensure backward compatibility
-require 'chef/shell_out'
-
require 'mixlib/shellout'
class Chef
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
new file mode 100644
index 0000000000..490b235065
--- /dev/null
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Edwards (<adamed@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'chef/exceptions'
+require 'chef/platform/query_helpers'
+require 'chef/win32/error' if Chef::Platform.windows?
+require 'chef/win32/api/system' if Chef::Platform.windows?
+
+class Chef
+ module Mixin
+ module WindowsEnvHelper
+
+ if Chef::Platform.windows?
+ include Chef::ReservedNames::Win32::API::System
+ end
+
+ #see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx
+ HWND_BROADCAST = 0xffff
+ WM_SETTINGCHANGE = 0x001A
+ SMTO_BLOCK = 0x0001
+ SMTO_ABORTIFHUNG = 0x0002
+ SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
+
+ 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)
+ end
+
+ def expand_path(path)
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724265%28v=vs.85%29.aspx
+ # Max size of env block on windows is 32k
+ buf = 0.chr * 32 * 1024
+ if ExpandEnvironmentStringsA(path, buf, buf.length) == 0
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ buf.strip
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index 0766ccffa7..9e15ea5658 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -197,10 +197,14 @@ class Chef
},
:suse => {
:default => {
- :service => Chef::Provider::Service::Redhat,
+ :service => Chef::Provider::Service::Systemd,
:cron => Chef::Provider::Cron,
:package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Suse
+ :group => Chef::Provider::Group::Gpasswd
+ },
+ "< 12.0" => {
+ :group => Chef::Provider::Group::Suse,
+ :service => Chef::Provider::Service::Redhat
}
},
:oracle => {
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 5cb1bcda41..350f8bda18 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -25,18 +25,21 @@ class Chef
class Provider
class Cron
class Unix < Chef::Provider::Cron
+ include Chef::Mixin::ShellOut
private
def read_crontab
- crontab = nil
- status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr|
- crontab = stdout.read
- end
- if status.exitstatus > 1
- raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}"
+ crontab = shell_out('/usr/bin/crontab -l', :user => @new_resource.user)
+ status = crontab.status.exitstatus
+
+ Chef::Log.debug crontab.format_for_exception if status > 0
+
+ if status > 1
+ raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}"
end
- crontab
+ return nil if status > 0
+ crontab.stdout.chomp << "\n"
end
def write_crontab(crontab)
@@ -47,8 +50,9 @@ class Chef
exit_status = 0
error_message = ""
begin
- status, stdout, stderr = run_command_and_return_stdout_stderr(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user)
- exit_status = status.exitstatus
+ crontab_write = shell_out("/usr/bin/crontab #{tempcron.path}", :user => @new_resource.user)
+ stderr = crontab_write.stderr
+ exit_status = crontab_write.status.exitstatus
# solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :(
if stderr && stderr.include?("errors detected in input, no crontab file generated")
error_message = stderr
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index db147278c2..b30f7ed17e 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -375,7 +375,7 @@ class Chef
def gem_resource_collection_runner
gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection << rbgem }
+ gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
gems_run_context = run_context.dup
gems_run_context.resource_collection = gems_collection
Chef::Runner.new(gems_run_context)
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index c979800cba..a70f4b5048 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -47,9 +47,11 @@ class Chef
end
def load_current_resource
- @dsc_resources_info = run_configuration(:test)
- @resource_converged = @dsc_resources_info.all? do |resource|
- !resource.changes_state?
+ if supports_dsc?
+ @dsc_resources_info = run_configuration(:test)
+ @resource_converged = @dsc_resources_info.all? do |resource|
+ !resource.changes_state?
+ end
end
end
@@ -57,8 +59,26 @@ class Chef
true
end
+ def define_resource_requirements
+ requirements.assert(:run) do |a|
+ err = [
+ 'Could not find PowerShell DSC support on the system',
+ powershell_info_str,
+ "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.",
+ ]
+ a.assertion { supports_dsc? }
+ a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
+ a.block_action!
+ end
+ end
+
protected
+ def supports_dsc?
+ run_context && Chef::Platform.supports_dsc?(node)
+ end
+
def run_configuration(operation)
config_directory = ::Dir.mktmpdir("chef-dsc-script")
configuration_data_path = get_configuration_data_path(config_directory)
@@ -78,9 +98,8 @@ class Chef
end
def get_augmented_configuration_flags(configuration_data_path)
- updated_flags = nil
+ updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup
if configuration_data_path
- updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup
Chef::Util::PathHelper.validate_path(configuration_data_path)
updated_flags[:configurationdata] = configuration_data_path
end
@@ -144,6 +163,14 @@ class Chef
end
end
end
+
+ def powershell_info_str
+ if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell]
+ install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system."
+ else
+ install_info = 'Powershell was not found.'
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
index f73cb42f7e..572ec5c633 100644
--- a/lib/chef/provider/env/windows.rb
+++ b/lib/chef/provider/env/windows.rb
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require 'chef/win32/api/system' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+require 'chef/mixin/windows_env_helper'
class Chef
class Provider
class Env
class Windows < Chef::Provider::Env
- include Chef::ReservedNames::Win32::API::System if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::Mixin::WindowsEnvHelper
def create_env
obj = env_obj(@new_resource.key_name)
@@ -33,7 +33,9 @@ class Chef
end
obj.variablevalue = @new_resource.value
obj.put_
- ENV[@new_resource.key_name] = @new_resource.value
+ value = @new_resource.value
+ value = expand_path(value) if @new_resource.key_name.upcase == 'PATH'
+ ENV[@new_resource.key_name] = value
broadcast_env_change
end
@@ -60,17 +62,6 @@ class Chef
end
end
- #see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx
- HWND_BROADCAST = 0xffff
- WM_SETTINGCHANGE = 0x001A
- SMTO_BLOCK = 0x0001
- SMTO_ABORTIFHUNG = 0x0002
- SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
-
- 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)
- end
end
end
end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index d469bea769..54632c0684 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -50,10 +50,11 @@ class Chef
opts[:umask] = @new_resource.umask if @new_resource.umask
opts[:log_level] = :info
opts[:log_tag] = @new_resource.to_s
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
+ if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? && !@new_resource.sensitive
opts[:live_stream] = STDOUT
end
- converge_by("execute #{@new_resource.command}") do
+ description = @new_resource.sensitive ? "sensitive resource" : @new_resource.command
+ converge_by("execute #{description}") do
result = shell_out!(@new_resource.command, opts)
Chef::Log.info("#{@new_resource} ran successfully")
end
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index aa58fdc4a1..2ef119e839 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -64,7 +64,7 @@ class Chef
a.failure_message Chef::Exceptions::UnresolvableGitReference,
"Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " +
"Verify your (case-sensitive) repository URL and revision.\n" +
- "`git ls-remote` output: #{@resolved_reference}"
+ "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
end
end
@@ -102,6 +102,10 @@ class Chef
end
end
+ def git_minor_version
+ @git_minor_version ||= Gem::Version.new(shell_out!('git --version', run_options).stdout.split.last)
+ end
+
def existing_git_clone?
::File.exist?(::File.join(@new_resource.destination, ".git"))
end
@@ -137,6 +141,7 @@ class Chef
args = []
args << "-o #{remote}" unless remote == 'origin'
args << "--depth #{@new_resource.depth}" if @new_resource.depth
+ args << "--no-single-branch" if @new_resource.depth and git_minor_version >= Gem::Version.new('1.7.10')
Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
@@ -150,7 +155,8 @@ class Chef
converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do
# checkout into a local branch rather than a detached HEAD
- shell_out!("git checkout -B #{@new_resource.checkout_branch} #{sha_ref}", run_options(:cwd => @new_resource.destination))
+ shell_out!("git branch -f #{@new_resource.checkout_branch} #{sha_ref}", run_options(:cwd => @new_resource.destination))
+ shell_out!("git checkout #{@new_resource.checkout_branch}", run_options(:cwd => @new_resource.destination))
Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} onto: #{@new_resource.checkout_branch} reference: #{sha_ref}"
end
end
@@ -234,35 +240,55 @@ class Chef
# annotated tags, we have to search for "revision*" and
# post-process. Special handling for 'HEAD' to ignore a tag
# named 'HEAD'.
- rev_pattern = case @new_resource.revision
- when '', 'HEAD'
- 'HEAD'
- else
- @new_resource.revision + '*'
- end
- command = git("ls-remote \"#{@new_resource.repository}\" \"#{rev_pattern}\"")
- @resolved_reference = shell_out!(command, run_options).stdout
- ref_lines = @resolved_reference.split("\n")
- refs = ref_lines.map { |line| line.split("\t") }
- # first try for ^{} indicating the commit pointed to by an
- # annotated tag
- tagged_commit = refs.find { |m| m[1].end_with?("#{@new_resource.revision}^{}") }
+ @resolved_reference = git_ls_remote(rev_search_pattern)
+ refs = @resolved_reference.split("\n").map { |line| line.split("\t") }
+ # First try for ^{} indicating the commit pointed to by an
+ # annotated tag.
# It is possible for a user to create a tag named 'HEAD'.
# Using such a degenerate annotated tag would be very
# confusing. We avoid the issue by disallowing the use of
# annotated tags named 'HEAD'.
- if tagged_commit && rev_pattern != 'HEAD'
- tagged_commit[0]
+ if rev_search_pattern != 'HEAD'
+ found = find_revision(refs, @new_resource.revision, '^{}')
else
- found = refs.find { |m| m[1].end_with?(@new_resource.revision) }
- if found
- found[0]
- else
- nil
- end
+ found = refs_search(refs, 'HEAD')
+ end
+ found = find_revision(refs, @new_resource.revision) if found.empty?
+ found.size == 1 ? found.first[0] : nil
+ end
+
+ def find_revision(refs, revision, suffix="")
+ found = refs_search(refs, rev_match_pattern('refs/tags/', revision) + suffix)
+ found = refs_search(refs, rev_match_pattern('refs/heads/', revision) + suffix) if found.empty?
+ found = refs_search(refs, revision + suffix) if found.empty?
+ found
+ end
+
+ def rev_match_pattern(prefix, revision)
+ if revision.start_with?(prefix)
+ revision
+ else
+ prefix + revision
+ end
+ end
+
+ def rev_search_pattern
+ if ['', 'HEAD'].include? @new_resource.revision
+ 'HEAD'
+ else
+ @new_resource.revision + '*'
end
end
+ def git_ls_remote(rev_pattern)
+ command = git(%Q(ls-remote "#{@new_resource.repository}" "#{rev_pattern}"))
+ shell_out!(command, run_options).stdout
+ end
+
+ def refs_search(refs, pattern)
+ refs.find_all { |m| m[1] == pattern }
+ end
+
private
def run_options(run_opts={})
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 90ce70ae61..135a3f6b7c 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -81,22 +81,24 @@ class Chef
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)
- # Add log entry if we override an existing light-weight provider.
class_name = convert_to_class_name(provider_name)
if Chef::Provider.const_defined?(class_name)
- Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!")
+ 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
+ provider_class = Class.new(self)
+ provider_class.class_from_file(filename)
+
+ class_name = convert_to_class_name(provider_name)
+ Chef::Provider.const_set(class_name, provider_class)
+ Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
end
- provider_class = Class.new(self)
- provider_class.class_from_file(filename)
-
- class_name = convert_to_class_name(provider_name)
- Chef::Provider.const_set(class_name, provider_class)
- Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
-
provider_class
end
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index 0741a4d95f..bfe6dca617 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -45,7 +45,7 @@ class Chef
def current_installed_version
pkg_info = shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
- pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1]
+ pkg_info.stdout[/^Version +: (.+)$/, 1]
end
def candidate_version
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 4b3510f0e9..8b191179f0 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -34,7 +34,7 @@ class Chef
end
def current_installed_version
- pkg_info = if supports_pkgng?
+ pkg_info = if @new_resource.supports_pkgng?
shell_out!("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])
@@ -53,14 +53,6 @@ class Chef
def port_dir
super(@new_resource.package_name)
end
-
- private
-
- def supports_pkgng?
- with_pkgng = makefile_variable_value('WITH_PKGNG')
- with_pkgng && with_pkgng =~ /yes/i
- end
-
end
end
end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index d964703c87..a9aeea1415 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -19,13 +19,13 @@
#
require 'etc'
-require 'chef/mixin/homebrew_owner'
+require 'chef/mixin/homebrew_user'
class Chef
class Provider
class Package
class Homebrew < Chef::Provider::Package
- include Chef::Mixin::HomebrewOwner
+ include Chef::Mixin::HomebrewUser
def load_current_resource
self.current_resource = Chef::Resource::Package.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
@@ -109,12 +109,14 @@ class Chef
private
def get_response_from_command(command)
- home_dir = Etc.getpwnam(homebrew_owner(node)).dir
+ homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user)
+ homebrew_user = Etc.getpwuid(homebrew_uid)
- Chef::Log.debug "Executing '#{command}' as user '#{homebrew_owner(node)}'"
- output = shell_out!(command, :timeout => 1800, :user => homebrew_owner(node), :environment => { 'HOME' => home_dir, 'RUBYOPT' => nil })
+ 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 })
output.stdout.chomp
end
+
end
end
end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index 1014ebcaa5..a9ff0edf7f 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -62,11 +62,11 @@ class Chef
package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
- status = popen4("pacman -Ss #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ status = popen4("pacman -Sl") do |pid, stdin, stdout, stderr|
stdout.each do |line|
case line
- when /^(#{package_repos})\/#{Regexp.escape(@new_resource.package_name)} (.+)$/
- # $2 contains a string like "4.4.0-1 (kde kdenetwork)" or "3.10-4 (base)"
+ when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
+ # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]"
# simply split by space and use first token
@candidate_version = $2.split(" ").first
end
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 7c5245fc97..f363b38e50 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -34,7 +34,7 @@ class Chef
installed = false
re = Regexp.new('(.*)[[:blank:]](.*)[[:blank:]](.*)$')
- shell_out!("cave -L warning print-ids -M none -m \"*/#{@new_resource.package_name.split('/').last}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line|
+ shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line|
res = re.match(line)
unless res.nil?
case res[3]
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index be0022f4aa..6c7e1c066e 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -493,6 +493,7 @@ class Chef
def target_version_already_installed?
return false unless @current_resource && @current_resource.version
return false if @current_resource.version.nil?
+ return false if @new_resource.version.nil?
Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version))
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index 553eb14ae5..5bd1cb5493 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -36,21 +36,18 @@ class Chef
def action_create
super
- files_to_purge = Set.new(Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(@new_resource.path), '**', '*'),
- ::File::FNM_DOTMATCH).select do |name|
- name !~ /(?:^|#{Regexp.escape(::File::SEPARATOR)})\.\.?$/
- end)
+ # Mark all files as needing to be purged
+ files_to_purge = Set.new(ls(@new_resource.path)) # Make sure each path is clean
+ # Transfer files
files_to_transfer.each do |cookbook_file_relative_path|
create_cookbook_file(cookbook_file_relative_path)
- # the file is removed from the purge list
- files_to_purge.delete(::File.join(@new_resource.path, cookbook_file_relative_path))
- # parent directories are also removed from the purge list
- directories=::File.dirname(::File.join(@new_resource.path, cookbook_file_relative_path)).split(::File::SEPARATOR)
- for i in 0..directories.length-1
- files_to_purge.delete(::File.join(directories[0..i]))
+ # parent directories and file being transfered are removed from the purge list
+ Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(@new_resource.path, cookbook_file_relative_path))).descend do |d|
+ files_to_purge.delete(d.to_s)
end
end
+
purge_unmanaged_files(files_to_purge)
end
@@ -62,6 +59,21 @@ class Chef
protected
+ # List all excluding . and ..
+ def ls(path)
+ files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'),
+ ::File::FNM_DOTMATCH)
+
+ # Remove current directory and previous directory
+ files.reject! do |name|
+ basename = Pathname.new(name).basename().to_s
+ ['.', '..'].include?(basename)
+ end
+
+ # Clean all the paths... this is required because of the join
+ files.map {|f| Chef::Util::PathHelper.cleanpath(f)}
+ end
+
def purge_unmanaged_files(unmanaged_files)
if @new_resource.purge
unmanaged_files.sort.reverse.each do |f|
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 32578da5ab..de72a8d0c4 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -96,6 +96,8 @@ class Chef
# true<TrueClass>:: If all the parameters are present
# false<FalseClass>:: If any of the parameters are missing
def tagged?(*tags)
+ return false if run_context.node[:tags].nil?
+
tags.each do |tag|
return false unless run_context.node[:tags].include?(tag)
end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 70abfbcdb0..e92ea28c69 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -229,6 +229,8 @@ F
attr_reader :elapsed_time
+ attr_reader :default_guard_interpreter
+
# Each notify entry is a resource/action pair, modeled as an
# Struct with a #resource and #action member
@@ -250,7 +252,13 @@ F
@not_if = []
@only_if = []
@source_line = nil
- @guard_interpreter = :default
+ # We would like to raise an error when the user gives us a guard
+ # interpreter and a ruby_block to the guard. In order to achieve this
+ # we need to understand when the user overrides the default guard
+ # interpreter. Therefore we store the default separately in a different
+ # attribute.
+ @guard_interpreter = nil
+ @default_guard_interpreter = :default
@elapsed_time = 0
@sensitive = false
end
@@ -297,12 +305,13 @@ F
end
end
- def load_prior_resource
+ def load_prior_resource(resource_type, instance_name)
begin
- prior_resource = run_context.resource_collection.lookup(self.to_s)
+ key = ::Chef::ResourceCollection::ResourceSet.create_key(resource_type, instance_name)
+ prior_resource = run_context.resource_collection.lookup(key)
# if we get here, there is a prior resource (otherwise we'd have jumped
# to the rescue clause).
- Chef::Log.warn("Cloning resource attributes for #{self.to_s} from prior resource (CHEF-3694)")
+ Chef::Log.warn("Cloning resource attributes for #{key} from prior resource (CHEF-3694)")
Chef::Log.warn("Previous #{prior_resource}: #{prior_resource.source_line}") if prior_resource.source_line
Chef::Log.warn("Current #{self}: #{self.source_line}") if self.source_line
prior_resource.instance_variables.each do |iv|
@@ -410,11 +419,15 @@ F
end
def guard_interpreter(arg=nil)
- set_or_return(
- :guard_interpreter,
- arg,
- :kind_of => Symbol
- )
+ if arg.nil?
+ @guard_interpreter || @default_guard_interpreter
+ else
+ set_or_return(
+ :guard_interpreter,
+ arg,
+ :kind_of => Symbol
+ )
+ end
end
# Sets up a notification from this resource to the resource specified by +resource_spec+.
@@ -639,6 +652,11 @@ F
# on accident
updated_by_last_action(false)
+ # Don't modify @retries directly and keep it intact, so that the
+ # recipe_snippet from ResourceFailureInspector can print the value
+ # that was set in the resource block initially.
+ remaining_retries = retries
+
begin
return if should_skip?(action)
provider_for_action(action).run_action
@@ -646,10 +664,10 @@ F
if ignore_failure
Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing")
events.resource_failed(self, action, e)
- elsif retries > 0
- events.resource_failed_retriable(self, action, retries, e)
- @retries -= 1
- Chef::Log.info("Retrying execution of #{self}, #{retries} attempt(s) left")
+ elsif remaining_retries > 0
+ events.resource_failed_retriable(self, action, remaining_retries, e)
+ remaining_retries -= 1
+ Chef::Log.info("Retrying execution of #{self}, #{remaining_retries} attempt(s) left")
sleep retry_delay
retry
else
diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb
index e6623be5dd..8960a4d57f 100644
--- a/lib/chef/resource/conditional.rb
+++ b/lib/chef/resource/conditional.rb
@@ -45,22 +45,42 @@ class Chef
def initialize(positivity, parent_resource, command=nil, command_opts={}, &block)
@positivity = positivity
- case command
+ @command, @command_opts = command, command_opts
+ @block = block
+ @block_given = block_given?
+ @parent_resource = parent_resource
+
+ raise ArgumentError, "only_if/not_if requires either a command or a block" unless command || block_given?
+ end
+
+ def configure
+ case @command
when String
- @guard_interpreter = new_guard_interpreter(parent_resource, command, command_opts, &block)
- @command, @command_opts = command, command_opts
+ @guard_interpreter = new_guard_interpreter(@parent_resource, @command, @command_opts, &@block)
@block = nil
when nil
- raise ArgumentError, "only_if/not_if requires either a command or a block" unless block_given?
+ # We should have a block if we get here
+ # Check to see if the user set the guard_interpreter on the parent resource. Note that
+ # this error will not be raised when using the default_guard_interpreter
+ if @parent_resource.guard_interpreter != @parent_resource.default_guard_interpreter
+ msg = "#{@parent_resource.name} was given a guard_interpreter of #{@parent_resource.guard_interpreter}, "
+ msg << "but not given a command as a string. guard_interpreter does not support blocks (because they just contain ruby)."
+ raise ArgumentError, msg
+ end
+
@guard_interpreter = nil
@command, @command_opts = nil, nil
- @block = block
else
- raise ArgumentError, "Invalid only_if/not_if command: #{command.inspect} (#{command.class})"
+ # command was passed, but it wasn't a String
+ raise ArgumentError, "Invalid only_if/not_if command, expected a string: #{command.inspect} (#{command.class})"
end
end
+ # this is run during convergence via Chef::Resource#run_action -> Chef::Resource#should_skip?
def continue?
+ # configure late in case guard_interpreter is specified on the resource after the conditional
+ configure
+
case @positivity
when :only_if
evaluate
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index de758aef71..2709cf64f4 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -40,7 +40,7 @@ class Chef
end
def source(source_filename=nil)
- set_or_return(:source, source_filename, :kind_of => String)
+ set_or_return(:source, source_filename, :kind_of => [ String, Array ])
end
def cookbook(cookbook_name=nil)
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index 2972ace1aa..76ac6659d6 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -28,12 +28,8 @@ class Chef
super
@allowed_actions.push(:run)
@action = :run
- if(run_context && Chef::Platform.supports_dsc?(run_context.node))
- @provider = Chef::Provider::DscScript
- else
- raise Chef::Exceptions::NoProviderAvailable,
- "#{powershell_info_str(run_context)}\nPowershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource."
- end
+ @provider = Chef::Provider::DscScript
+ @resource_name = :dsc_script
end
def code(arg=nil)
@@ -125,16 +121,6 @@ class Chef
:kind_of => [ Integer ]
)
end
-
- private
-
- def powershell_info_str(run_context)
- if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell]
- install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system."
- else
- install_info = 'Powershell was not found.'
- end
- end
end
end
end
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 7c4fa48c0a..ae118b1c9e 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -35,12 +35,12 @@ class Chef
@cwd = nil
@environment = nil
@group = nil
- @path = nil
@returns = 0
@timeout = nil
@user = nil
@allowed_actions.push(:run)
@umask = nil
+ @default_guard_interpreter = :execute
end
def umask(arg=nil)
@@ -93,14 +93,6 @@ class Chef
)
end
- def path(arg=nil)
- set_or_return(
- :path,
- arg,
- :kind_of => [ Array ]
- )
- end
-
def returns(arg=nil)
set_or_return(
:returns,
@@ -125,6 +117,30 @@ class Chef
)
end
+ def self.set_guard_inherited_attributes(*inherited_attributes)
+ @class_inherited_attributes = inherited_attributes
+ end
+
+ def self.guard_inherited_attributes(*inherited_attributes)
+ # Similar to patterns elsewhere, return attributes from this
+ # class and superclasses as a form of inheritance
+ ancestor_attributes = []
+
+ if superclass.respond_to?(:guard_inherited_attributes)
+ ancestor_attributes = superclass.guard_inherited_attributes
+ end
+
+ ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq
+ end
+
+ set_guard_inherited_attributes(
+ :cwd,
+ :environment,
+ :group,
+ :user,
+ :umask
+ )
+
end
end
end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 70ef62ae8a..957c25caf1 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -31,49 +31,36 @@ class Chef
provides :package, :on_platforms => ["freebsd"]
- attr_accessor :created_as_type
-
def initialize(name, run_context=nil)
super
@resource_name = :freebsd_package
- @created_as_type = "freebsd_package"
end
def after_created
assign_provider
end
- # This resource can be invoked with multiple names package & freebsd_package.
- # We override the to_s method to ensure the key in resource collection
- # matches the type resource is declared as using created_as_type. This
- # logic can be removed once Chef does this for all resource in Chef 12:
- # https://github.com/opscode/chef/issues/1817
- def to_s
- "#{created_as_type}[#{name}]"
+ def supports_pkgng?
+ ships_with_pkgng? || !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i)
end
private
+ def ships_with_pkgng?
+ # It was not until __FreeBSD_version 1000017 that pkgng became
+ # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'.
+ node.automatic[:os_version].to_i >= 1000017
+ end
+
def assign_provider
@provider = if @source.to_s =~ /^ports$/i
Chef::Provider::Package::Freebsd::Port
- elsif ships_with_pkgng? || supports_pkgng?
+ elsif supports_pkgng?
Chef::Provider::Package::Freebsd::Pkgng
else
Chef::Provider::Package::Freebsd::Pkg
end
end
-
- def ships_with_pkgng?
- # It was not until __FreeBSD_version 1000017 that pkgng became
- # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'.
- node[:os_version].to_i >= 1000017
- end
-
- def supports_pkgng?
- !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i)
- end
-
end
end
end
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index c3fa9ddffc..e1d50c1739 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -24,11 +24,22 @@ require 'chef/resource/package'
class Chef
class Resource
class HomebrewPackage < Chef::Resource::Package
+
def initialize(name, run_context=nil)
super
@resource_name = :homebrew_package
@provider = Chef::Provider::Package::Homebrew
+ @homebrew_user = nil
+ end
+
+ def homebrew_user(arg=nil)
+ set_or_return(
+ :homebrew_user,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
end
+
end
end
end
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index 5b67941a8b..a4606be842 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -35,26 +35,24 @@ class Chef
# 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)
- # Add log entry if we override an existing lightweight resource.
class_name = convert_to_class_name(rname)
if Resource.strict_const_defined?(class_name)
- old_class = Resource.send(:remove_const, class_name)
- # CHEF-3432 -- Chef::Resource keeps a list of subclasses; need to
- # remove old ones from the list when replacing.
- resource_classes.delete(old_class)
- Chef::Log.info("#{class_name} lightweight resource already initialized -- overriding!")
- end
-
- resource_class = Class.new(self)
+ 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)
- resource_class.resource_name = rname
- resource_class.run_context = run_context
- resource_class.class_from_file(filename)
+ resource_class.resource_name = rname
+ resource_class.run_context = run_context
+ resource_class.class_from_file(filename)
- Chef::Resource.const_set(class_name, resource_class)
- Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
+ Chef::Resource.const_set(class_name, resource_class)
+ Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
+ end
resource_class
end
@@ -112,10 +110,21 @@ class Chef
if action_names.empty?
defined?(@actions) ? @actions : from_superclass(:actions, []).dup
else
- @actions = action_names
+ # BC-compat way for checking if actions have already been defined
+ if defined?(@actions)
+ @actions.push(*action_names)
+ else
+ @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
+
# Set the run context on the class. Used to provide access to the node
# during class definition.
def self.run_context=(run_context)
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index 6f66fb9094..ff7d23f075 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -32,6 +32,7 @@ class Chef
@code = nil
@interpreter = nil
@flags = nil
+ @default_guard_interpreter = :default
end
def code(arg=nil)
@@ -58,31 +59,6 @@ class Chef
)
end
- def self.set_guard_inherited_attributes(*inherited_attributes)
- @class_inherited_attributes = inherited_attributes
- end
-
- def self.guard_inherited_attributes(*inherited_attributes)
- # Similar to patterns elsewhere, return attributes from this
- # class and superclasses as a form of inheritance
- ancestor_attributes = []
-
- if superclass.respond_to?(:guard_inherited_attributes)
- ancestor_attributes = superclass.guard_inherited_attributes
- end
-
- ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq
- end
-
- set_guard_inherited_attributes(
- :cwd,
- :environment,
- :group,
- :path,
- :user,
- :umask
- )
-
end
end
end
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 9cba6f1c38..8473f5b677 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -50,7 +50,7 @@ class Chef
set_or_return(
:source,
file,
- :kind_of => [ String ]
+ :kind_of => [ String, Array ]
)
end
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index cc14a03962..30520cff7e 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -17,250 +17,75 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/resource_collection/stepable_iterator'
-
+require 'chef/resource_collection/resource_set'
+require 'chef/resource_collection/resource_list'
+require 'chef/resource_collection/resource_collection_serialization'
+require 'chef/log'
+require 'forwardable'
+
+##
+# ResourceCollection currently handles two tasks:
+# 1) Keeps an ordered list of resources to use when converging the node
+# 2) Keeps a unique list of resources (keyed as `type[name]`) used for notifications
class Chef
class ResourceCollection
- include Enumerable
-
- # Matches a multiple resource lookup specification,
- # e.g., "service[nginx,unicorn]"
- MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/
+ include ResourceCollectionSerialization
+ extend Forwardable
- # Matches a single resource lookup specification,
- # e.g., "service[nginx]"
- SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/
-
- attr_reader :iterator
+ attr_reader :resource_set, :resource_list
+ private :resource_set, :resource_list
def initialize
- @resources = Array.new
- @resources_by_name = Hash.new
- @insert_after_idx = nil
- end
-
- def all_resources
- @resources
- end
-
- def [](index)
- @resources[index]
- end
-
- def []=(index, arg)
- is_chef_resource(arg)
- @resources[index] = arg
- @resources_by_name[arg.to_s] = index
- end
-
- def <<(*args)
- args.flatten.each do |a|
- is_chef_resource(a)
- @resources << a
- @resources_by_name[a.to_s] = @resources.length - 1
- end
- self
- end
-
- # 'push' is an alias method to <<
- alias_method :push, :<<
-
- def insert(resource)
- if @insert_after_idx
- # in the middle of executing a run, so any resources inserted now should
- # be placed after the most recent addition done by the currently executing
- # resource
- insert_at(@insert_after_idx + 1, resource)
- @insert_after_idx += 1
+ @resource_set = ResourceSet.new
+ @resource_list = ResourceList.new
+ end
+
+ # @param resource [Chef::Resource] The resource to insert
+ # @param resource_type [String,Symbol] If known, the resource type used in the recipe, Eg `package`, `execute`
+ # @param instance_name [String] If known, the recource name as used in the recipe, IE `vim` in `package 'vim'`
+ # @param at_location [Integer] If know, a location in the @resource_list to insert resource
+ # If you know the at_location but not the resource_type or instance_name, pass them in as nil
+ # This method is meant to be the 1 insert method necessary in the future. It should support all known use cases
+ # for writing into the ResourceCollection.
+ def insert(resource, opts={})
+ resource_type ||= opts[:resource_type] # Would rather use Ruby 2.x syntax, but oh well
+ instance_name ||= opts[:instance_name]
+ resource_list.insert(resource)
+ if !(resource_type.nil? && instance_name.nil?)
+ resource_set.insert_as(resource, resource_type, instance_name)
else
- is_chef_resource(resource)
- @resources << resource
- @resources_by_name[resource.to_s] = @resources.length - 1
+ resource_set.insert_as(resource)
end
end
- def insert_at(insert_at_index, *resources)
- resources.each do |resource|
- is_chef_resource(resource)
- end
- @resources.insert(insert_at_index, *resources)
- # update name -> location mappings and register new resource
- @resources_by_name.each_key do |key|
- @resources_by_name[key] += resources.size if @resources_by_name[key] >= insert_at_index
- end
- resources.each_with_index do |resource, i|
- @resources_by_name[resource.to_s] = insert_at_index + i
- end
+ # @deprecated
+ def []=(index, resource)
+ Chef::Log.warn("`[]=` is deprecated, use `insert` with the `at_location` parameter")
+ resource_list[index] = resource
+ resource_set.insert_as(resource)
end
- def each
- @resources.each do |resource|
- yield resource
+ # @deprecated
+ def push(*resources)
+ Chef::Log.warn("`push` is deprecated, use `insert`")
+ resources.flatten.each do |res|
+ insert(res)
end
+ self
end
- def execute_each_resource(&resource_exec_block)
- @iterator = StepableIterator.for_collection(@resources)
- @iterator.each_with_index do |resource, idx|
- @insert_after_idx = idx
- yield resource
- end
- end
-
- def each_index
- @resources.each_index do |i|
- yield i
- end
- end
-
- def empty?
- @resources.empty?
- end
-
- def lookup(resource)
- lookup_by = nil
- if resource.kind_of?(Chef::Resource)
- lookup_by = resource.to_s
- elsif resource.kind_of?(String)
- lookup_by = resource
- else
- raise ArgumentError, "Must pass a Chef::Resource or String to lookup"
- end
- res = @resources_by_name[lookup_by]
- unless res
- raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{lookup_by} (did you define it first?)"
- end
- @resources[res]
- end
-
- # Find existing resources by searching the list of existing resources. Possible
- # forms are:
- #
- # find(:file => "foobar")
- # find(:file => [ "foobar", "baz" ])
- # find("file[foobar]", "file[baz]")
- # find("file[foobar,baz]")
- #
- # Returns the matching resource, or an Array of matching resources.
- #
- # Raises an ArgumentError if you feed it bad lookup information
- # Raises a Runtime Error if it can't find the resources you are looking for.
- def find(*args)
- results = Array.new
- args.each do |arg|
- case arg
- when Hash
- results << find_resource_by_hash(arg)
- when String
- results << find_resource_by_string(arg)
- else
- msg = "arguments to #{self.class.name}#find should be of the form :resource => 'name' or resource[name]"
- raise Chef::Exceptions::InvalidResourceSpecification, msg
- end
- end
- flat_results = results.flatten
- flat_results.length == 1 ? flat_results[0] : flat_results
- end
-
- # resources is a poorly named, but we have to maintain it for back
- # compat.
- alias_method :resources, :find
-
- # Returns true if +query_object+ is a valid string for looking up a
- # resource, or raises InvalidResourceSpecification if not.
- # === Arguments
- # * query_object should be a string of the form
- # "resource_type[resource_name]", a single element Hash (e.g., :service =>
- # "apache2"), or a Chef::Resource (this is the happy path). Other arguments
- # will raise an exception.
- # === Returns
- # * true returns true for all valid input.
- # === Raises
- # * Chef::Exceptions::InvalidResourceSpecification for all invalid input.
- def validate_lookup_spec!(query_object)
- case query_object
- when Chef::Resource
- true
- when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH
- true
- when Hash
- true
- when String
- raise Chef::Exceptions::InvalidResourceSpecification,
- "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'"
- else
- raise Chef::Exceptions::InvalidResourceSpecification,
- "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
- "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
- end
- end
-
- # Serialize this object as a hash
- def to_hash
- instance_vars = Hash.new
- self.instance_variables.each do |iv|
- instance_vars[iv] = self.instance_variable_get(iv)
- end
- {
- 'json_class' => self.class.name,
- 'instance_vars' => instance_vars
- }
- end
-
- def to_json(*a)
- Chef::JSONCompat.to_json(to_hash, *a)
- end
-
- def self.json_create(o)
- collection = self.new()
- o["instance_vars"].each do |k,v|
- collection.instance_variable_set(k.to_sym, v)
- end
- collection
- end
+ # @deprecated
+ alias_method :<<, :insert
- private
+ # Read-only methods are simple to delegate - doing that below
- def find_resource_by_hash(arg)
- results = Array.new
- arg.each do |resource_name, name_list|
- names = name_list.kind_of?(Array) ? name_list : [ name_list ]
- names.each do |name|
- res_name = "#{resource_name.to_s}[#{name}]"
- results << lookup(res_name)
- end
- end
- return results
- end
+ resource_list_methods = Enumerable.instance_methods +
+ [:iterator, :all_resources, :[], :each, :execute_each_resource, :each_index, :empty?] -
+ [:find] # find needs to run on the set
+ resource_set_methods = [:lookup, :find, :resources, :keys, :validate_lookup_spec!]
- def find_resource_by_string(arg)
- results = Array.new
- case arg
- when MULTIPLE_RESOURCE_MATCH
- resource_type = $1
- arg =~ /^.+\[(.+)\]$/
- resource_list = $1
- resource_list.split(",").each do |name|
- resource_name = "#{resource_type}[#{name}]"
- results << lookup(resource_name)
- end
- when SINGLE_RESOURCE_MATCH
- resource_type = $1
- name = $2
- resource_name = "#{resource_type}[#{name}]"
- results << lookup(resource_name)
- else
- raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!"
- end
- return results
- end
+ def_delegators :resource_list, *resource_list_methods
+ def_delegators :resource_set, *resource_set_methods
- def is_chef_resource(arg)
- unless arg.kind_of?(Chef::Resource)
- raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource"
- end
- true
- end
end
end
diff --git a/lib/chef/resource_collection/resource_collection_serialization.rb b/lib/chef/resource_collection/resource_collection_serialization.rb
new file mode 100644
index 0000000000..3651fb2a2a
--- /dev/null
+++ b/lib/chef/resource_collection/resource_collection_serialization.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class Chef
+ class ResourceCollection
+ module ResourceCollectionSerialization
+ # Serialize this object as a hash
+ def to_hash
+ instance_vars = Hash.new
+ self.instance_variables.each do |iv|
+ instance_vars[iv] = self.instance_variable_get(iv)
+ end
+ {
+ 'json_class' => self.class.name,
+ 'instance_vars' => instance_vars
+ }
+ end
+
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def json_create(o)
+ collection = self.new()
+ o["instance_vars"].each do |k,v|
+ collection.instance_variable_set(k.to_sym, v)
+ end
+ collection
+ end
+ end
+
+ def is_chef_resource!(arg)
+ unless arg.kind_of?(Chef::Resource)
+ raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource"
+ end
+ true
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource_collection/resource_list.rb b/lib/chef/resource_collection/resource_list.rb
new file mode 100644
index 0000000000..e083cc0a9f
--- /dev/null
+++ b/lib/chef/resource_collection/resource_list.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource'
+require 'chef/resource_collection/stepable_iterator'
+require 'chef/resource_collection/resource_collection_serialization'
+
+# This class keeps the list of all known Resources in the order they are to be executed in. It also keeps a pointer
+# to the most recently executed resource so we can add resources-to-execute after this point.
+class Chef
+ class ResourceCollection
+ class ResourceList
+ include ResourceCollection::ResourceCollectionSerialization
+ include Enumerable
+
+ attr_reader :iterator
+
+ def initialize
+ @resources = Array.new
+ @insert_after_idx = nil
+ end
+
+ # @param resource [Chef::Resource] The resource to insert
+ # If @insert_after_idx is nil, we are not currently executing a converge so the Resource is appended to the
+ # end of the list. If @insert_after_idx is NOT nil, we ARE currently executing a converge so the resource
+ # is inserted into the middle of the list after the last resource that was converged. If it is called multiple
+ # times (when an LWRP contains multiple resources) it keeps track of that. See this example ResourceList:
+ # [File1, LWRP1, File2] # The iterator starts and points to File1. It is executed and @insert_after_idx=0
+ # [File1, LWRP1, File2] # The iterator moves to LWRP1. It is executed and @insert_after_idx=1
+ # [File1, LWRP1, Service1, File2] # The LWRP execution inserts Service1 and @insert_after_idx=2
+ # [File1, LWRP1, Service1, Service2, File2] # The LWRP inserts Service2 and @insert_after_idx=3. The LWRP
+ # finishes executing
+ # [File1, LWRP1, Service1, Service2, File2] # The iterator moves to Service1 since it is the next non-executed
+ # resource. The execute_each_resource call below resets @insert_after_idx=2
+ # If Service1 was another LWRP, it would insert its resources between Service1 and Service2. The iterator keeps
+ # track of executed resources and @insert_after_idx keeps track of where the next resource to insert should be.
+ def insert(resource)
+ is_chef_resource!(resource)
+ if @insert_after_idx
+ @resources.insert(@insert_after_idx += 1, resource)
+ else
+ @resources << resource
+ end
+ end
+
+ # @deprecated - can be removed when it is removed from resource_collection.rb
+ def []=(index, resource)
+ @resources[index] = resource
+ end
+
+ def all_resources
+ @resources
+ end
+
+ def [](index)
+ @resources[index]
+ end
+
+ def each
+ @resources.each do |resource|
+ yield resource
+ end
+ end
+
+ def execute_each_resource(&resource_exec_block)
+ @iterator = ResourceCollection::StepableIterator.for_collection(@resources)
+ @iterator.each_with_index do |resource, idx|
+ @insert_after_idx = idx
+ yield resource
+ end
+ end
+
+ def each_index
+ @resources.each_index do |i|
+ yield i
+ end
+ end
+
+ def empty?
+ @resources.empty?
+ end
+
+ end
+ end
+end
+
diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb
new file mode 100644
index 0000000000..6425c2ab08
--- /dev/null
+++ b/lib/chef/resource_collection/resource_set.rb
@@ -0,0 +1,170 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource'
+require 'chef/resource_collection/resource_collection_serialization'
+
+class Chef
+ class ResourceCollection
+ class ResourceSet
+ include ResourceCollection::ResourceCollectionSerialization
+
+ # Matches a multiple resource lookup specification,
+ # e.g., "service[nginx,unicorn]"
+ MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/
+
+ # Matches a single resource lookup specification,
+ # e.g., "service[nginx]"
+ SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/
+
+ def initialize
+ @resources_by_key = Hash.new
+ end
+
+ def keys
+ @resources_by_key.keys
+ end
+
+ def insert_as(resource, resource_type=nil, instance_name=nil)
+ is_chef_resource!(resource)
+ resource_type ||= resource.resource_name
+ instance_name ||= resource.name
+ key = ResourceSet.create_key(resource_type, instance_name)
+ @resources_by_key[key] = resource
+ end
+
+ def lookup(key)
+ case
+ when key.kind_of?(String)
+ lookup_by = key
+ when key.kind_of?(Chef::Resource)
+ lookup_by = ResourceSet.create_key(key.resource_name, key.name)
+ else
+ raise ArgumentError, "Must pass a Chef::Resource or String to lookup"
+ end
+
+ res = @resources_by_key[lookup_by]
+ unless res
+ raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{lookup_by} (did you define it first?)"
+ end
+ res
+ end
+
+ # Find existing resources by searching the list of existing resources. Possible
+ # forms are:
+ #
+ # find(:file => "foobar")
+ # find(:file => [ "foobar", "baz" ])
+ # find("file[foobar]", "file[baz]")
+ # find("file[foobar,baz]")
+ #
+ # Returns the matching resource, or an Array of matching resources.
+ #
+ # Raises an ArgumentError if you feed it bad lookup information
+ # Raises a Runtime Error if it can't find the resources you are looking for.
+ def find(*args)
+ results = Array.new
+ args.each do |arg|
+ case arg
+ when Hash
+ results << find_resource_by_hash(arg)
+ when String
+ results << find_resource_by_string(arg)
+ else
+ msg = "arguments to #{self.class.name}#find should be of the form :resource => 'name' or 'resource[name]'"
+ raise Chef::Exceptions::InvalidResourceSpecification, msg
+ end
+ end
+ flat_results = results.flatten
+ flat_results.length == 1 ? flat_results[0] : flat_results
+ end
+
+ # @deprecated
+ # resources is a poorly named, but we have to maintain it for back
+ # compat.
+ alias_method :resources, :find
+
+ # Returns true if +query_object+ is a valid string for looking up a
+ # resource, or raises InvalidResourceSpecification if not.
+ # === Arguments
+ # * query_object should be a string of the form
+ # "resource_type[resource_name]", a single element Hash (e.g., :service =>
+ # "apache2"), or a Chef::Resource (this is the happy path). Other arguments
+ # will raise an exception.
+ # === Returns
+ # * true returns true for all valid input.
+ # === Raises
+ # * Chef::Exceptions::InvalidResourceSpecification for all invalid input.
+ def validate_lookup_spec!(query_object)
+ case query_object
+ when Chef::Resource
+ true
+ when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH
+ true
+ when Hash
+ true
+ when String
+ raise Chef::Exceptions::InvalidResourceSpecification,
+ "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'"
+ else
+ raise Chef::Exceptions::InvalidResourceSpecification,
+ "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
+ "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
+ end
+ end
+
+ def self.create_key(resource_type, instance_name)
+ "#{resource_type}[#{instance_name}]"
+ end
+
+ private
+
+ def find_resource_by_hash(arg)
+ results = Array.new
+ arg.each do |resource_type, name_list|
+ instance_names = name_list.kind_of?(Array) ? name_list : [ name_list ]
+ instance_names.each do |instance_name|
+ results << lookup(ResourceSet.create_key(resource_type, instance_name))
+ end
+ end
+ return results
+ end
+
+ def find_resource_by_string(arg)
+ results = Array.new
+ case arg
+ when MULTIPLE_RESOURCE_MATCH
+ resource_type = $1
+ arg =~ /^.+\[(.+)\]$/
+ resource_list = $1
+ resource_list.split(",").each do |instance_name|
+ results << lookup(ResourceSet.create_key(resource_type, instance_name))
+ end
+ when SINGLE_RESOURCE_MATCH
+ resource_type = $1
+ name = $2
+ results << lookup(ResourceSet.create_key(resource_type, name))
+ else
+ raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!"
+ end
+ return results
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb
index bc4e955169..fd785e2f79 100644
--- a/lib/chef/shell/ext.rb
+++ b/lib/chef/shell/ext.rb
@@ -547,7 +547,7 @@ E
desc "list all the resources on the current recipe"
def resources(*args)
if args.empty?
- pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
+ pp run_context.resource_collection.keys
else
pp resources = original_resources(*args)
resources
diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb
index 4a56b6a397..7395dd5bbf 100644
--- a/lib/chef/util/dsc/local_configuration_manager.rb
+++ b/lib/chef/util/dsc/local_configuration_manager.rb
@@ -29,7 +29,7 @@ class Chef::Util::DSC
def test_configuration(configuration_document)
status = run_configuration_cmdlet(configuration_document)
- handle_what_if_exception!(status.stderr) unless status.succeeded?
+ log_what_if_exception(status.stderr) unless status.succeeded?
configuration_update_required?(status.return_value)
end
@@ -78,14 +78,14 @@ $ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configu
EOH
end
- def handle_what_if_exception!(what_if_exception_output)
+ def log_what_if_exception(what_if_exception_output)
if what_if_exception_output.gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i
# LCM returns an error if any of the resources do not support the opptional What-If
Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'")
elsif output_has_dsc_module_failure?(what_if_exception_output)
Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}")
else
- raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{what_if_exception_output.gsub(/\s+/, ' ')}"
+ Chef::Log::warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}")
end
end
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 8ca8279593..26c9c76fe5 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -141,6 +141,10 @@ class Chef
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
end
end
end
diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb
index af7b3607cd..246701a7bc 100644
--- a/lib/chef/util/powershell/cmdlet_result.rb
+++ b/lib/chef/util/powershell/cmdlet_result.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'json'
+require 'chef/json_compat'
class Chef::Util::Powershell
class CmdletResult
@@ -33,7 +33,7 @@ class Chef::Util::Powershell
def return_value
if output_format == :object
- JSON.parse(@status.stdout)
+ Chef::JSONCompat.parse(@status.stdout)
else
@status.stdout
end
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index a58c0f38f4..d57699acb4 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -200,6 +200,15 @@ LRESULT WINAPI SendMessageTimeout(
safe_attach_function :SendMessageTimeoutW, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT
safe_attach_function :SendMessageTimeoutA, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT
+=begin
+DWORD WINAPI ExpandEnvironmentStrings(
+ _In_ LPCTSTR lpSrc,
+ _Out_opt_ LPTSTR lpDst,
+ _In_ DWORD nSize
+);
+=end
+ safe_attach_function :ExpandEnvironmentStringsW, [:pointer, :pointer, :DWORD], :DWORD
+ safe_attach_function :ExpandEnvironmentStringsA, [:pointer, :pointer, :DWORD], :DWORD
end
end
end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index d2138289f5..d16bd8c12f 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -48,6 +48,8 @@ class Chef
public
WIN_VERSIONS = {
+ "Windows 10" => {:major => 6, :minor => 4, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }},
+ "Windows Server 10" => {:major => 6, :minor => 4, :callable => lambda {|product_type, suite_mask| product_type != VER_NT_WORKSTATION }},
"Windows 8.1" => {:major => 6, :minor => 3, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }},
"Windows Server 2012 R2" => {:major => 6, :minor => 3, :callable => lambda {|product_type, suite_mask| product_type != VER_NT_WORKSTATION }},
"Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ |product_type, suite_mask| product_type == VER_NT_WORKSTATION }},