summaryrefslogtreecommitdiff
path: root/lib/chef/mixin
diff options
context:
space:
mode:
authorSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
committerSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
commit24dc69a9a97e82a6e4207de68d6dcc664178249b (patch)
tree19bb289c9f88b4bbab066bc56b95d6d222fd5c35 /lib/chef/mixin
parent9348c1c9c80ee757354d624b7dc1b78ebc7605c4 (diff)
downloadchef-24dc69a9a97e82a6e4207de68d6dcc664178249b.tar.gz
[OC-3564] move core Chef to the repo root \o/ \m/
The opscode/chef repository now only contains the core Chef library code used by chef-client, knife and chef-solo!
Diffstat (limited to 'lib/chef/mixin')
-rw-r--r--lib/chef/mixin/check_helper.rb31
-rw-r--r--lib/chef/mixin/checksum.rb32
-rw-r--r--lib/chef/mixin/command.rb164
-rw-r--r--lib/chef/mixin/command/unix.rb220
-rw-r--r--lib/chef/mixin/command/windows.rb76
-rw-r--r--lib/chef/mixin/convert_to_class_name.rb65
-rw-r--r--lib/chef/mixin/create_path.rb57
-rw-r--r--lib/chef/mixin/deep_merge.rb142
-rw-r--r--lib/chef/mixin/deprecation.rb65
-rw-r--r--lib/chef/mixin/enforce_ownership_and_permissions.rb39
-rw-r--r--lib/chef/mixin/file_class.rb46
-rw-r--r--lib/chef/mixin/from_file.rb50
-rw-r--r--lib/chef/mixin/get_source_from_package.rb42
-rw-r--r--lib/chef/mixin/language.rb36
-rw-r--r--lib/chef/mixin/language_include_attribute.rb29
-rw-r--r--lib/chef/mixin/language_include_recipe.rb26
-rw-r--r--lib/chef/mixin/params_validate.rb225
-rw-r--r--lib/chef/mixin/path_sanity.rb67
-rw-r--r--lib/chef/mixin/recipe_definition_dsl_core.rb33
-rw-r--r--lib/chef/mixin/securable.rb180
-rw-r--r--lib/chef/mixin/shell_out.rb69
-rw-r--r--lib/chef/mixin/template.rb100
-rw-r--r--lib/chef/mixin/why_run.rb339
-rw-r--r--lib/chef/mixin/xml_escape.rb140
24 files changed, 2273 insertions, 0 deletions
diff --git a/lib/chef/mixin/check_helper.rb b/lib/chef/mixin/check_helper.rb
new file mode 100644
index 0000000000..b3a7835e09
--- /dev/null
+++ b/lib/chef/mixin/check_helper.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ module Mixin
+ module CheckHelper
+ def set_if_args(thing, arguments)
+ raise ArgumentError, "Must call set_if_args with a block!" unless Kernel.block_given?
+ if arguments != nil
+ yield(arguments)
+ else
+ thing
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/checksum.rb b/lib/chef/mixin/checksum.rb
new file mode 100644
index 0000000000..7b716b6285
--- /dev/null
+++ b/lib/chef/mixin/checksum.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'digest/sha2'
+require 'chef/checksum_cache'
+
+class Chef
+ module Mixin
+ module Checksum
+
+ def checksum(file)
+ Chef::ChecksumCache.checksum_for_file(file)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb
new file mode 100644
index 0000000000..55c028ff5f
--- /dev/null
+++ b/lib/chef/mixin/command.rb
@@ -0,0 +1,164 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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/log'
+require 'chef/exceptions'
+require 'tmpdir'
+require 'fcntl'
+require 'etc'
+
+class Chef
+ module Mixin
+ module Command
+ extend self
+
+ # NOTE: run_command is deprecated in favor of using Chef::Shellout which now comes from the mixlib-shellout gem. NOTE #
+
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/mixin/command/windows'
+ include ::Chef::Mixin::Command::Windows
+ extend ::Chef::Mixin::Command::Windows
+ else
+ require 'chef/mixin/command/unix'
+ include ::Chef::Mixin::Command::Unix
+ extend ::Chef::Mixin::Command::Unix
+ end
+
+ # === Parameters
+ # args<Hash>: A number of required and optional arguments
+ # command<String>, <Array>: A complete command with options to execute or a command and options as an Array
+ # creates<String>: The absolute path to a file that prevents the command from running if it exists
+ # cwd<String>: Working directory to execute command in, defaults to Dir.tmpdir
+ # timeout<String>: How many seconds to wait for the command to execute before timing out
+ # returns<String>: The single exit value command is expected to return, otherwise causes an exception
+ # ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status
+ # output_on_failure<Boolean>: Return output in raised exception regardless of Log.level
+ #
+ # user<String>: The UID or user name of the user to execute the command as
+ # group<String>: The GID or group name of the group to execute the command as
+ # environment<Hash>: Pairs of environment variable names and their values to set before execution
+ #
+ # === Returns
+ # Returns the exit status of args[:command]
+ def run_command(args={})
+ command_output = ""
+
+ args[:ignore_failure] ||= false
+ args[:output_on_failure] ||= false
+
+ # TODO: This is the wrong place for this responsibility.
+ if args.has_key?(:creates)
+ if File.exists?(args[:creates])
+ Chef::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.")
+ return false
+ end
+ end
+
+ status, stdout, stderr = output_of_command(args[:command], args)
+ command_output << "STDOUT: #{stdout}"
+ command_output << "STDERR: #{stderr}"
+ handle_command_failures(status, command_output, args)
+
+ status
+ end
+
+ def output_of_command(command, args)
+ Chef::Log.debug("Executing #{command}")
+ stderr_string, stdout_string, status = "", "", nil
+
+ exec_processing_block = lambda do |pid, stdin, stdout, stderr|
+ stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
+ end
+
+ args[:cwd] ||= Dir.tmpdir
+ unless ::File.directory?(args[:cwd])
+ raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
+ end
+
+ Dir.chdir(args[:cwd]) do
+ if args[:timeout]
+ begin
+ Timeout.timeout(args[:timeout]) do
+ status = popen4(command, args, &exec_processing_block)
+ end
+ rescue Timeout::Error => e
+ Chef::Log.error("#{command} exceeded timeout #{args[:timeout]}")
+ raise(e)
+ end
+ else
+ status = popen4(command, args, &exec_processing_block)
+ end
+
+ Chef::Log.debug("---- Begin output of #{command} ----")
+ Chef::Log.debug("STDOUT: #{stdout_string}")
+ Chef::Log.debug("STDERR: #{stderr_string}")
+ Chef::Log.debug("---- End output of #{command} ----")
+ Chef::Log.debug("Ran #{command} returned #{status.exitstatus}")
+ end
+
+ return status, stdout_string, stderr_string
+ end
+
+ def handle_command_failures(status, command_output, opts={})
+ unless opts[:ignore_failure]
+ opts[:returns] ||= 0
+ unless Array(opts[:returns]).include?(status.exitstatus)
+ # if the log level is not debug, through output of command when we fail
+ output = ""
+ if Chef::Log.level == :debug || opts[:output_on_failure]
+ output << "\n---- Begin output of #{opts[:command]} ----\n"
+ output << command_output.to_s
+ output << "\n---- End output of #{opts[:command]} ----\n"
+ end
+ raise Chef::Exceptions::Exec, "#{opts[:command]} returned #{status.exitstatus}, expected #{opts[:returns]}#{output}"
+ end
+ end
+ end
+
+ # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C.
+ #
+ # === Parameters
+ # args<Hash>: A number of required and optional arguments that will be handed out to #run_command
+ #
+ # === Returns
+ # Returns the result of #run_command
+ def run_command_with_systems_locale(args={})
+ args[:environment] ||= {}
+ args[:environment]["LC_ALL"] = ENV["LC_ALL"]
+ run_command args
+ end
+
+ # def popen4(cmd, args={}, &b)
+ # @@os_handler.popen4(cmd, args, &b)
+ # end
+
+ # module_function :popen4
+
+ def chdir_or_tmpdir(dir, &block)
+ dir ||= Dir.tmpdir
+ unless File.directory?(dir)
+ raise Chef::Exceptions::Exec, "#{dir} does not exist or is not a directory"
+ end
+ Dir.chdir(dir) do
+ block.call
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb
new file mode 100644
index 0000000000..b63a02663b
--- /dev/null
+++ b/lib/chef/mixin/command/unix.rb
@@ -0,0 +1,220 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module Command
+ module Unix
+ # This is taken directly from Ara T Howard's Open4 library, and then
+ # modified to suit the needs of Chef. Any bugs here are most likely
+ # my own, and not Ara's.
+ #
+ # The original appears in external/open4.rb in its unmodified form.
+ #
+ # Thanks Ara!
+ def popen4(cmd, args={}, &b)
+ # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select
+ # See CHEF-2916 / CHEF-1305
+ GC.disable
+
+ # Waitlast - this is magic.
+ #
+ # Do we wait for the child process to die before we yield
+ # to the block, or after? That is the magic of waitlast.
+ #
+ # By default, we are waiting before we yield the block.
+ args[:waitlast] ||= false
+
+ args[:user] ||= nil
+ unless args[:user].kind_of?(Integer)
+ args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
+ end
+ args[:group] ||= nil
+ unless args[:group].kind_of?(Integer)
+ args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
+ end
+ args[:environment] ||= {}
+
+ # Default on C locale so parsing commands output can be done
+ # independently of the node's default locale.
+ # "LC_ALL" could be set to nil, in which case we also must ignore it.
+ unless args[:environment].has_key?("LC_ALL")
+ args[:environment]["LC_ALL"] = "C"
+ end
+
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
+
+ verbose = $VERBOSE
+ begin
+ $VERBOSE = nil
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+
+ cid = fork {
+ pw.last.close
+ STDIN.reopen pw.first
+ pw.first.close
+
+ pr.first.close
+ STDOUT.reopen pr.last
+ pr.last.close
+
+ pe.first.close
+ STDERR.reopen pe.last
+ pe.last.close
+
+ STDOUT.sync = STDERR.sync = true
+
+ if args[:group]
+ Process.egid = args[:group]
+ Process.gid = args[:group]
+ end
+
+ if args[:user]
+ Process.euid = args[:user]
+ Process.uid = args[:user]
+ end
+
+ args[:environment].each do |key,value|
+ ENV[key] = value
+ end
+
+ if args[:umask]
+ umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
+ File.umask(umask)
+ end
+
+ begin
+ if cmd.kind_of?(Array)
+ exec(*cmd)
+ else
+ exec(cmd)
+ end
+ raise 'forty-two'
+ rescue Exception => e
+ Marshal.dump(e, ps.last)
+ ps.last.flush
+ end
+ ps.last.close unless (ps.last.closed?)
+ exit!
+ }
+ ensure
+ $VERBOSE = verbose
+ end
+
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
+
+ begin
+ e = Marshal.load ps.first
+ raise(Exception === e ? e : "unknown failure!")
+ rescue EOFError # If we get an EOF error, then the exec was successful
+ 42
+ ensure
+ ps.first.close
+ end
+
+ pw.last.sync = true
+
+ pi = [pw.last, pr.first, pe.first]
+
+ if b
+ begin
+ if args[:waitlast]
+ b[cid, *pi]
+ # send EOF so that if the child process is reading from STDIN
+ # it will actually finish up and exit
+ pi[0].close_write
+ Process.waitpid2(cid).last
+ else
+ # This took some doing.
+ # The trick here is to close STDIN
+ # Then set our end of the childs pipes to be O_NONBLOCK
+ # Then wait for the child to die, which means any IO it
+ # wants to do must be done - it's dead. If it isn't,
+ # it's because something totally skanky is happening,
+ # and we don't care.
+ o = StringIO.new
+ e = StringIO.new
+
+ pi[0].close
+
+ stdout = pi[1]
+ stderr = pi[2]
+
+ stdout.sync = true
+ stderr.sync = true
+
+ stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
+ stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
+
+ stdout_finished = false
+ stderr_finished = false
+
+ results = nil
+
+ while !stdout_finished || !stderr_finished
+ begin
+ channels_to_watch = []
+ channels_to_watch << stdout if !stdout_finished
+ channels_to_watch << stderr if !stderr_finished
+ ready = IO.select(channels_to_watch, nil, nil, 1.0)
+ rescue Errno::EAGAIN
+ ensure
+ results = Process.waitpid2(cid, Process::WNOHANG)
+ if results
+ stdout_finished = true
+ stderr_finished = true
+ end
+ end
+
+ if ready && ready.first.include?(stdout)
+ line = results ? stdout.gets(nil) : stdout.gets
+ if line
+ o.write(line)
+ else
+ stdout_finished = true
+ end
+ end
+ if ready && ready.first.include?(stderr)
+ line = results ? stderr.gets(nil) : stderr.gets
+ if line
+ e.write(line)
+ else
+ stderr_finished = true
+ end
+ end
+ end
+ results = Process.waitpid2(cid) unless results
+ o.rewind
+ e.rewind
+ b[cid, pi[0], o, e]
+ results.last
+ end
+ ensure
+ pi.each{|fd| fd.close unless fd.closed?}
+ end
+ else
+ [cid, pw.last, pr.first, pe.first]
+ end
+ ensure
+ GC.enable
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/command/windows.rb b/lib/chef/mixin/command/windows.rb
new file mode 100644
index 0000000000..e3d0cfdb18
--- /dev/null
+++ b/lib/chef/mixin/command/windows.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+if RUBY_VERSION =~ /^1\.8/
+ require 'win32/open3'
+else
+ require 'open3'
+end
+
+class Chef
+ module Mixin
+ module Command
+ module Windows
+ def popen4(cmd, args={}, &b)
+
+ # By default, we are waiting before we yield the block.
+ args[:waitlast] ||= false
+
+ #XXX :user, :group, :environment support?
+
+ Open3.popen3(cmd) do |stdin,stdout,stderr,cid|
+ if b
+ if args[:waitlast]
+ b[cid, stdin, stdout, stderr]
+ # send EOF so that if the child process is reading from STDIN
+ # it will actually finish up and exit
+ stdin.close_write
+ else
+ o = StringIO.new
+ e = StringIO.new
+
+ stdin.close
+
+ stdout.sync = true
+ stderr.sync = true
+
+ line = stdout.gets(nil)
+ if line
+ o.write(line)
+ end
+ line = stderr.gets(nil)
+ if line
+ e.write(line)
+ end
+ o.rewind
+ e.rewind
+ b[cid, stdin, o, e]
+ end
+ else
+ [cid, stdin, stdout, stderr]
+ end
+ end
+ $?
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
new file mode 100644
index 0000000000..7b4ec7ad3f
--- /dev/null
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module ConvertToClassName
+ extend self
+
+ def convert_to_class_name(str)
+ str = str.dup
+ str.gsub!(/[^A-Za-z0-9_]/,'_')
+ rname = nil
+ regexp = %r{^(.+?)(_(.+))?$}
+
+ mn = str.match(regexp)
+ if mn
+ rname = mn[1].capitalize
+
+ while mn && mn[3]
+ mn = mn[3].match(regexp)
+ rname << mn[1].capitalize if mn
+ end
+ end
+
+ rname
+ end
+
+ def convert_to_snake_case(str, namespace=nil)
+ str = str.dup
+ str.sub!(/^#{namespace}(\:\:)?/, '') if namespace
+ str.gsub!(/[A-Z]/) {|s| "_" + s}
+ str.downcase!
+ str.sub!(/^\_/, "")
+ str
+ end
+
+ def snake_case_basename(str)
+ with_namespace = convert_to_snake_case(str)
+ with_namespace.split("::").last.sub(/^_/, '')
+ end
+
+ def filename_to_qualified_string(base, filename)
+ file_base = File.basename(filename, ".rb")
+ base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/create_path.rb b/lib/chef/mixin/create_path.rb
new file mode 100644
index 0000000000..9b5dba14f2
--- /dev/null
+++ b/lib/chef/mixin/create_path.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ module Mixin
+ module CreatePath
+
+ # Creates a given path, including all directories that lead up to it.
+ # Like mkdir_p, but without the leaking.
+ #
+ # === Parameters
+ # file_path<String, Array>:: A string that represents the path to create,
+ # or an Array with the path-parts.
+ #
+ # === Returns
+ # The created file_path.
+ def create_path(file_path)
+ unless file_path.kind_of?(String) || file_path.kind_of?(Array)
+ raise ArgumentError, "file_path must be a string or an array!"
+ end
+
+ if file_path.kind_of?(String)
+ file_path = File.expand_path(file_path).split(File::SEPARATOR)
+ file_path.shift if file_path[0] == ''
+ # Check if path starts with a separator or drive letter (Windows)
+ unless file_path[0].match("^#{File::SEPARATOR}|^[a-zA-Z]:")
+ file_path[0] = "#{File::SEPARATOR}#{file_path[0]}"
+ end
+ end
+
+ file_path.each_index do |i|
+ create_path = File.join(file_path[0, i + 1])
+ unless File.directory?(create_path)
+ Chef::Log.debug("Creating directory #{create_path}")
+ Dir.mkdir(create_path)
+ end
+ end
+ File.expand_path(File.join(file_path))
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb
new file mode 100644
index 0000000000..c5bbc8d9e6
--- /dev/null
+++ b/lib/chef/mixin/deep_merge.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Steve Midgley (http://www.misuse.org/science)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2008 Steve Midgley
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ module Mixin
+ # == Chef::Mixin::DeepMerge
+ # Implements a deep merging algorithm for nested data structures.
+ # ==== Notice:
+ # This code was originally imported from deep_merge by Steve Midgley.
+ # deep_merge is available under the MIT license from
+ # http://trac.misuse.org/science/wiki/DeepMerge
+ module DeepMerge
+
+ class InvalidSubtractiveMerge < ArgumentError; end
+
+
+ OLD_KNOCKOUT_PREFIX = "!merge:".freeze
+
+ # Regex to match the "knockout prefix" that was used to indicate
+ # subtractive merging in Chef 10.x and previous. Subtractive merging is
+ # removed as of Chef 11, but we detect attempted use of it and raise an
+ # error (see: raise_if_knockout_used!)
+ OLD_KNOCKOUT_MATCH = %r[!merge].freeze
+
+ extend self
+
+ def merge(first, second)
+ first = Mash.new(first) unless first.kind_of?(Mash)
+ second = Mash.new(second) unless second.kind_of?(Mash)
+
+ DeepMerge.deep_merge(second, first)
+ end
+
+ # Inherited roles use the knockout_prefix array subtraction functionality
+ # This is likely to go away in Chef >= 0.11
+ def role_merge(first, second)
+ first = Mash.new(first) unless first.kind_of?(Mash)
+ second = Mash.new(second) unless second.kind_of?(Mash)
+
+ DeepMerge.deep_merge(second, first)
+ end
+
+ class InvalidParameter < StandardError; end
+
+ # Deep Merge core documentation.
+ # deep_merge! method permits merging of arbitrary child elements. The two top level
+ # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
+ # of child elements. These child elements to not have to be of the same types.
+ # Where child elements are of the same type, deep_merge will attempt to merge them together.
+ # Where child elements are not of the same type, deep_merge will skip or optionally overwrite
+ # the destination element with the contents of the source element at that level.
+ # So if you have two hashes like this:
+ # source = {:x => [1,2,3], :y => 2}
+ # dest = {:x => [4,5,'6'], :y => [7,8,9]}
+ # dest.deep_merge!(source)
+ # Results: {:x => [1,2,3,4,5,'6'], :y => 2}
+ # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
+ # To avoid this, use "deep_merge" (no bang/exclamation mark)
+ def deep_merge!(source, dest)
+ # if dest doesn't exist, then simply copy source to it
+ if dest.nil?
+ dest = source; return dest
+ end
+
+ raise_if_knockout_used!(source)
+ raise_if_knockout_used!(dest)
+ case source
+ when nil
+ dest
+ when Hash
+ source.each do |src_key, src_value|
+ if dest.kind_of?(Hash)
+ if dest[src_key]
+ dest[src_key] = deep_merge!(src_value, dest[src_key])
+ else # dest[src_key] doesn't exist so we take whatever source has
+ raise_if_knockout_used!(src_value)
+ dest[src_key] = src_value
+ end
+ else # dest isn't a hash, so we overwrite it completely
+ dest = source
+ end
+ end
+ when Array
+ if dest.kind_of?(Array)
+ dest = dest | source
+ else
+ dest = source
+ end
+ when String
+ dest = source
+ else # src_hash is not an array or hash, so we'll have to overwrite dest
+ dest = source
+ end
+ dest
+ end # deep_merge!
+
+ # Checks for attempted use of subtractive merge, which was removed for
+ # Chef 11.0. If subtractive merge use is detected, will raise an
+ # InvalidSubtractiveMerge exception.
+ def raise_if_knockout_used!(obj)
+ if uses_knockout?(obj)
+ raise InvalidSubtractiveMerge, "subtractive merge with !merge is no longer supported"
+ end
+ end
+
+ # Checks for attempted use of subtractive merge in +obj+.
+ def uses_knockout?(obj)
+ case obj
+ when String
+ obj =~ OLD_KNOCKOUT_MATCH
+ when Array
+ obj.any? {|element| element.respond_to?(:gsub) && element =~ OLD_KNOCKOUT_MATCH }
+ else
+ false
+ end
+ end
+
+ def deep_merge(source, dest)
+ deep_merge!(source.dup, dest.dup)
+ end
+
+ end
+ end
+end
+
+
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
new file mode 100644
index 0000000000..cc85c4e976
--- /dev/null
+++ b/lib/chef/mixin/deprecation.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module Deprecation
+ class DeprecatedObjectProxyBase
+ KEEPERS = %w{__id__ __send__ instance_eval == equal? initialize object_id}
+ instance_methods.each { |method_name| undef_method(method_name) unless KEEPERS.include?(method_name.to_s)}
+ end
+
+ class DeprecatedInstanceVariable < DeprecatedObjectProxyBase
+ def initialize(target, ivar_name, level=nil)
+ @target, @ivar_name = target, ivar_name
+ @level ||= :warn
+ end
+
+ def method_missing(method_name, *args, &block)
+ log_deprecation_msg(caller[0..3])
+ @target.send(method_name, *args, &block)
+ end
+
+ def inspect
+ @target.inspect
+ end
+
+ private
+
+ def log_deprecation_msg(*called_from)
+ called_from = called_from.flatten
+ log("Accessing #{@ivar_name} by the variable @#{@ivar_name} is deprecated. Support will be removed in a future release.")
+ log("Please update your cookbooks to use #{@ivar_name} in place of @#{@ivar_name}. Accessed from:")
+ called_from.each {|l| log(l)}
+ end
+
+ def log(msg)
+ # WTF: I don't get the log prefix (i.e., "[timestamp] LEVEL:") if I
+ # send to Chef::Log. No one but me should use method_missing, ever.
+ Chef::Log.logger.send(@level, msg)
+ end
+
+ end
+
+ def deprecated_ivar(obj, name, level=nil)
+ DeprecatedInstanceVariable.new(obj, name, level)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/enforce_ownership_and_permissions.rb b/lib/chef/mixin/enforce_ownership_and_permissions.rb
new file mode 100644
index 0000000000..9c1e4dda93
--- /dev/null
+++ b/lib/chef/mixin/enforce_ownership_and_permissions.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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/file_access_control'
+
+class Chef
+ module Mixin
+ module EnforceOwnershipAndPermissions
+
+ def access_controls
+ @access_controls ||= Chef::FileAccessControl.new(current_resource, new_resource, self)
+ end
+
+ # will set the proper user, group and
+ # permissions using a platform specific
+ # version of Chef::FileAccessControl
+ def enforce_ownership_and_permissions
+ access_controls.set_all
+ new_resource.updated_by_last_action(true) if access_controls.modified?
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/file_class.rb b/lib/chef/mixin/file_class.rb
new file mode 100644
index 0000000000..ed2cda47db
--- /dev/null
+++ b/lib/chef/mixin/file_class.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Mark Mzyk <mmzyk@opscode.com>
+# Author:: Seth Chisamore <schisamo@opscode.com>
+# Author:: Bryan McLellan <btm@opscode.com>
+# Copyright:: Copyright (c) 2011-2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module FileClass
+
+ def file_class
+ @host_os_file ||= if Chef::Platform.windows?
+ require 'chef/win32/file'
+ begin
+ Chef::ReservedNames::Win32::File.verify_links_supported!
+ rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
+ message = "Link resource is not supported on this version of Windows"
+ message << ": #{node[:kernel][:name]}" if node
+ message << " (#{node[:platform_version]})" if node
+ Chef::Log.fatal(message)
+ raise e
+ end
+ Chef::ReservedNames::Win32::File
+ else
+ ::File
+ end
+ end
+ end
+ end
+end
+
+
diff --git a/lib/chef/mixin/from_file.rb b/lib/chef/mixin/from_file.rb
new file mode 100644
index 0000000000..609fe1de55
--- /dev/null
+++ b/lib/chef/mixin/from_file.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module FromFile
+
+ # Loads a given ruby file, and runs instance_eval against it in the context of the current
+ # object.
+ #
+ # Raises an IOError if the file cannot be found, or is not readable.
+ def from_file(filename)
+ if File.exists?(filename) && File.readable?(filename)
+ self.instance_eval(IO.read(filename), filename, 1)
+ else
+ raise IOError, "Cannot open or read #{filename}!"
+ end
+ end
+
+ # Loads a given ruby file, and runs class_eval against it in the context of the current
+ # object.
+ #
+ # Raises an IOError if the file cannot be found, or is not readable.
+ def class_from_file(filename)
+ if File.exists?(filename) && File.readable?(filename)
+ self.class_eval(IO.read(filename), filename, 1)
+ else
+ raise IOError, "Cannot open or read #{filename}!"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
new file mode 100644
index 0000000000..6d5cb56a27
--- /dev/null
+++ b/lib/chef/mixin/get_source_from_package.rb
@@ -0,0 +1,42 @@
+# Author:: Lamont Granquist (<lamont@opscode.com>)
+# Copyright:: Copyright (c) 2008 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.
+#
+
+
+#
+# mixin to make this syntax work without specifying a source:
+#
+# gem_pacakge "/tmp/foo-x.y.z.gem"
+# rpm_package "/tmp/foo-x.y-z.rpm"
+# dpkg_package "/tmp/foo-x.y.z.deb"
+#
+
+class Chef
+ module Mixin
+ module GetSourceFromPackage
+ def initialize(new_resource, run_context)
+ super
+ # if we're passed something that looks like a filesystem path, with no source, use it
+ # - require at least one '/' in the path to avoid gem_package "foo" breaking if a file named 'foo' exists in the cwd
+ if new_resource.source.nil? && new_resource.package_name.match(/#{::File::SEPARATOR}/) && ::File.exists?(new_resource.package_name)
+ Chef::Log.debug("No package source specified, but #{new_resource.package_name} exists on the filesystem, copying to package source")
+ new_resource.source(@new_resource.package_name)
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/mixin/language.rb b/lib/chef/mixin/language.rb
new file mode 100644
index 0000000000..3aa6a6d800
--- /dev/null
+++ b/lib/chef/mixin/language.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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/dsl/platform_introspection'
+require 'chef/dsl/data_query'
+
+class Chef
+ module Mixin
+
+ # == [DEPRECATED] Chef::Mixin::Language
+ # This module is deprecated and remains only for backwards compatibility.
+ #
+ # See Chef::DSL::PlatformIntrospection and Chef::DSL::DataQuery
+ module Language
+
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::DataQuery
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/language_include_attribute.rb b/lib/chef/mixin/language_include_attribute.rb
new file mode 100644
index 0000000000..283773b25d
--- /dev/null
+++ b/lib/chef/mixin/language_include_attribute.rb
@@ -0,0 +1,29 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/dsl/include_attribute'
+
+class Chef
+ module Mixin
+
+ # DEPRECATED: This is just here for compatibility, use
+ # Chef::DSL::IncludeAttribute instead.
+ LanguageIncludeAttribute = Chef::DSL::IncludeAttribute
+ end
+end
+
diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/language_include_recipe.rb
new file mode 100644
index 0000000000..0566046560
--- /dev/null
+++ b/lib/chef/mixin/language_include_recipe.rb
@@ -0,0 +1,26 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/dsl/include_recipe'
+
+class Chef
+ module Mixin
+ LanguageIncludeRecipe = Chef::DSL::IncludeRecipe
+ end
+end
+
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
new file mode 100644
index 0000000000..649224f978
--- /dev/null
+++ b/lib/chef/mixin/params_validate.rb
@@ -0,0 +1,225 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+
+ module Mixin
+ module ParamsValidate
+
+ # Takes a hash of options, along with a map to validate them. Returns the original
+ # options hash, plus any changes that might have been made (through things like setting
+ # default values in the validation map)
+ #
+ # For example:
+ #
+ # validate({ :one => "neat" }, { :one => { :kind_of => String }})
+ #
+ # Would raise an exception if the value of :one above is not a kind_of? string. Valid
+ # map options are:
+ #
+ # :default:: Sets the default value for this parameter.
+ # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
+ # The key will be inserted into the error message if the Proc does not return true:
+ # "Option #{key}'s value #{value} #{message}!"
+ # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
+ # that the value is one of those types.
+ # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
+ # method names.
+ # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
+ # by default, options are not required.
+ # :regex:: Match the value of the paramater against a regular expression.
+ # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any
+ # of the values.
+ def validate(opts, map)
+ #--
+ # validate works by taking the keys in the validation map, assuming it's a hash, and
+ # looking for _pv_:symbol as methods. Assuming it find them, it calls the right
+ # one.
+ #++
+ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash)
+ raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)
+
+ map.each do |key, validation|
+ unless key.kind_of?(Symbol) || key.kind_of?(String)
+ raise ArgumentError, "Validation map keys must be symbols or strings!"
+ end
+ case validation
+ when true
+ _pv_required(opts, key)
+ when false
+ true
+ when Hash
+ validation.each do |check, carg|
+ check_method = "_pv_#{check.to_s}"
+ if self.respond_to?(check_method, true)
+ self.send(check_method, opts, key, carg)
+ else
+ raise ArgumentError, "Validation map has unknown check: #{check}"
+ end
+ end
+ end
+ end
+ opts
+ end
+
+ def set_or_return(symbol, arg, validation)
+ iv_symbol = "@#{symbol.to_s}".to_sym
+ map = {
+ symbol => validation
+ }
+
+ if arg == nil && self.instance_variable_defined?(iv_symbol) == true
+ self.instance_variable_get(iv_symbol)
+ else
+ opts = validate({ symbol => arg }, { symbol => validation })
+ self.instance_variable_set(iv_symbol, opts[symbol])
+ end
+ end
+
+ private
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
+ end
+ end
+
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true)
+ if is_required
+ if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
+ (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
+ true
+ else
+ raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
+ end
+ end
+ end
+
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ passes = false
+ Array(to_be).each do |tb|
+ passes = true if value == tb
+ end
+ unless passes
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ end
+ end
+ end
+
+ # Raise an exception if the parameter is not a kind_of?(to_be)
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ passes = false
+ Array(to_be).each do |tb|
+ passes = true if value.kind_of?(tb)
+ end
+ unless passes
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ end
+ end
+ end
+
+ # Raise an exception if the parameter does not respond to a given set of methods.
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
+ end
+ end
+ end
+ end
+
+ # Assert that parameter returns false when passed a predicate method.
+ # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
+ # error value.blank? returns a 'truthy' (not nil or false) value.
+ #
+ # Note, this will *PASS* if the object doesn't respond to the method.
+ # So, to make sure a value is not nil and not blank, you need to do
+ # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ predicate_method = (predicate_method_base_name.to_s + "?").to_sym
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ end
+ end
+ end
+
+ # Assign a default value to a parameter.
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value == nil
+ opts[key] = default_value
+ end
+ end
+
+ # Check a parameter against a regular expression.
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ passes = false
+ [ regex ].flatten.each do |r|
+ if value != nil
+ if r.match(value.to_s)
+ passes = true
+ end
+ end
+ end
+ unless passes
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
+ end
+ end
+ end
+
+ # Check a parameter against a hash of proc's.
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ callbacks.each do |message, zeproc|
+ if zeproc.call(value) != true
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
+ end
+ end
+ end
+ end
+
+ # Allow a parameter to default to @name
+ def _pv_name_attribute(opts, key, is_name_attribute=true)
+ if is_name_attribute
+ if opts[key] == nil
+ opts[key] = self.instance_variable_get("@name")
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/mixin/path_sanity.rb b/lib/chef/mixin/path_sanity.rb
new file mode 100644
index 0000000000..1d324f54e9
--- /dev/null
+++ b/lib/chef/mixin/path_sanity.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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.
+#
+
+class Chef
+ module Mixin
+ module PathSanity
+
+ def enforce_path_sanity(env=ENV)
+ if Chef::Config[:enforce_path_sanity]
+ path_separator = Chef::Platform.windows? ? ';' : ':'
+ existing_paths = env["PATH"].split(path_separator)
+ # ensure the Ruby and Gem bindirs are included
+ # mainly for 'full-stack' Chef installs
+ paths_to_add = []
+ paths_to_add << ruby_bindir unless sane_paths.include?(ruby_bindir)
+ paths_to_add << gem_bindir unless sane_paths.include?(gem_bindir)
+ paths_to_add << sane_paths if sane_paths
+ paths_to_add.flatten!.compact!
+ paths_to_add.each do |sane_path|
+ unless existing_paths.include?(sane_path)
+ env_path = env["PATH"].dup
+ env_path << path_separator unless env["PATH"].empty?
+ env_path << sane_path
+ env["PATH"] = env_path
+ end
+ end
+ end
+ end
+
+ private
+
+ def sane_paths
+ @sane_paths ||= begin
+ if Chef::Platform.windows?
+ %w[]
+ else
+ %w[/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin]
+ end
+ end
+ end
+
+ def ruby_bindir
+ RbConfig::CONFIG['bindir']
+ end
+
+ def gem_bindir
+ Gem.bindir
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/recipe_definition_dsl_core.rb b/lib/chef/mixin/recipe_definition_dsl_core.rb
new file mode 100644
index 0000000000..ff422d892f
--- /dev/null
+++ b/lib/chef/mixin/recipe_definition_dsl_core.rb
@@ -0,0 +1,33 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+###
+# NOTE: This file and constant are here only for backwards compatibility.
+# New code should use Chef::DSL::Recipe instead.
+#
+# This constant (module name) will eventually be deprecated and then removed.
+###
+
+require 'chef/dsl/recipe'
+
+class Chef
+ module Mixin
+ RecipeDefinitionDSLCore = Chef::DSL::Recipe
+ end
+end
diff --git a/lib/chef/mixin/securable.rb b/lib/chef/mixin/securable.rb
new file mode 100644
index 0000000000..47c388b239
--- /dev/null
+++ b/lib/chef/mixin/securable.rb
@@ -0,0 +1,180 @@
+#
+# Author:: Seth Chisamore (<schisamo@opscode.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.
+#
+
+class Chef
+ module Mixin
+ module Securable
+
+ def owner(arg=nil)
+ set_or_return(
+ :owner,
+ arg,
+ :regex => Chef::Config[:user_valid_regex]
+ )
+ end
+
+ alias :user :owner
+
+ def group(arg=nil)
+ set_or_return(
+ :group,
+ arg,
+ :regex => Chef::Config[:group_valid_regex]
+ )
+ end
+
+ def mode(arg=nil)
+ set_or_return(
+ :mode,
+ arg,
+ :callbacks => {
+ "not in valid numeric range" => lambda { |m|
+ if m.kind_of?(String)
+ m =~ /^0/ || m="0#{m}"
+ end
+
+ # Windows does not support the sticky or setuid bits
+ if Chef::Platform.windows?
+ Integer(m)<=0777 && Integer(m)>=0
+ else
+ Integer(m)<=07777 && Integer(m)>=0
+ end
+ },
+ }
+ )
+ end
+
+ # TODO should this be separated into different files?
+ if RUBY_PLATFORM =~ /mswin|mingw|windows/
+
+ # === rights_attribute
+ # "meta-method" for dynamically creating rights attributes on resources.
+ #
+ # Multiple rights attributes can be declared. This enables resources to
+ # have multiple rights attributes with separate runtime states.
+ #
+ # For example, +Chef::Resource::RemoteDirectory+ supports different
+ # rights on the directories and files by declaring separate rights
+ # attributes for each (rights and files_rights).
+ #
+ # ==== User Level API
+ # Given a resource that calls
+ #
+ # rights_attribute(:rights)
+ #
+ # Then the resource DSL could be used like this:
+ #
+ # rights :read, ["Administrators","Everyone"]
+ # rights :deny, "Pinky"
+ # rights :full_control, "Users", :applies_to_children => true
+ # rights :write, "John Keiser", :applies_to_children => :containers_only, :applies_to_self => false, :one_level_deep => true
+ #
+ # ==== Internal Data Structure
+ # rights attributes support multiple right declarations
+ # in a single resource block--the data will be merged
+ # into a single internal hash.
+ #
+ # The internal representation is a hash with the following keys:
+ #
+ # * `:permissions`: Integer of Windows permissions flags, 1..2^32
+ # or one of `[:full_control, :modify, :read_execute, :read, :write]`
+ # * `:principals`: String or Array of Strings represnting usernames on
+ # the system.
+ # * `:applies_to_children` (optional): Boolean
+ # * `:applies_to_self` (optional): Boolean
+ # * `:one_level_deep` (optional): Boolean
+ #
+ def self.rights_attribute(name)
+
+ # equivalent to something like:
+ # def rights(permissions=nil, principals=nil, args_hash=nil)
+ define_method(name) do |*args|
+ # Ruby 1.8 compat: default the arguments
+ permissions = args.length >= 1 ? args[0] : nil
+ principals = args.length >= 2 ? args[1] : nil
+ args_hash = args.length >= 3 ? args[2] : nil
+ raise ArgumentError.new("wrong number of arguments (#{args.length} for 3)") if args.length >= 4
+
+ rights = self.instance_variable_get("@#{name.to_s}".to_sym)
+ unless permissions.nil?
+ input = {
+ :permissions => permissions,
+ :principals => principals
+ }
+ input.merge!(args_hash) unless args_hash.nil?
+
+ validations = {:permissions => { :required => true },
+ :principals => { :required => true, :kind_of => [String, Array] },
+ :applies_to_children => { :equal_to => [ true, false, :containers_only, :objects_only ]},
+ :applies_to_self => { :kind_of => [ TrueClass, FalseClass ] },
+ :one_level_deep => { :kind_of => [ TrueClass, FalseClass ] }
+ }
+ validate(input, validations)
+
+ [ permissions ].flatten.each do |permission|
+ if permission.is_a?(Integer)
+ if permission < 0 || permission > 1<<32
+ raise ArgumentError, "permissions flags must be positive and <= 32 bits (#{permission})"
+ end
+ elsif !([:full_control, :modify, :read_execute, :read, :write].include?(permission.to_sym))
+ raise ArgumentError, "permissions parameter must be :full_control, :modify, :read_execute, :read, :write or an integer representing Windows permission flags"
+ end
+ end
+
+ [ principals ].flatten.each do |principal|
+ if !principal.is_a?(String)
+ raise ArgumentError, "principals parameter must be a string or array of strings representing usernames"
+ end
+ end
+
+ if input[:applies_to_children] == false
+ if input[:applies_to_self] == false
+ raise ArgumentError, "'rights' attribute must specify either :applies_to_children or :applies_to_self."
+ end
+ if input[:one_level_deep] == true
+ raise ArgumentError, "'rights' attribute specified :one_level_deep without specifying :applies_to_children."
+ end
+ end
+ rights ||= []
+ rights << input
+ end
+ set_or_return(
+ name,
+ rights,
+ {}
+ )
+ end
+ end
+
+ # create a default 'rights' attribute
+ rights_attribute(:rights)
+ rights_attribute(:deny_rights)
+
+ def inherits(arg=nil)
+ set_or_return(
+ :inherits,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ end # Windows-specific
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
new file mode 100644
index 0000000000..4eaa509f8b
--- /dev/null
+++ b/lib/chef/mixin/shell_out.rb
@@ -0,0 +1,69 @@
+#--
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2010 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/shell_out'
+require 'chef/config'
+
+class Chef
+ module Mixin
+ module ShellOut
+
+ def shell_out(*command_args)
+ cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
+ if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug?
+ cmd.live_stream = STDOUT
+ end
+ cmd.run_command
+ cmd
+ end
+
+ def shell_out!(*command_args)
+ cmd= shell_out(*command_args)
+ cmd.error!
+ cmd
+ end
+
+ DEPRECATED_OPTIONS =
+ [ [:command_log_level, :log_level],
+ [:command_log_prepend, :log_tag] ]
+
+ # CHEF-3090: Deprecate command_log_level and command_log_prepend
+ # Patterned after https://github.com/opscode/chef/commit/e1509990b559984b43e428d4d801c394e970f432
+ def run_command_compatible_options(command_args)
+ return command_args unless command_args.last.is_a?(Hash)
+
+ _command_args = command_args.dup
+ _options = _command_args.last
+
+ DEPRECATED_OPTIONS.each do |old_option, new_option|
+ # Edge case: someone specifies :command_log_level and 'command_log_level' in the option hash
+ next unless value = _options.delete(old_option) || _options.delete(old_option.to_s)
+ deprecate_option old_option, new_option
+ _options[new_option] = value
+ end
+
+ return _command_args
+ end
+
+ private
+
+ def deprecate_option(old_option, new_option)
+ Chef::Log.logger.warn "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}"
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
new file mode 100644
index 0000000000..78148d2577
--- /dev/null
+++ b/lib/chef/mixin/template.rb
@@ -0,0 +1,100 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 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 'tempfile'
+require 'erubis'
+
+class Chef
+ module Mixin
+ module Template
+
+ module ChefContext
+ def node
+ return @node if @node
+ raise "Could not find a value for node. If you are explicitly setting variables in a template, " +
+ "include a node variable if you plan to use it."
+ end
+ end
+
+ ::Erubis::Context.send(:include, ChefContext)
+
+ # Render a template with Erubis. Takes a template as a string, and a
+ # context hash.
+ def render_template(template, context)
+ begin
+ eruby = Erubis::Eruby.new(template)
+ output = eruby.evaluate(context)
+ rescue Object => e
+ raise TemplateError.new(e, template, context)
+ end
+ Tempfile.open("chef-rendered-template") do |tempfile|
+ tempfile.print(output)
+ tempfile.close
+ yield tempfile
+ end
+ end
+
+ class TemplateError < RuntimeError
+ attr_reader :original_exception, :context
+ SOURCE_CONTEXT_WINDOW = 2
+
+ def initialize(original_exception, template, context)
+ @original_exception, @template, @context = original_exception, template, context
+ end
+
+ def message
+ @original_exception.message
+ end
+
+ def line_number
+ @line_number ||= $1.to_i if original_exception.backtrace.find {|line| line =~ /\(erubis\):(\d+)/ }
+ end
+
+ def source_location
+ "on line ##{line_number}"
+ end
+
+ def source_listing
+ @source_listing ||= begin
+ lines = @template.split(/\n/)
+ if line_number
+ line_index = line_number - 1
+ beginning_line = line_index <= SOURCE_CONTEXT_WINDOW ? 0 : line_index - SOURCE_CONTEXT_WINDOW
+ source_size = SOURCE_CONTEXT_WINDOW * 2 + 1
+ else
+ beginning_line = 0
+ source_size = lines.length
+ end
+ contextual_lines = lines[beginning_line, source_size]
+ output = []
+ contextual_lines.each_with_index do |line, index|
+ line_number = (index+beginning_line+1).to_s.rjust(3)
+ output << "#{line_number}: #{line}"
+ end
+ output.join("\n")
+ end
+ end
+
+ def to_s
+ "\n\n#{self.class} (#{message}) #{source_location}:\n\n" +
+ "#{source_listing}\n\n #{original_exception.backtrace.join("\n ")}\n\n"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/why_run.rb b/lib/chef/mixin/why_run.rb
new file mode 100644
index 0000000000..22c58c1e54
--- /dev/null
+++ b/lib/chef/mixin/why_run.rb
@@ -0,0 +1,339 @@
+#
+# Author:: Dan DeLeo ( <dan@opscode.com> )
+# Author:: Marc Paradise ( <marc@opscode.com> )
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module WhyRun
+
+ # ==ConvergeActions
+ # ConvergeActions implements the logic for why run. A ConvergeActions
+ # object wraps a collection of actions, which consist of a descriptive
+ # string and a block/Proc. Actions are executed by calling #converge!
+ # When why_run mode is enabled, each action's description will be
+ # printed, but the block will not be called. Conversely, in normal mode,
+ # the block is called, but the message is not printed.
+ #
+ # In general, this class should be accessed through the API provided by
+ # Chef::Provider.
+ class ConvergeActions
+ attr_reader :actions
+
+ def initialize(resource, run_context, action)
+ @resource, @run_context = resource, run_context
+ @actions = []
+ end
+
+ def events
+ @run_context.events
+ end
+
+ # Adds an action to the list. +descriptions+ can either be an Array of
+ # Strings, or a single String describing the action; +block+ is a
+ # block/proc that implements the action.
+ def add_action(descriptions, &block)
+ @actions << [descriptions, block]
+ end
+
+ # True if there are no actions to execute.
+ def empty?
+ @actions.empty?
+ end
+
+ # Iterate over the actions, and either print the action's message, or
+ # run its code block, depending on whether why_run mode is active.
+ def converge!
+ @actions.each do |descriptions, block|
+ if !Chef::Config[:why_run]
+ block.call
+ end
+ events.resource_update_applied(@resource, @action, descriptions)
+ end
+ end
+ end
+
+ # == ResourceRequirements
+ # ResourceRequirements provides a framework for making assertions about
+ # the host system's state. It also provides a mechanism for making
+ # assumptions about what the system's state might have been when running
+ # in why run mode.
+ #
+ # For example, consider a recipe that consists of a package resource and
+ # a service resource. If the service's init script is installed by the
+ # package, and Chef is running in why run mode, then the service resource
+ # would fail when attempting to run `/etc/init.d/software-name status`.
+ # In order to provide a more useful approximation of what would happen in
+ # a real chef run, we want to instead assume that the service was created
+ # but isn't running. The logic would look like this:
+ #
+ # # Hypothetical service provider demonstrating why run assumption logic.
+ # # This isn't the actual API, it just shows the logic.
+ # class HypotheticalServiceProvider < Chef::Provider
+ #
+ # def load_current_resource
+ # # Make sure we have the init script available:
+ # if ::File.exist?("/etc/init.d/some-service"
+ # # If the init script exists, proceed as normal:
+ # status_cmd = shell_out("/etc/init.d/some-service status")
+ # if status_cmd.success?
+ # @current_resource.status(:running)
+ # else
+ # @current_resource.status(:stopped)
+ # end
+ # else
+ # if whyrun_mode?
+ # # If the init script is not available, and we're in why run mode,
+ # # assume that some previous action would've created it:
+ # log("warning: init script '/etc/init.d/some-service' is not available")
+ # log("warning: assuming that the init script would have been created, assuming the state of 'some-service' is 'stopped'")
+ # @current_resource.status(:stopped)
+ # else
+ # raise "expected init script /etc/init.d/some-service doesn't exist"
+ # end
+ # end
+ # end
+ #
+ # end
+ #
+ # In short, the code above does the following:
+ # * runs a test to determine if a requirement is met:
+ # `::File.exist?("/etc/init.d/some-service"`
+ # * raises an error if the requirement is not met, and we're not in why
+ # run mode.
+ # * if we *are* in why run mode, print a message explaining the
+ # situation, and run some code that makes an assumption about what the
+ # state of the system would be. In this case, we also skip the normal
+ # `load_current_resource` logic
+ # * when the requirement *is* met, we run the normal `load_current_resource`
+ # logic
+ #
+ # ResourceRequirements encapsulates the above logic in a more declarative API.
+ #
+ # === Examples
+ # Assertions and assumptions should be created through the WhyRun#assert
+ # method, which gets mixed in to providers. See that method's
+ # documentation for examples.
+ class ResourceRequirements
+
+ # Implements the logic for a single assertion/assumption. See the
+ # documentation for ResourceRequirements for full discussion.
+ class Assertion
+ class AssertionFailure < RuntimeError
+ end
+
+ def initialize
+ @block_action = false
+ @assertion_proc = nil
+ @failure_message = nil
+ @whyrun_message = nil
+ @resource_modifier = nil
+ @assertion_failed = false
+ @exception_type = AssertionFailure
+ end
+
+ # Defines the code block that determines if a requirement is met. The
+ # block should return a truthy value to indicate that the requirement
+ # is met, and a falsey value if the requirement is not met.
+ # # in a provider:
+ # assert(:some_action) do |a|
+ # # This provider requires the file /tmp/foo to exist:
+ # a.assertion { ::File.exist?("/tmp/foo") }
+ # end
+ def assertion(&assertion_proc)
+ @assertion_proc = assertion_proc
+ end
+
+ # Defines the failure message, and optionally the Exception class to
+ # use when a requirement is not met. It works like `raise`:
+ # # in a provider:
+ # assert(:some_action) do |a|
+ # # This example shows usage with 1 or 2 args by calling #failure_message twice.
+ # # In practice you should only call this once per Assertion.
+ #
+ # # Set the Exception class explicitly
+ # a.failure_message(Chef::Exceptions::MissingRequiredFile, "File /tmp/foo doesn't exist")
+ # # Fallback to the default error class (AssertionFailure)
+ # a.failure_message("File /tmp/foo" doesn't exist")
+ # end
+ def failure_message(*args)
+ case args.size
+ when 1
+ @failure_message = args[0]
+ when 2
+ @exception_type, @failure_message = args[0], args[1]
+ else
+ raise ArgumentError, "#{self.class}#failure_message takes 1 or 2 arguments, you gave #{args.inspect}"
+ end
+ end
+
+ # Defines a message and optionally provides a code block to execute
+ # when the requirement is not met and Chef is executing in why run
+ # mode
+ #
+ # If no failure_message is provided (above), then execution
+ # will be allowed to continue in both whyrun an dnon-whyrun
+ # mode
+ #
+ # With a service resource that requires /etc/init.d/service-name to exist:
+ # # in a provider
+ # assert(:start, :restart) do |a|
+ # a.assertion { ::File.exist?("/etc/init.d/service-name") }
+ # a.whyrun("Init script '/etc/init.d/service-name' doesn't exist, assuming a prior action would have created it.") do
+ # # blindly assume that the service exists but is stopped in why run mode:
+ # @new_resource.status(:stopped)
+ # end
+ # end
+ def whyrun(message, &resource_modifier)
+ @whyrun_message = message
+ @resource_modifier = resource_modifier
+ end
+
+ # Prevents associated actions from being invoked in whyrun mode.
+ # This will also stop further processing of assertions for a given action.
+ #
+ # An example from the template provider: if the source template doesn't exist
+ # we can't parse it in the action_create block of template - something that we do
+ # even in whyrun mode. Because the soruce template may have been created in an earlier
+ # step, we still want to keep going in whyrun mode.
+ #
+ # assert(:create, :create_if_missing) do |a|
+ # a.assertion { File::exists?(@new_resource.source) }
+ # a.whyrun "Template source file does not exist, assuming it would have been created."
+ # a.block_action!
+ # end
+ #
+ def block_action!
+ @block_action = true
+ end
+
+ def block_action?
+ @block_action
+ end
+
+ def assertion_failed?
+ @assertion_failed
+ end
+
+
+ # Runs the assertion/assumption logic. Will raise an Exception of the
+ # type specified in #failure_message (or AssertionFailure by default)
+ # if the requirement is not met and Chef is not running in why run
+ # mode. An exception will also be raised if running in why run mode
+ # and no why run message or block has been declared.
+ def run(action, events, resource)
+ if !@assertion_proc || !@assertion_proc.call
+ @assertion_failed = true
+ if Chef::Config[:why_run] && @whyrun_message
+ events.provider_requirement_failed(action, resource, @exception_type, @failure_message)
+ events.whyrun_assumption(action, resource, @whyrun_message) if @whyrun_message
+ @resource_modifier.call if @resource_modifier
+ else
+ if @failure_message
+ events.provider_requirement_failed(action, resource, @exception_type, @failure_message)
+ raise @exception_type, @failure_message
+ end
+ end
+ end
+ end
+ end
+
+ def initialize(resource, run_context)
+ @resource, @run_context = resource, run_context
+ @assertions = Hash.new {|h,k| h[k] = [] }
+ @blocked_actions = []
+ end
+
+ def events
+ @run_context.events
+ end
+
+ # Check to see if a given action is blocked by a failed assertion
+ #
+ # Takes the action name to be verified.
+ def action_blocked?(action)
+ @blocked_actions.include?(action)
+ end
+
+ # Define a new Assertion.
+ #
+ # Takes a list of action names for which the assertion should be made.
+ # ==== Examples:
+ # A File provider that requires the parent directory to exist:
+ #
+ # assert(:create, :create_if_missing) do |a|
+ # parent_dir = File.basename(@new_resource.path)
+ # a.assertion { ::File.directory?(parent_dir) }
+ # a.failure_message(Exceptions::ParentDirectoryDoesNotExist,
+ # "Can't create file #{@new_resource.path}: parent directory #{parent_dir} doesn't exist")
+ # a.why_run("assuming parent directory #{parent_dir} would have been previously created"
+ # end
+ #
+ # A service provider that requires the init script to exist:
+ #
+ # assert(:start, :restart) do |a|
+ # a.assertion { ::File.exist?(@new_resource.init_script) }
+ # a.failure_message(Exceptions::MissingInitScript,
+ # "Can't check status of #{@new_resource}: init script #{@new_resource.init_script} is missing")
+ # a.why_run("Assuming init script would have been created and service is stopped") do
+ # @current_resource.status(:stopped)
+ # end
+ # end
+ #
+ # A File provider that will error out if you don't have permissions do
+ # delete the file, *even in why run mode*:
+ #
+ # assert(:delete) do |a|
+ # a.assertion { ::File.writable?(@new_resource.path) }
+ # a.failure_message(Exceptions::InsufficientPrivileges,
+ # "You don't have sufficient privileges to delete #{@new_resource.path}")
+ # end
+ #
+ # A Template provider that will prevent action execution but continue the run in
+ # whyrun mode if the template source is not available.
+ # assert(:create, :create_if_missing) do |a|
+ # a.assertion { File::exist?(@new_resource.source) }
+ # a.failure_message Chef::Exceptions::TemplateError, "Template #{@new_resource.source} could not be found exist."
+ # a.whyrun "Template source #{@new_resource.source} does not exist. Assuming it would have been created."
+ # a.block_action!
+ # end
+ #
+ # assert(:delete) do |a|
+ # a.assertion { ::File.writable?(@new_resource.path) }
+ # a.failure_message(Exceptions::InsufficientPrivileges,
+ # "You don't have sufficient privileges to delete #{@new_resource.path}")
+ # end
+ def assert(*actions)
+ assertion = Assertion.new
+ yield assertion
+ actions.each {|action| @assertions[action] << assertion }
+ end
+
+ # Run the assertion and assumption logic.
+ def run(action)
+ @assertions[action.to_sym].each do |a|
+ a.run(action, events, @resource)
+ if a.assertion_failed? and a.block_action?
+ @blocked_actions << action
+ return
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/xml_escape.rb b/lib/chef/mixin/xml_escape.rb
new file mode 100644
index 0000000000..dac2f0c6af
--- /dev/null
+++ b/lib/chef/mixin/xml_escape.rb
@@ -0,0 +1,140 @@
+#--
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2005 Sam Ruby
+# 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.
+
+#--
+# Portions of this code are adapted from Sam Ruby's xchar.rb
+# http://intertwingly.net/stories/2005/09/28/xchar.rb
+#
+# Such code appears here under Sam's original MIT license, while portions of
+# this file are covered by the above Apache License. For a completely MIT
+# licensed version, please see Sam's original.
+#
+# Thanks, Sam!
+#
+# Copyright (c) 2005, Sam Ruby
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+require 'chef/log'
+
+begin
+ require 'fast_xs'
+rescue LoadError
+ Chef::Log.info "The fast_xs gem is not installed, slower pure ruby XML escaping will be used."
+end
+
+class Chef
+ module Mixin
+ module XMLEscape
+
+ module PureRuby
+ extend self
+
+ CP1252 = {
+ 128 => 8364, # euro sign
+ 130 => 8218, # single low-9 quotation mark
+ 131 => 402, # latin small letter f with hook
+ 132 => 8222, # double low-9 quotation mark
+ 133 => 8230, # horizontal ellipsis
+ 134 => 8224, # dagger
+ 135 => 8225, # double dagger
+ 136 => 710, # modifier letter circumflex accent
+ 137 => 8240, # per mille sign
+ 138 => 352, # latin capital letter s with caron
+ 139 => 8249, # single left-pointing angle quotation mark
+ 140 => 338, # latin capital ligature oe
+ 142 => 381, # latin capital letter z with caron
+ 145 => 8216, # left single quotation mark
+ 146 => 8217, # right single quotation mark
+ 147 => 8220, # left double quotation mark
+ 148 => 8221, # right double quotation mark
+ 149 => 8226, # bullet
+ 150 => 8211, # en dash
+ 151 => 8212, # em dash
+ 152 => 732, # small tilde
+ 153 => 8482, # trade mark sign
+ 154 => 353, # latin small letter s with caron
+ 155 => 8250, # single right-pointing angle quotation mark
+ 156 => 339, # latin small ligature oe
+ 158 => 382, # latin small letter z with caron
+ 159 => 376 # latin capital letter y with diaeresis
+ }
+
+ # http://www.w3.org/TR/REC-xml/#dt-chardata
+ PREDEFINED = {
+ 38 => '&amp;', # ampersand
+ 60 => '&lt;', # left angle bracket
+ 62 => '&gt;' # right angle bracket
+ }
+
+ # http://www.w3.org/TR/REC-xml/#charsets
+ VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF),
+ (0xE000..0xFFFD), (0x10000..0x10FFFF)]
+
+ def xml_escape(unescaped_str)
+ begin
+ unescaped_str.unpack("U*").map {|char| xml_escape_char!(char)}.join
+ rescue
+ unescaped_str.unpack("C*").map {|char| xml_escape_char!(char)}.join
+ end
+ end
+
+ private
+
+ def xml_escape_char!(char)
+ char = CP1252[char] || char
+ char = 42 unless VALID.detect {|range| range.include? char}
+ char = PREDEFINED[char] || (char<128 ? char.chr : "&##{char};")
+ end
+ end
+
+ module FastXS
+ extend self
+
+ def xml_escape(string)
+ string.fast_xs
+ end
+
+ end
+
+ if "strings".respond_to?(:fast_xs)
+ include FastXS
+ extend FastXS
+ else
+ include PureRuby
+ extend PureRuby
+ end
+ end
+ end
+end