summaryrefslogtreecommitdiff
path: root/lib/chef/provider
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/provider')
-rw-r--r--lib/chef/provider/breakpoint.rb36
-rw-r--r--lib/chef/provider/cookbook_file.rb84
-rw-r--r--lib/chef/provider/cron.rb214
-rw-r--r--lib/chef/provider/cron/solaris.rb56
-rw-r--r--lib/chef/provider/deploy.rb480
-rw-r--r--lib/chef/provider/deploy/revision.rb80
-rw-r--r--lib/chef/provider/deploy/timestamped.rb32
-rw-r--r--lib/chef/provider/directory.rb128
-rw-r--r--lib/chef/provider/env.rb152
-rw-r--r--lib/chef/provider/env/windows.rb75
-rw-r--r--lib/chef/provider/erl_call.rb106
-rw-r--r--lib/chef/provider/execute.rb68
-rw-r--r--lib/chef/provider/file.rb338
-rw-r--r--lib/chef/provider/git.rb260
-rw-r--r--lib/chef/provider/group.rb159
-rw-r--r--lib/chef/provider/group/aix.rb70
-rw-r--r--lib/chef/provider/group/dscl.rb129
-rw-r--r--lib/chef/provider/group/gpasswd.rb65
-rw-r--r--lib/chef/provider/group/groupadd.rb96
-rw-r--r--lib/chef/provider/group/groupmod.rb120
-rw-r--r--lib/chef/provider/group/pw.rb93
-rw-r--r--lib/chef/provider/group/suse.rb60
-rw-r--r--lib/chef/provider/group/usermod.rb68
-rw-r--r--lib/chef/provider/group/windows.rb79
-rw-r--r--lib/chef/provider/http_request.rb136
-rw-r--r--lib/chef/provider/ifconfig.rb214
-rw-r--r--lib/chef/provider/link.rb130
-rw-r--r--lib/chef/provider/log.rb54
-rw-r--r--lib/chef/provider/mdadm.rb92
-rw-r--r--lib/chef/provider/mount.rb128
-rw-r--r--lib/chef/provider/mount/mount.rb252
-rw-r--r--lib/chef/provider/mount/windows.rb81
-rw-r--r--lib/chef/provider/ohai.rb47
-rw-r--r--lib/chef/provider/package.rb229
-rw-r--r--lib/chef/provider/package/apt.rb147
-rw-r--r--lib/chef/provider/package/dpkg.rb128
-rw-r--r--lib/chef/provider/package/easy_install.rb136
-rw-r--r--lib/chef/provider/package/freebsd.rb149
-rw-r--r--lib/chef/provider/package/ips.rb101
-rw-r--r--lib/chef/provider/package/macports.rb105
-rw-r--r--lib/chef/provider/package/pacman.rb111
-rw-r--r--lib/chef/provider/package/portage.rb138
-rw-r--r--lib/chef/provider/package/rpm.rb121
-rw-r--r--lib/chef/provider/package/rubygems.rb548
-rw-r--r--lib/chef/provider/package/smartos.rb84
-rw-r--r--lib/chef/provider/package/solaris.rb139
-rw-r--r--lib/chef/provider/package/yum-dump.py287
-rw-r--r--lib/chef/provider/package/yum.rb1214
-rw-r--r--lib/chef/provider/package/zypper.rb144
-rw-r--r--lib/chef/provider/remote_directory.rb174
-rw-r--r--lib/chef/provider/remote_file.rb138
-rw-r--r--lib/chef/provider/resource_update.rb55
-rw-r--r--lib/chef/provider/route.rb223
-rw-r--r--lib/chef/provider/ruby_block.rb42
-rw-r--r--lib/chef/provider/script.rb57
-rw-r--r--lib/chef/provider/service.rb158
-rw-r--r--lib/chef/provider/service/arch.rb113
-rw-r--r--lib/chef/provider/service/debian.rb152
-rw-r--r--lib/chef/provider/service/freebsd.rb175
-rw-r--r--lib/chef/provider/service/gentoo.rb67
-rw-r--r--lib/chef/provider/service/init.rb87
-rw-r--r--lib/chef/provider/service/insserv.rb52
-rw-r--r--lib/chef/provider/service/invokercd.rb35
-rw-r--r--lib/chef/provider/service/macosx.rb144
-rw-r--r--lib/chef/provider/service/redhat.rb77
-rw-r--r--lib/chef/provider/service/simple.rb172
-rw-r--r--lib/chef/provider/service/solaris.rb86
-rw-r--r--lib/chef/provider/service/systemd.rb115
-rw-r--r--lib/chef/provider/service/upstart.rb232
-rw-r--r--lib/chef/provider/service/windows.rb163
-rw-r--r--lib/chef/provider/subversion.rb214
-rw-r--r--lib/chef/provider/template.rb117
-rw-r--r--lib/chef/provider/user.rb207
-rw-r--r--lib/chef/provider/user/dscl.rb288
-rw-r--r--lib/chef/provider/user/pw.rb113
-rw-r--r--lib/chef/provider/user/useradd.rb144
-rw-r--r--lib/chef/provider/user/windows.rb124
77 files changed, 11687 insertions, 0 deletions
diff --git a/lib/chef/provider/breakpoint.rb b/lib/chef/provider/breakpoint.rb
new file mode 100644
index 0000000000..224e2758eb
--- /dev/null
+++ b/lib/chef/provider/breakpoint.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.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
+ class Provider
+ class Breakpoint < Chef::Provider
+
+ def load_current_resource
+ end
+
+ def action_break
+ if defined?(Shell) && Shell.running?
+ run_context.resource_collection.iterator.pause
+ @new_resource.updated_by_last_action(true)
+ run_context.resource_collection.iterator
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb
new file mode 100644
index 0000000000..431f3f2367
--- /dev/null
+++ b/lib/chef/provider/cookbook_file.rb
@@ -0,0 +1,84 @@
+#
+# 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/file_access_control'
+require 'chef/provider/file'
+require 'tempfile'
+
+class Chef
+ class Provider
+ class CookbookFile < Chef::Provider::File
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name)
+ super
+ end
+
+ def action_create
+ if file_cache_location && content_stale?
+ description = []
+ description << "create a new cookbook_file #{@new_resource.path}"
+ description << diff_current(file_cache_location)
+ converge_by(description) do
+ Chef::Log.debug("#{@new_resource} has new contents")
+ backup_new_resource
+ deploy_tempfile do |tempfile|
+ Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
+ tempfile.close
+ FileUtils.cp(file_cache_location, tempfile.path)
+ # Since the @new_resource.path file will not be updated
+ # at the time of converge, we must use the tempfile
+ update_new_file_state(tempfile.path)
+ end
+ Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
+ end
+ else
+ set_all_access_controls
+ end
+ end
+
+ def file_cache_location
+ @file_cache_location ||= begin
+ cookbook = run_context.cookbook_collection[resource_cookbook]
+ cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
+ end
+ end
+
+ # Determine the cookbook to get the file from. If new resource sets an
+ # explicit cookbook, use it, otherwise fall back to the implicit cookbook
+ # i.e., the cookbook the resource was declared in.
+ def resource_cookbook
+ @new_resource.cookbook || @new_resource.cookbook_name
+ end
+
+ def backup_new_resource
+ if ::File.exists?(@new_resource.path)
+ backup @new_resource.path
+ end
+ end
+
+ def content_stale?
+ ( ! ::File.exist?(@new_resource.path)) || ( ! compare_content)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
new file mode 100644
index 0000000000..a7218fea5a
--- /dev/null
+++ b/lib/chef/provider/cron.rb
@@ -0,0 +1,214 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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/mixin/command'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class Cron < Chef::Provider
+ include Chef::Mixin::Command
+
+ CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/
+ ENV_PATTERN = /\A(\S+)=(\S*)/
+
+ CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home, :environment]
+
+ def initialize(new_resource, run_context)
+ super(new_resource, run_context)
+ @cron_exists = false
+ @cron_empty = false
+ end
+ attr_accessor :cron_exists, :cron_empty
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ crontab_lines = []
+ @current_resource = Chef::Resource::Cron.new(@new_resource.name)
+ @current_resource.user(@new_resource.user)
+ if crontab = read_crontab
+ cron_found = false
+ crontab.each_line do |line|
+ case line.chomp
+ when "# Chef Name: #{@new_resource.name}"
+ Chef::Log.debug("Found cron '#{@new_resource.name}'")
+ cron_found = true
+ @cron_exists = true
+ next
+ when ENV_PATTERN
+ set_environment_var($1, $2) if cron_found
+ next
+ when CRON_PATTERN
+ if cron_found
+ @current_resource.minute($1)
+ @current_resource.hour($2)
+ @current_resource.day($3)
+ @current_resource.month($4)
+ @current_resource.weekday($5)
+ @current_resource.command($6)
+ cron_found=false
+ end
+ next
+ else
+ cron_found=false # We've got a Chef comment with no following crontab line
+ next
+ end
+ end
+ Chef::Log.debug("Cron '#{@new_resource.name}' not found") unless @cron_exists
+ else
+ Chef::Log.debug("Cron empty for '#{@new_resource.user}'")
+ @cron_empty = true
+ end
+
+ @current_resource
+ end
+
+ def cron_different?
+ CRON_ATTRIBUTES.any? do |cron_var|
+ !@new_resource.send(cron_var).nil? && @new_resource.send(cron_var) != @current_resource.send(cron_var)
+ end
+ end
+
+ def action_create
+ crontab = String.new
+ newcron = String.new
+ cron_found = false
+
+ newcron << "# Chef Name: #{new_resource.name}\n"
+ [ :mailto, :path, :shell, :home ].each do |v|
+ newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v)
+ end
+ @new_resource.environment.each do |name, value|
+ newcron << "#{name}=#{value}\n"
+ end
+ newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+
+ if @cron_exists
+ unless cron_different?
+ Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'")
+ return
+ end
+ read_crontab.each_line do |line|
+ case line.chomp
+ when "# Chef Name: #{@new_resource.name}"
+ cron_found = true
+ next
+ when ENV_PATTERN
+ crontab << line unless cron_found
+ next
+ when CRON_PATTERN
+ if cron_found
+ cron_found = false
+ crontab << newcron
+ next
+ end
+ else
+ if cron_found # We've got a Chef comment with no following crontab line
+ crontab << newcron
+ cron_found = false
+ end
+ end
+ crontab << line
+ end
+
+ # Handle edge case where the Chef comment is the last line in the current crontab
+ crontab << newcron if cron_found
+
+ converge_by("update crontab entry for #{@new_resource}") do
+ write_crontab crontab
+ Chef::Log.info("#{@new_resource} updated crontab entry")
+ end
+
+ else
+ crontab = read_crontab unless @cron_empty
+ crontab << newcron
+
+ converge_by("add crontab entry for #{@new_resource}") do
+ write_crontab crontab
+ Chef::Log.info("#{@new_resource} added crontab entry")
+ end
+ end
+ end
+
+ def action_delete
+ if @cron_exists
+ crontab = String.new
+ cron_found = false
+ read_crontab.each_line do |line|
+ case line.chomp
+ when "# Chef Name: #{@new_resource.name}"
+ cron_found = true
+ next
+ when ENV_PATTERN
+ next if cron_found
+ when CRON_PATTERN
+ if cron_found
+ cron_found = false
+ next
+ end
+ else
+ # We've got a Chef comment with no following crontab line
+ cron_found = false
+ end
+ crontab << line
+ end
+ description = cron_found ? "remove #{@new_resource.name} from crontab" :
+ "save unmodified crontab"
+ converge_by(description) do
+ write_crontab crontab
+ Chef::Log.info("#{@new_resource} deleted crontab entry")
+ end
+ end
+ end
+
+ private
+
+ def set_environment_var(attr_name, attr_value)
+ if %w(MAILTO PATH SHELL HOME).include?(attr_name)
+ @current_resource.send(attr_name.downcase.to_sym, attr_value)
+ else
+ @current_resource.environment(@current_resource.environment.merge(attr_name => attr_value))
+ end
+ end
+
+ def read_crontab
+ crontab = nil
+ status = popen4("crontab -l -u #{@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}"
+ end
+ crontab
+ end
+
+ def write_crontab(crontab)
+ status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr|
+ stdin.write crontab
+ end
+ if status.exitstatus > 0
+ raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/cron/solaris.rb b/lib/chef/provider/cron/solaris.rb
new file mode 100644
index 0000000000..e0811ba0ac
--- /dev/null
+++ b/lib/chef/provider/cron/solaris.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Author:: Toomas Pelberg (toomasp@gmx.net)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# Copyright:: Copyright (c) 2010 Toomas Pelberg
+# 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/provider'
+
+class Chef
+ class Provider
+ class Cron
+ class Solaris < Chef::Provider::Cron
+
+ 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}"
+ end
+ crontab
+ end
+
+ def write_crontab(crontab)
+ tempcron = Tempfile.new("chef-cron")
+ tempcron << crontab
+ tempcron.flush
+ tempcron.chmod(0644)
+ status = run_command(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user)
+ tempcron.close!
+ if status.exitstatus > 0
+ raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
new file mode 100644
index 0000000000..60c626ab08
--- /dev/null
+++ b/lib/chef/provider/deploy.rb
@@ -0,0 +1,480 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.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/mixin/command"
+require "chef/mixin/from_file"
+require "chef/provider/git"
+require "chef/provider/subversion"
+require 'chef/dsl/recipe'
+
+class Chef
+ class Provider
+ class Deploy < Chef::Provider
+
+ include Chef::DSL::Recipe
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::Command
+
+ attr_reader :scm_provider, :release_path, :previous_release_path
+
+ def initialize(new_resource, run_context)
+ super(new_resource, run_context)
+
+ # will resolve to ither git or svn based on resource attributes ,
+ # and will create a resource corresponding to that provider
+ @scm_provider = new_resource.scm_provider.new(new_resource, run_context)
+
+ # @configuration is not used by Deploy, it is only for backwards compat with
+ # chef-deploy or capistrano hooks that might use it to get environment information
+ @configuration = @new_resource.to_hash
+ @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"]
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @scm_provider.load_current_resource
+ @release_path = @new_resource.deploy_to + "/releases/#{release_slug}"
+ end
+
+ def sudo(command,&block)
+ execute(command, &block)
+ end
+
+ def run(command, &block)
+ exec = execute(command, &block)
+ exec.user(@new_resource.user) if @new_resource.user
+ exec.group(@new_resource.group) if @new_resource.group
+ exec.cwd(release_path) unless exec.cwd
+ exec.environment(@new_resource.environment) unless exec.environment
+ converge_by("execute #{command}") do
+ exec
+ end
+ end
+
+ def define_resource_requirements
+ requirements.assert(:rollback) do |a|
+ a.assertion { all_releases[-2] }
+ a.failure_message(RuntimeError, "There is no release to rollback to!")
+ #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun.
+ end
+
+ [ @new_resource.before_migrate, @new_resource.before_symlink,
+ @new_resource.before_restart, @new_resource.after_restart ].each do |script|
+ requirements.assert(:deploy, :force_deploy) do |a|
+ callback_file = "#{release_path}/#{script}"
+ a.assertion do
+ if script && script.class == String
+ ::File.exist?(callback_file)
+ else
+ true
+ end
+ end
+ a.failure_message(RuntimeError, "Can't find your callback file #{callback_file}")
+ a.whyrun("Would assume callback file #{callback_file} included in release")
+ end
+ end
+
+ end
+
+ def action_deploy
+ save_release_state
+ if deployed?(release_path )
+ if current_release?(release_path )
+ Chef::Log.debug("#{@new_resource} is the latest version")
+ else
+ rollback_to release_path
+ end
+ else
+
+ with_rollback_on_error do
+ deploy
+ end
+ end
+ end
+
+ def action_force_deploy
+ if deployed?(release_path)
+ converge_by("delete deployed app at #{release_path} prior to force-deploy") do
+ Chef::Log.info("Already deployed app at #{release_path}, forcing.")
+ FileUtils.rm_rf(release_path)
+ Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}")
+ end
+ end
+
+ # Alternatives:
+ # * Move release_path directory before deploy and move it back when error occurs
+ # * Rollback to previous commit
+ # * Do nothing - because deploy is force, it will be retried in short time
+ # Because last is simpliest, keep it
+ deploy
+ end
+
+ def action_rollback
+ rollback_to all_releases[-2]
+ end
+
+ def rollback_to(target_release_path)
+ @release_path = target_release_path
+
+ rp_index = all_releases.index(release_path)
+ releases_to_nuke = all_releases[(rp_index + 1)..-1]
+
+ rollback
+
+ releases_to_nuke.each do |i|
+ converge_by("roll back by removing release #{i}") do
+ Chef::Log.info "#{@new_resource} removing release: #{i}"
+ FileUtils.rm_rf i
+ end
+ release_deleted(i)
+ end
+ end
+
+ def deploy
+ verify_directories_exist
+ # CHEF-3435: We need to create the directories if they don't exist before calling the
+ # scm_provider because it expects them to be there in its own assertations
+ unless self.converge_actions.empty?
+ Chef::Log.info "#{@new_resource} running collected converge_actions before calling scm_provider"
+ self.converge_actions.converge!
+ end
+ update_cached_repo # no converge-by - scm provider will dothis
+ enforce_ownership
+ copy_cached_repo
+ install_gems
+ enforce_ownership
+ callback(:before_migrate, @new_resource.before_migrate)
+ migrate
+ callback(:before_symlink, @new_resource.before_symlink)
+ symlink
+ callback(:before_restart, @new_resource.before_restart)
+ restart
+ callback(:after_restart, @new_resource.after_restart)
+ cleanup!
+ Chef::Log.info "#{@new_resource} deployed to #{@new_resource.deploy_to}"
+ end
+
+ def rollback
+ Chef::Log.info "#{@new_resource} rolling back to previous release #{release_path}"
+ symlink
+ Chef::Log.info "#{@new_resource} restarting with previous release"
+ restart
+ end
+
+
+ def callback(what, callback_code=nil)
+ @collection = Chef::ResourceCollection.new
+ case callback_code
+ when Proc
+ Chef::Log.info "#{@new_resource} running callback #{what}"
+ recipe_eval(&callback_code)
+ when String
+ run_callback_from_file("#{release_path}/#{callback_code}")
+ when nil
+ run_callback_from_file("#{release_path}/deploy/#{what}.rb")
+ end
+ end
+
+ def migrate
+ run_symlinks_before_migrate
+
+ if @new_resource.migrate
+ enforce_ownership
+
+ environment = @new_resource.environment
+ env_info = environment && environment.map do |key_and_val|
+ "#{key_and_val.first}='#{key_and_val.last}'"
+ end.join(" ")
+
+ converge_by("execute migration command #{@new_resource.migration_command}") do
+ Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
+ run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+ end
+ end
+ end
+
+ def symlink
+ purge_tempfiles_from_current_release
+ link_tempfiles_to_current_release
+ link_current_release_to_production
+ Chef::Log.info "#{@new_resource} updated symlinks"
+ end
+
+ def restart
+ if restart_cmd = @new_resource.restart_command
+ if restart_cmd.kind_of?(Proc)
+ Chef::Log.info("#{@new_resource} restarting app with embedded recipe")
+ recipe_eval(&restart_cmd)
+ else
+ converge_by("restart app using command #{@new_resource.restart_command}") do
+ Chef::Log.info("#{@new_resource} restarting app")
+ run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+ end
+ end
+ end
+ end
+
+ def cleanup!
+ chop = -1 - @new_resource.keep_releases
+ all_releases[0..chop].each do |old_release|
+ converge_by("remove old release #{old_release}") do
+ Chef::Log.info "#{@new_resource} removing old release #{old_release}"
+ FileUtils.rm_rf(old_release)
+ end
+ release_deleted(old_release)
+ end
+ end
+
+ def all_releases
+ Dir.glob(@new_resource.deploy_to + "/releases/*").sort
+ end
+
+ def update_cached_repo
+ if @new_resource.svn_force_export
+ # TODO assertion, non-recoverable - @scm_provider must be svn if force_export?
+ svn_force_export
+ else
+ run_scm_sync
+ end
+ end
+
+ def run_scm_sync
+ @scm_provider.run_action(:sync)
+ end
+
+ def svn_force_export
+ Chef::Log.info "#{@new_resource} exporting source repository"
+ @scm_provider.run_action(:force_export)
+ end
+
+ def copy_cached_repo
+ target_dir_path = @new_resource.deploy_to + "/releases"
+ converge_by("deploy from repo to #{@target_dir_path} ") do
+ FileUtils.mkdir_p(target_dir_path)
+ run_command(:command => "cp -RPp #{::File.join(@new_resource.destination, ".")} #{release_path}")
+ Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}"
+ release_created(release_path)
+ end
+ end
+
+ def enforce_ownership
+ converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do
+ FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to)
+ Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
+ Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
+ end
+ end
+
+ def verify_directories_exist
+ create_dir_unless_exists(@new_resource.deploy_to)
+ create_dir_unless_exists(@new_resource.shared_path)
+ end
+
+ def link_current_release_to_production
+ converge_by(["remove existing link at #{@new_resource.current_path}",
+ "link release #{release_path} into production at #{@new_resource.current_path}"]) do
+ FileUtils.rm_f(@new_resource.current_path)
+ begin
+ FileUtils.ln_sf(release_path, @new_resource.current_path)
+ rescue => e
+ raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}")
+ end
+ Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}"
+ end
+ enforce_ownership
+ end
+
+ def run_symlinks_before_migrate
+ links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ")
+ converge_by("make pre-migration symliinks: #{links_info}") do
+ @new_resource.symlink_before_migrate.each do |src, dest|
+ begin
+ FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}")
+ rescue => e
+ raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}")
+ end
+ end
+ Chef::Log.info "#{@new_resource} made pre-migration symlinks"
+ end
+ end
+
+ def link_tempfiles_to_current_release
+ dirs_info = @new_resource.create_dirs_before_symlink.join(",")
+ @new_resource.create_dirs_before_symlink.each do |dir|
+ create_dir_unless_exists(release_path + "/#{dir}")
+ end
+ Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}")
+
+ links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ")
+ converge_by("link shared paths into current release: #{links_info}") do
+ @new_resource.symlinks.each do |src, dest|
+ begin
+ FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest))
+ rescue => e
+ raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}")
+ end
+ end
+ Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}")
+ end
+ run_symlinks_before_migrate
+ enforce_ownership
+ end
+
+ def create_dirs_before_symlink
+ end
+
+ def purge_tempfiles_from_current_release
+ log_info = @new_resource.purge_before_symlink.join(", ")
+ converge_by("purge directories in checkout #{log_info}") do
+ @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") }
+ Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}")
+ end
+ end
+
+ protected
+
+ # Internal callback, called after copy_cached_repo.
+ # Override if you need to keep state externally.
+ # Note that YOU are responsible for implementing whyrun-friendly behavior
+ # in any actions you take in this callback.
+ def release_created(release_path)
+ end
+
+ # Note that YOU are responsible for using appropriate whyrun nomenclature
+ # Override if you need to keep state externally.
+ # Note that YOU are responsible for implementing whyrun-friendly behavior
+ # in any actions you take in this callback.
+ def release_deleted(release_path)
+ end
+
+ def release_slug
+ raise Chef::Exceptions::Override, "You must override release_slug in #{self.to_s}"
+ end
+
+ def install_gems
+ gem_resource_collection_runner.converge
+ end
+
+ def gem_resource_collection_runner
+ gems_collection = Chef::ResourceCollection.new
+ gem_packages.each { |rbgem| gems_collection << rbgem }
+ gems_run_context = run_context.dup
+ gems_run_context.resource_collection = gems_collection
+ Chef::Runner.new(gems_run_context)
+ end
+
+ def gem_packages
+ return [] unless ::File.exist?("#{release_path}/gems.yml")
+ gems = YAML.load(IO.read("#{release_path}/gems.yml"))
+
+ gems.map do |g|
+ r = Chef::Resource::GemPackage.new(g[:name], run_context)
+ r.version g[:version]
+ r.action :install
+ r.source "http://gems.github.com"
+ r
+ end
+ end
+
+ def run_options(run_opts={})
+ run_opts[:user] = @new_resource.user if @new_resource.user
+ run_opts[:group] = @new_resource.group if @new_resource.group
+ run_opts[:environment] = @new_resource.environment if @new_resource.environment
+ run_opts[:log_tag] = @new_resource.to_s
+ run_opts[:log_level] ||= :debug
+ if run_opts[:log_level] == :info
+ if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
+ run_opts[:live_stream] = STDOUT
+ end
+ end
+ run_opts
+ end
+
+ def run_callback_from_file(callback_file)
+ Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}"
+ recipe_eval do
+ Dir.chdir(release_path) do
+ from_file(callback_file) if ::File.exist?(callback_file)
+ end
+ end
+ end
+
+ def create_dir_unless_exists(dir)
+ if ::File.directory?(dir)
+ Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists"
+ return false
+ end
+ converge_by("create new directory #{dir}") do
+ begin
+ FileUtils.mkdir_p(dir)
+ Chef::Log.debug "#{@new_resource} created directory #{dir}"
+ if @new_resource.user
+ FileUtils.chown(@new_resource.user, nil, dir)
+ Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}")
+ end
+ if @new_resource.group
+ FileUtils.chown(nil, @new_resource.group, dir)
+ Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}")
+ end
+ rescue => e
+ raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}")
+ end
+ end
+ end
+
+ def with_rollback_on_error
+ yield
+ rescue ::Exception => e
+ if @new_resource.rollback_on_error
+ Chef::Log.warn "Error on deploying #{release_path}: #{e.message}"
+ failed_release = release_path
+
+ if previous_release_path
+ @release_path = previous_release_path
+ rollback
+ end
+ converge_by("remove failed deploy #{failed_release}") do
+ Chef::Log.info "Removing failed deploy #{failed_release}"
+ FileUtils.rm_rf failed_release
+ end
+ release_deleted(failed_release)
+ end
+
+ raise
+ end
+
+ def save_release_state
+ if ::File.exists?(@new_resource.current_path)
+ release = ::File.readlink(@new_resource.current_path)
+ @previous_release_path = release if ::File.exists?(release)
+ end
+ end
+
+ def deployed?(release)
+ all_releases.include?(release)
+ end
+
+ def current_release?(release)
+ @previous_release_path == release
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
new file mode 100644
index 0000000000..3728fc6a31
--- /dev/null
+++ b/lib/chef/provider/deploy/revision.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Author:: Tim Hinderliter (<tim@opscode.com>)
+# Author:: Seth Falcon (<seth@opscode.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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/provider'
+require 'chef/provider/deploy'
+require 'chef/json_compat'
+
+class Chef
+ class Provider
+ class Deploy
+ class Revision < Chef::Provider::Deploy
+
+ def all_releases
+ sorted_releases
+ end
+
+ protected
+
+ def release_created(release)
+ sorted_releases {|r| r.delete(release); r << release }
+ end
+
+ def release_deleted(release)
+ sorted_releases { |r| r.delete(release)}
+ end
+
+ def release_slug
+ scm_provider.revision_slug
+ end
+
+ private
+
+ def sorted_releases
+ cache = load_cache
+ if block_given?
+ yield cache
+ save_cache(cache)
+ end
+ cache
+ end
+
+ def sorted_releases_from_filesystem
+ Dir.glob(new_resource.deploy_to + "/releases/*").sort_by { |d| ::File.ctime(d) }
+ end
+
+ def load_cache
+ begin
+ Chef::JSONCompat.from_json(Chef::FileCache.load("revision-deploys/#{new_resource.name}"))
+ rescue Chef::Exceptions::FileNotFound
+ sorted_releases_from_filesystem
+ end
+ end
+
+ def save_cache(cache)
+ Chef::FileCache.store("revision-deploys/#{new_resource.name}", cache.to_json)
+ cache
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/provider/deploy/timestamped.rb
new file mode 100644
index 0000000000..9c2d55b490
--- /dev/null
+++ b/lib/chef/provider/deploy/timestamped.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 Provider
+ class Deploy
+ class Timestamped < Chef::Provider::Deploy
+
+ protected
+
+ def release_slug
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
new file mode 100644
index 0000000000..0329aeb1ad
--- /dev/null
+++ b/lib/chef/provider/directory.rb
@@ -0,0 +1,128 @@
+#
+# 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/config'
+require 'chef/log'
+require 'chef/resource/directory'
+require 'chef/provider'
+require 'chef/provider/file'
+require 'fileutils'
+
+class Chef
+ class Provider
+ class Directory < Chef::Provider::File
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Directory.new(@new_resource.name)
+ @current_resource.path(@new_resource.path)
+ load_current_resource_attrs
+ setup_acl
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ # this must be evaluated before whyrun messages are printed
+ access_controls.requires_changes?
+
+ requirements.assert(:create) do |a|
+ # Make sure the parent dir exists, or else fail.
+ # for why run, print a message explaining the potential error.
+ parent_directory = ::File.dirname(@new_resource.path)
+ a.assertion { @new_resource.recursive || ::File.directory?(parent_directory) }
+ a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{@new_resource.path}")
+ a.whyrun("Assuming directory #{parent_directory} would have been created")
+ end
+
+ requirements.assert(:create) do |a|
+ parent_directory = ::File.dirname(@new_resource.path)
+ a.assertion do
+ if @new_resource.recursive
+ # find the lowest-level directory in @new_resource.path that already exists
+ # make sure we have write permissions to that directory
+ is_parent_writable = lambda do |base_dir|
+ base_dir = ::File.dirname(base_dir)
+ if ::File.exist?(base_dir)
+ ::File.writable?(base_dir)
+ else
+ is_parent_writable.call(base_dir)
+ end
+ end
+ is_parent_writable.call(@new_resource.path)
+ else
+ # in why run mode & parent directory does not exist no permissions check is required
+ # If not in why run, permissions must be valid and we rely on prior assertion that dir exists
+ if !whyrun_mode? || ::File.exist?(parent_directory)
+ ::File.writable?(parent_directory)
+ else
+ true
+ end
+ end
+ end
+ a.failure_message(Chef::Exceptions::InsufficientPermissions,
+ "Cannot create #{@new_resource} at #{@new_resource.path} due to insufficient permissions")
+ end
+
+ requirements.assert(:delete) do |a|
+ a.assertion do
+ if ::File.exist?(@new_resource.path)
+ ::File.directory?(@new_resource.path) && ::File.writable?(@new_resource.path)
+ else
+ true
+ end
+ end
+ a.failure_message(RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource.path}!")
+ # No why-run handling here:
+ # * if we don't have permissions, this is unlikely to be changed earlier in the run
+ # * if the target is a file (not a dir), there's no reasonable path by which this would have been changed
+ end
+ end
+
+ def action_create
+ unless ::File.exist?(@new_resource.path)
+ converge_by("create new directory #{@new_resource.path}") do
+ if @new_resource.recursive == true
+ ::FileUtils.mkdir_p(@new_resource.path)
+ else
+ ::Dir.mkdir(@new_resource.path)
+ end
+ Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}")
+ end
+ end
+ set_all_access_controls
+ end
+
+ def action_delete
+ if ::File.exist?(@new_resource.path)
+ converge_by("delete existing directory #{@new_resource.path}") do
+ if @new_resource.recursive == true
+ FileUtils.rm_rf(@new_resource.path)
+ Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
+ else
+ ::Dir.delete(@new_resource.path)
+ Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb
new file mode 100644
index 0000000000..e857d74d68
--- /dev/null
+++ b/lib/chef/provider/env.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider'
+require 'chef/mixin/command'
+require 'chef/resource/env'
+
+class Chef
+ class Provider
+ class Env < Chef::Provider
+ include Chef::Mixin::Command
+ attr_accessor :key_exists
+
+ def initialize(new_resource, run_context)
+ super
+ @key_exists = true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Env.new(@new_resource.name)
+ @current_resource.key_name(@new_resource.key_name)
+
+ if env_key_exists(@new_resource.key_name)
+ @current_resource.value(env_value(@new_resource.key_name))
+ else
+ @key_exists = false
+ Chef::Log.debug("#{@new_resource} key does not exist")
+ end
+
+ @current_resource
+ end
+
+ def env_value(key_name)
+ raise Chef::Exceptions::Env, "#{self.to_s} provider does not implement env_value!"
+ end
+
+ def env_key_exists(key_name)
+ env_value(key_name) ? true : false
+ end
+
+ # Check to see if value needs any changes
+ #
+ # ==== Returns
+ # <true>:: If a change is required
+ # <false>:: If a change is not required
+ def compare_value
+ if @new_resource.delim
+ #e.g. check for existing value within PATH
+ not @current_resource.value.split(@new_resource.delim).any? do |val|
+ val == @new_resource.value
+ end
+ else
+ @new_resource.value != @current_resource.value
+ end
+ end
+
+ def action_create
+ if @key_exists
+ if compare_value
+ modify_env
+ Chef::Log.info("#{@new_resource} altered")
+ @new_resource.updated_by_last_action(true)
+ end
+ else
+ create_env
+ Chef::Log.info("#{@new_resource} created")
+ @new_resource.updated_by_last_action(true)
+ end
+ end
+
+ #e.g. delete a PATH element
+ #
+ # ==== Returns
+ # <true>:: If we handled the element case and caller should not delete the key
+ # <false>:: Caller should delete the key, either no :delim was specific or value was empty
+ # after we removed the element.
+ def delete_element
+ return false unless @new_resource.delim #no delim: delete the key
+ if compare_value
+ Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist")
+ return true #do not delete the key
+ else
+ new_value =
+ @current_resource.value.split(@new_resource.delim).select { |item|
+ item != @new_resource.value
+ }.join(@new_resource.delim)
+
+ if new_value.empty?
+ return false #nothing left here, delete the key
+ else
+ old_value = @new_resource.value(new_value)
+ create_env
+ Chef::Log.debug("#{@new_resource} deleted #{old_value} element")
+ @new_resource.updated_by_last_action(true)
+ return true #we removed the element and updated; do not delete the key
+ end
+ end
+ end
+
+ def action_delete
+ if @key_exists && !delete_element
+ delete_env
+ Chef::Log.info("#{@new_resource} deleted")
+ @new_resource.updated_by_last_action(true)
+ end
+ end
+
+ def action_modify
+ if @key_exists
+ if compare_value
+ modify_env
+ Chef::Log.info("#{@new_resource} modified")
+ @new_resource.updated_by_last_action(true)
+ end
+ else
+ raise Chef::Exceptions::Env, "Cannot modify #{@new_resource} - key does not exist!"
+ end
+ end
+
+ def create_env
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :#{@new_resource.action}"
+ end
+
+ def delete_env
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :delete"
+ end
+
+ def modify_env
+ if @new_resource.delim
+ #e.g. add to PATH
+ @new_resource.value << @new_resource.delim << @current_resource.value
+ end
+ create_env
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
new file mode 100644
index 0000000000..bf728b1fae
--- /dev/null
+++ b/lib/chef/provider/env/windows.rb
@@ -0,0 +1,75 @@
+#
+# 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_PLATFORM =~ /mswin|mingw32|windows/
+ require 'ruby-wmi'
+ require 'Win32API'
+end
+
+class Chef
+ class Provider
+ class Env
+ class Windows < Chef::Provider::Env
+
+ def create_env
+ obj = env_obj(@new_resource.key_name)
+ unless obj
+ obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
+ obj.name = @new_resource.key_name
+ obj.username = "<System>"
+ end
+ obj.variablevalue = @new_resource.value
+ obj.put_
+ broadcast_env_change
+ end
+
+ def delete_env
+ obj = env_obj(@new_resource.key_name)
+ if obj
+ obj.delete_
+ broadcast_env_change
+ end
+ end
+
+ def env_value(key_name)
+ obj = env_obj(key_name)
+ return obj ? obj.variablevalue : nil
+ end
+
+ def env_obj(key_name)
+ WMI::Win32_Environment.find(:first,
+ :conditions => { :name => key_name })
+ 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
+ result = 0
+ flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG
+ @send_message ||= Win32API.new('user32', 'SendMessageTimeout', 'LLLPLLP', 'L')
+ @send_message.call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment', flags, 5000, result)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb
new file mode 100644
index 0000000000..1ee1da500c
--- /dev/null
+++ b/lib/chef/provider/erl_call.rb
@@ -0,0 +1,106 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/log'
+require 'chef/mixin/command'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class ErlCall < Chef::Provider
+ include Chef::Mixin::Command
+
+ def initialize(node, new_resource)
+ super(node, new_resource)
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ true
+ end
+
+ def action_run
+ case @new_resource.name_type
+ when "sname"
+ node = "-sname #{@new_resource.node_name}"
+ when "name"
+ node = "-name #{@new_resource.node_name}"
+ end
+
+ if @new_resource.cookie
+ cookie = "-c #{@new_resource.cookie}"
+ else
+ cookie = ""
+ end
+
+ if @new_resource.distributed
+ distributed = "-s"
+ else
+ distributed = ""
+ end
+
+ command = "erl_call -e #{distributed} #{node} #{cookie}"
+
+ converge_by("run erlang block") do
+ begin
+ pid, stdin, stdout, stderr = popen4(command, :waitlast => true)
+
+ Chef::Log.debug("#{@new_resource} running")
+ Chef::Log.debug("#{@new_resource} command: #{command}")
+ Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}")
+
+ @new_resource.code.each_line { |line| stdin.puts(line.chomp) }
+
+ stdin.close
+
+ Chef::Log.debug("#{@new_resource} output: ")
+
+ stdout_output = ""
+ stdout.each_line { |line| stdout_output << line }
+ stdout.close
+
+ stderr_output = ""
+ stderr.each_line { |line| stderr_output << line }
+ stderr.close
+
+ # fail if stderr contains anything
+ if stderr_output.length > 0
+ raise Chef::Exceptions::ErlCall, stderr_output
+ end
+
+ # fail if the first 4 characters aren't "{ok,"
+ unless stdout_output[0..3].include?('{ok,')
+ raise Chef::Exceptions::ErlCall, stdout_output
+ end
+
+ @new_resource.updated_by_last_action(true)
+
+ Chef::Log.debug("#{@new_resource} #{stdout_output}")
+ Chef::Log.info("#{@new_resouce} ran successfully")
+ ensure
+ Process.wait(pid) if pid
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
new file mode 100644
index 0000000000..d6b2f91bab
--- /dev/null
+++ b/lib/chef/provider/execute.rb
@@ -0,0 +1,68 @@
+#
+# 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/mixin/shell_out'
+require 'chef/log'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class Execute < Chef::Provider
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ true
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def action_run
+ opts = {}
+
+ if sentinel_file = @new_resource.creates
+ if ::File.exists?(sentinel_file)
+ Chef::Log.debug("#{@new_resource} sentinel file #{sentinel_file} exists - nothing to do")
+ return false
+ end
+ end
+
+ # original implementation did not specify a timeout, but ShellOut
+ # *always* times out. So, set a very long default timeout
+ opts[:timeout] = @new_resource.timeout || 3600
+ opts[:returns] = @new_resource.returns if @new_resource.returns
+ opts[:environment] = @new_resource.environment if @new_resource.environment
+ opts[:user] = @new_resource.user if @new_resource.user
+ opts[:group] = @new_resource.group if @new_resource.group
+ opts[:cwd] = @new_resource.cwd if @new_resource.cwd
+ 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?
+ opts[:live_stream] = STDOUT
+ end
+ converge_by("execute #{@new_resource.command}") do
+ result = shell_out!(@new_resource.command, opts)
+ Chef::Log.info("#{@new_resource} ran successfully")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
new file mode 100644
index 0000000000..659afc6517
--- /dev/null
+++ b/lib/chef/provider/file.rb
@@ -0,0 +1,338 @@
+#
+# 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/config'
+require 'chef/log'
+require 'chef/resource/file'
+require 'chef/mixin/checksum'
+require 'chef/provider'
+require 'etc'
+require 'fileutils'
+require 'chef/scan_access_control'
+require 'chef/mixin/shell_out'
+
+class Chef
+
+ class Provider
+ class File < Chef::Provider
+ include Chef::Mixin::Checksum
+ include Chef::Mixin::ShellOut
+
+ def negative_complement(big)
+ if big > 1073741823 # Fixnum max
+ big -= (2**32) # diminished radix wrap to negative
+ end
+ big
+ end
+
+ def octal_mode(mode)
+ ((mode.respond_to?(:oct) ? mode.oct : mode.to_i) & 007777)
+ end
+
+ private :negative_complement, :octal_mode
+
+ def diff_current_from_content(new_content)
+ result = nil
+ Tempfile.open("chef-diff") do |file|
+ file.write new_content
+ file.close
+ result = diff_current file.path
+ end
+ result
+ end
+
+ def is_binary?(path)
+ ::File.open(path) do |file|
+
+ buff = file.read(Chef::Config[:diff_filesize_threshold])
+ buff = "" if buff.nil?
+ return buff !~ /^[\r[:print:]]*$/
+ end
+ end
+
+
+ def diff_current(temp_path)
+ suppress_resource_reporting = false
+
+ return [ "(diff output suppressed by config)" ] if Chef::Config[:diff_disabled]
+ return [ "(no temp file with new content, diff output suppressed)" ] unless ::File.exists?(temp_path) # should never happen?
+
+ # solaris does not support diff -N, so create tempfile to diff against if we are creating a new file
+ target_path = if ::File.exists?(@current_resource.path)
+ @current_resource.path
+ else
+ suppress_resource_reporting = true # suppress big diffs going to resource reporting service
+ tempfile = Tempfile.new('chef-tempfile')
+ tempfile.path
+ end
+
+ diff_filesize_threshold = Chef::Config[:diff_filesize_threshold]
+ diff_output_threshold = Chef::Config[:diff_output_threshold]
+
+ if ::File.size(target_path) > diff_filesize_threshold || ::File.size(temp_path) > diff_filesize_threshold
+ return [ "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)" ]
+ end
+
+ # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs
+ return [ "(current file is binary, diff output suppressed)"] if is_binary?(target_path)
+ return [ "(new content is binary, diff output suppressed)"] if is_binary?(temp_path)
+
+ begin
+ # -u: Unified diff format
+ result = shell_out("diff -u #{target_path} #{temp_path}" )
+ rescue Exception => e
+ # Should *not* receive this, but in some circumstances it seems that
+ # an exception can be thrown even using shell_out instead of shell_out!
+ return [ "Could not determine diff. Error: #{e.message}" ]
+ end
+
+ # diff will set a non-zero return code even when there's
+ # valid stdout results, if it encounters something unexpected
+ # So as long as we have output, we'll show it.
+ if not result.stdout.empty?
+ if result.stdout.length > diff_output_threshold
+ [ "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" ]
+ else
+ val = result.stdout.split("\n")
+ val.delete("\\ No newline at end of file")
+ @new_resource.diff(val.join("\\n")) unless suppress_resource_reporting
+ val
+ end
+ elsif not result.stderr.empty?
+ [ "Could not determine diff. Error: #{result.stderr}" ]
+ else
+ [ "(no diff)" ]
+ end
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ # Every child should be specifying their own constructor, so this
+ # should only be run in the file case.
+ @current_resource ||= Chef::Resource::File.new(@new_resource.name)
+ @new_resource.path.gsub!(/\\/, "/") # for Windows
+ @current_resource.path(@new_resource.path)
+ if !::File.directory?(@new_resource.path)
+ if ::File.exist?(@new_resource.path)
+ @current_resource.checksum(checksum(@new_resource.path))
+ end
+ end
+ load_current_resource_attrs
+ setup_acl
+
+ @current_resource
+ end
+
+ def load_current_resource_attrs
+ if ::File.exist?(@new_resource.path)
+ stat = ::File.stat(@new_resource.path)
+ @current_resource.owner(stat.uid)
+ @current_resource.mode(stat.mode & 07777)
+ @current_resource.group(stat.gid)
+
+ if @new_resource.group.nil?
+ @new_resource.group(@current_resource.group)
+ end
+ if @new_resource.owner.nil?
+ @new_resource.owner(@current_resource.owner)
+ end
+ if @new_resource.mode.nil?
+ @new_resource.mode(@current_resource.mode)
+ end
+ end
+ end
+
+ def setup_acl
+ @acl_scanner = ScanAccessControl.new(@new_resource, @current_resource)
+ @acl_scanner.set_all!
+ end
+
+ def define_resource_requirements
+ # this must be evaluated before whyrun messages are printed
+ access_controls.requires_changes?
+
+ requirements.assert(:create, :create_if_missing, :touch) do |a|
+ # Make sure the parent dir exists, or else fail.
+ # for why run, print a message explaining the potential error.
+ parent_directory = ::File.dirname(@new_resource.path)
+
+ a.assertion { ::File.directory?(parent_directory) }
+ a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.")
+ a.whyrun("Assuming directory #{parent_directory} would have been created")
+ end
+
+ # Make sure the file is deletable if it exists. Otherwise, fail.
+ requirements.assert(:delete) do |a|
+ a.assertion do
+ if ::File.exists?(@new_resource.path)
+ ::File.writable?(@new_resource.path)
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::InsufficientPermissions,"File #{@new_resource.path} exists but is not writable so it cannot be deleted")
+ end
+ end
+
+ # Compare the content of a file. Returns true if they are the same, false if they are not.
+ def compare_content
+ checksum(@current_resource.path) == new_resource_content_checksum
+ end
+
+ # Set the content of the file, assuming it is not set correctly already.
+ def set_content
+ unless compare_content
+ description = []
+ description << "update content in file #{@new_resource.path} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(new_resource_content_checksum)}"
+ description << diff_current_from_content(@new_resource.content)
+ converge_by(description) do
+ backup @new_resource.path if ::File.exists?(@new_resource.path)
+ ::File.open(@new_resource.path, "w") {|f| f.write @new_resource.content }
+ Chef::Log.info("#{@new_resource} contents updated")
+ end
+ end
+ end
+
+ # if you are using a tempfile before creating, you must
+ # override the default with the tempfile, since the
+ # file at @new_resource.path will not be updated on converge
+ def update_new_file_state(path=@new_resource.path)
+ stat = ::File.stat(path)
+ @new_resource.owner(stat.uid)
+ @new_resource.mode(stat.mode & 07777)
+ @new_resource.group(stat.gid)
+ if !::File.directory?(path)
+ @new_resource.checksum(checksum(path))
+ end
+ end
+
+ def action_create
+ if !::File.exists?(@new_resource.path)
+ description = []
+ desc = "create new file #{@new_resource.path}"
+ desc << " with content checksum #{short_cksum(new_resource_content_checksum)}" if new_resource.content
+ description << desc
+ description << diff_current_from_content(@new_resource.content)
+
+ converge_by(description) do
+ Chef::Log.info("entered create")
+ ::File.open(@new_resource.path, "w+") {|f| f.write @new_resource.content }
+ access_controls.set_all
+ Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
+ update_new_file_state
+ end
+ else
+ set_content unless @new_resource.content.nil?
+ set_all_access_controls
+ end
+ end
+
+ def set_all_access_controls
+ if access_controls.requires_changes?
+ converge_by(access_controls.describe_changes) do
+ access_controls.set_all
+ #Update file state with new access values
+ update_new_file_state
+ end
+ end
+ end
+
+ def action_create_if_missing
+ if ::File.exists?(@new_resource.path)
+ Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.")
+ else
+ action_create
+ end
+ end
+
+ def action_delete
+ if ::File.exists?(@new_resource.path)
+ converge_by("delete file #{@new_resource.path}") do
+ backup unless ::File.symlink?(@new_resource.path)
+ ::File.delete(@new_resource.path)
+ Chef::Log.info("#{@new_resource} deleted file at #{@new_resource.path}")
+ end
+ end
+ end
+
+ def action_touch
+ action_create
+ converge_by("update utime on file #{@new_resource.path}") do
+ time = Time.now
+ ::File.utime(time, time, @new_resource.path)
+ Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}")
+ end
+ end
+
+ def backup(file=nil)
+ file ||= @new_resource.path
+ if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(file)
+ time = Time.now
+ savetime = time.strftime("%Y%m%d%H%M%S")
+ backup_filename = "#{@new_resource.path}.chef-#{savetime}"
+ backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
+ # if :file_backup_path is nil, we fallback to the old behavior of
+ # keeping the backup in the same directory. We also need to to_s it
+ # so we don't get a type error around implicit to_str conversions.
+ prefix = Chef::Config[:file_backup_path].to_s
+ backup_path = ::File.join(prefix, backup_filename)
+ FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
+ FileUtils.cp(file, backup_path, :preserve => true)
+ Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
+
+ # Clean up after the number of backups
+ slice_number = @new_resource.backup
+ backup_files = Dir[::File.join(prefix, ".#{@new_resource.path}.chef-*")].sort { |a,b| b <=> a }
+ if backup_files.length >= @new_resource.backup
+ remainder = backup_files.slice(slice_number..-1)
+ remainder.each do |backup_to_delete|
+ FileUtils.rm(backup_to_delete)
+ Chef::Log.info("#{@new_resource} removed backup at #{backup_to_delete}")
+ end
+ end
+ end
+ end
+
+ def deploy_tempfile
+ Tempfile.open(::File.basename(@new_resource.name)) do |tempfile|
+ yield tempfile
+
+ temp_res = Chef::Resource::CookbookFile.new(@new_resource.name)
+ temp_res.path(tempfile.path)
+ ac = Chef::FileAccessControl.new(temp_res, @new_resource, self)
+ ac.set_all!
+ FileUtils.mv(tempfile.path, @new_resource.path)
+ end
+ end
+
+ private
+
+ def short_cksum(checksum)
+ return "none" if checksum.nil?
+ checksum.slice(0,6)
+ end
+
+ def new_resource_content_checksum
+ @new_resource.content && Digest::SHA2.hexdigest(@new_resource.content)
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
new file mode 100644
index 0000000000..cc524a2fcd
--- /dev/null
+++ b/lib/chef/provider/git.rb
@@ -0,0 +1,260 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.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/provider'
+require 'chef/mixin/shell_out'
+require 'fileutils'
+require 'shellwords'
+
+class Chef
+ class Provider
+ class Git < Chef::Provider
+
+ include Chef::Mixin::ShellOut
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @resolved_reference = nil
+ @current_resource = Chef::Resource::Git.new(@new_resource.name)
+ if current_revision = find_current_revision
+ @current_resource.revision current_revision
+ end
+ end
+
+ def define_resource_requirements
+ # Parent directory of the target must exist.
+ requirements.assert(:checkout, :sync) do |a|
+ dirname = ::File.dirname(@new_resource.destination)
+ a.assertion { ::File.directory?(dirname) }
+ a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
+ a.failure_message(Chef::Exceptions::MissingParentDirectory,
+ "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{dirname} does not exist")
+ end
+
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !(@new_resource.revision =~ /^origin\//) }
+ a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
+ "Deploying remote branches is not supported. " +
+ "Specify the remote branch as a local branch for " +
+ "the git repository you're deploying from " +
+ "(ie: '#{@new_resource.revision.gsub('origin/', '')}' rather than '#{@new_resource.revision}')."
+ end
+
+ requirements.assert(:all_actions) do |a|
+ # this can't be recovered from in why-run mode, because nothing that
+ # we do in the course of a run is likely to create a valid target_revision
+ # if we can't resolve it up front.
+ a.assertion { target_revision != nil }
+ 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}"
+ end
+ end
+
+ def action_checkout
+ if target_dir_non_existent_or_empty?
+ clone
+ checkout
+ enable_submodules
+ add_remotes
+ else
+ Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory"
+ end
+ end
+
+ def action_export
+ action_checkout
+ converge_by("complete the export by removing #{@new_resource.destination}.git after checkout") do
+ FileUtils.rm_rf(::File.join(@new_resource.destination,".git"))
+ end
+ end
+
+ def action_sync
+ if existing_git_clone?
+ current_rev = find_current_revision
+ Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{target_revision}"
+ unless current_revision_matches_target_revision?
+ fetch_updates
+ enable_submodules
+ Chef::Log.info "#{@new_resource} updated to revision #{target_revision}"
+ end
+ add_remotes
+ else
+ action_checkout
+ end
+ end
+
+
+ def existing_git_clone?
+ ::File.exist?(::File.join(@new_resource.destination, ".git"))
+ end
+
+ def target_dir_non_existent_or_empty?
+ !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
+ end
+
+ def find_current_revision
+ Chef::Log.debug("#{@new_resource} finding current git revision")
+ if ::File.exist?(::File.join(cwd, ".git"))
+ # 128 is returned when we're not in a git repo. this is fine
+ result = shell_out!('git rev-parse HEAD', :cwd => cwd, :returns => [0,128]).stdout.strip
+ end
+ sha_hash?(result) ? result : nil
+ end
+
+ def add_remotes
+ if (@new_resource.additional_remotes.length > 0)
+ @new_resource.additional_remotes.each_pair do |remote_name, remote_url|
+ converge_by("add remote #{remote_name} from #{remote_url}") do
+ Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
+ command = "git remote add #{remote_name} #{remote_url}"
+ if shell_out(command, run_options(:cwd => @new_resource.destination, :log_level => :info)).exitstatus != 0
+ @new_resource.updated_by_last_action(true)
+ end
+ end
+ end
+ end
+ end
+
+ def clone
+ converge_by("clone from #{@new_resource.repository} into #{@new_resource.destination}") do
+ remote = @new_resource.remote
+
+ args = []
+ args << "-o #{remote}" unless remote == 'origin'
+ args << "--depth #{@new_resource.depth}" if @new_resource.depth
+
+ Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
+
+ clone_cmd = "git clone #{args.join(' ')} #{@new_resource.repository} #{Shellwords.escape @new_resource.destination}"
+ shell_out!(clone_cmd, run_options(:log_level => :info))
+ end
+ end
+
+ def checkout
+ sha_ref = target_revision
+ 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 deploy #{sha_ref}", run_options(:cwd => @new_resource.destination))
+ Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} reference: #{sha_ref}"
+ end
+ end
+
+ def enable_submodules
+ if @new_resource.enable_submodules
+ converge_by("enable git submodules for #{@new_resource}") do
+ Chef::Log.info "#{@new_resource} enabling git submodules"
+ # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
+ command = "git submodule update --init --recursive"
+ shell_out!(command, run_options(:cwd => @new_resource.destination, :log_level => :info))
+ end
+ end
+ end
+
+ def fetch_updates
+ setup_remote_tracking_branches if @new_resource.remote != 'origin'
+ converge_by("fetch updates for #{@new_resource.remote}") do
+ # since we're in a local branch already, just reset to specified revision rather than merge
+ fetch_command = "git fetch #{@new_resource.remote} && git fetch #{@new_resource.remote} --tags && git reset --hard #{target_revision}"
+ Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
+ shell_out!(fetch_command, run_options(:cwd => @new_resource.destination))
+ end
+ end
+
+ # Use git-config to setup a remote tracking branches. Could use
+ # git-remote but it complains when a remote of the same name already
+ # exists, git-config will just silenty overwrite the setting every
+ # time. This could cause wierd-ness in the remote cache if the url
+ # changes between calls, but as long as the repositories are all
+ # based from each other it should still work fine.
+ def setup_remote_tracking_branches
+ command = []
+ converge_by("set up remote tracking branches for #{@new_resource.repository} at #{@new_resource.remote}") do
+ Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{@new_resource.repository} "+
+ "at remote #{@new_resource.remote}"
+ command << "git config remote.#{@new_resource.remote}.url #{@new_resource.repository}"
+ command << "git config remote.#{@new_resource.remote}.fetch +refs/heads/*:refs/remotes/#{@new_resource.remote}/*"
+ shell_out!(command.join(" && "), run_options(:cwd => @new_resource.destination))
+ end
+ end
+
+ def current_revision_matches_target_revision?
+ (!@current_resource.revision.nil?) && (target_revision.strip.to_i(16) == @current_resource.revision.strip.to_i(16))
+ end
+
+ def target_revision
+ @target_revision ||= begin
+ if sha_hash?(@new_resource.revision)
+ @target_revision = @new_resource.revision
+ else
+ @target_revision = remote_resolve_reference
+ end
+ end
+ end
+
+ alias :revision_slug :target_revision
+
+ def remote_resolve_reference
+ Chef::Log.debug("#{@new_resource} resolving remote reference")
+ command = git('ls-remote', @new_resource.repository, @new_resource.revision)
+ @resolved_reference = shell_out!(command, run_options).stdout
+ if @resolved_reference =~ /^([0-9a-f]{40})\s+(\S+)/
+ $1
+ else
+ nil
+ end
+ end
+
+ private
+
+ def run_options(run_opts={})
+ run_opts[:user] = @new_resource.user if @new_resource.user
+ run_opts[:group] = @new_resource.group if @new_resource.group
+ run_opts[:environment] = {"GIT_SSH" => @new_resource.ssh_wrapper} if @new_resource.ssh_wrapper
+ run_opts[:log_tag] = @new_resource.to_s
+ run_opts[:log_level] ||= :debug
+ if run_opts[:log_level] == :info
+ if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
+ run_opts[:live_stream] = STDOUT
+ end
+ end
+ run_opts
+ end
+
+ def cwd
+ @new_resource.destination
+ end
+
+ def git(*args)
+ ["git", *args].compact.join(" ")
+ end
+
+ def sha_hash?(string)
+ string =~ /^[0-9a-f]{40}$/
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
new file mode 100644
index 0000000000..81d7d7a400
--- /dev/null
+++ b/lib/chef/provider/group.rb
@@ -0,0 +1,159 @@
+#
+# Author:: AJ Christensen (<aj@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/provider'
+require 'chef/mixin/command'
+require 'chef/resource/group'
+require 'etc'
+
+class Chef
+ class Provider
+ class Group < Chef::Provider
+ include Chef::Mixin::Command
+ attr_accessor :group_exists
+ attr_accessor :change_desc
+
+ def whyrun_supported?
+ true
+ end
+
+ def initialize(new_resource, run_context)
+ super
+ @group_exists = true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Group.new(@new_resource.name)
+ @current_resource.group_name(@new_resource.group_name)
+
+ group_info = nil
+ begin
+ group_info = Etc.getgrnam(@new_resource.group_name)
+ rescue ArgumentError => e
+ @group_exists = false
+ Chef::Log.debug("#{@new_resource} group does not exist")
+ end
+
+ if group_info
+ @new_resource.gid(group_info.gid) unless @new_resource.gid
+ @current_resource.gid(group_info.gid)
+ @current_resource.members(group_info.mem)
+ end
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:modify) do |a|
+ a.assertion { @group_exists }
+ a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!")
+ a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
+ end
+ end
+
+ # Check to see if a group needs any changes. Populate
+ # @change_desc with a description of why a change must occur
+ #
+ # ==== Returns
+ # <true>:: If a change is required
+ # <false>:: If a change is not required
+ def compare_group
+ @change_desc = nil
+ if @new_resource.gid != @current_resource.gid
+ @change_desc = "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+ return true
+ end
+
+ if(@new_resource.append)
+ missing_members = []
+ @new_resource.members.each do |member|
+ next if @current_resource.members.include?(member)
+ missing_members << member
+ end
+ if missing_members.length > 0
+ @change_desc = "add missing member(s): #{missing_members.join(", ")}"
+ return true
+ end
+ else
+ if @new_resource.members != @current_resource.members
+ @change_desc = "replace group members with new list of members"
+ return true
+ end
+ end
+ return false
+ end
+
+ def action_create
+ case @group_exists
+ when false
+ converge_by("create #{@new_resource}") do
+ create_group
+ Chef::Log.info("#{@new_resource} created")
+ end
+ else
+ if compare_group
+ converge_by(["alter group #{@new_resource}", @change_desc ]) do
+ manage_group
+ Chef::Log.info("#{@new_resource} altered")
+ end
+ end
+ end
+ end
+
+ def action_remove
+ if @group_exists
+ converge_by("remove group #{@new_resource}") do
+ remove_group
+ Chef::Log.info("#{@new_resource} removed")
+ end
+ end
+ end
+
+ def action_manage
+ if @group_exists && compare_group
+ converge_by(["manage group #{@new_resource}", @change_desc]) do
+ manage_group
+ Chef::Log.info("#{@new_resource} managed")
+ end
+ end
+ end
+
+ def action_modify
+ if compare_group
+ converge_by(["modify group #{@new_resource}", @change_desc]) do
+ manage_group
+ Chef::Log.info("#{@new_resource} modified")
+ end
+ end
+ end
+
+ def create_group
+ raise NotImplementedError, "subclasses of Chef::Provider::Group should define #create_group"
+ end
+
+ def manage_group
+ raise NotImplementedError, "subclasses of Chef::Provider::Group should define #manage_group"
+ end
+
+ def remove_group
+ raise NotImplementedError, "subclasses of Chef::Provider::Group should define #remove_group"
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
new file mode 100644
index 0000000000..9dedef351a
--- /dev/null
+++ b/lib/chef/provider/group/aix.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/group/usermod'
+
+class Chef
+ class Provider
+ class Group
+ class Aix < Chef::Provider::Group::Usermod
+
+ def required_binaries
+ [ "/usr/bin/mkgroup",
+ "/usr/bin/chgroup",
+ "/usr/sbin/rmgroup" ]
+ end
+
+ def create_group
+ command = "mkgroup"
+ command << set_options << " #{@new_resource.group_name}"
+ run_command(:command => command)
+ modify_group_members
+ end
+
+ def manage_group
+ command = "chgroup"
+ options = set_options
+ #Usage: chgroup [-R load_module] "attr=value" ... group
+ if options.size > 0
+ command << options << " #{@new_resource.group_name}"
+ run_command(:command => command)
+ end
+ modify_group_members
+ end
+
+ def remove_group
+ run_command(:command => "rmgroup #{@new_resource.group_name}")
+ end
+
+ def set_options
+ opts = ""
+ { :gid => "id" }.sort { |a,b| a[0] <=> b[0] }.each do |field, option|
+ if @current_resource.send(field) != @new_resource.send(field)
+ if @new_resource.send(field)
+ Chef::Log.debug("#{@new_resource} setting #{field.to_s} to #{@new_resource.send(field)}")
+ opts << " '#{option}=#{@new_resource.send(field)}'"
+ end
+ end
+ end
+ opts
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
new file mode 100644
index 0000000000..a8ba32641c
--- /dev/null
+++ b/lib/chef/provider/group/dscl.rb
@@ -0,0 +1,129 @@
+#
+# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
+# Copyright:: Copyright (c) 2009 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class Group
+ class Dscl < Chef::Provider::Group
+
+ def dscl(*args)
+ host = "."
+ stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}"
+ status = popen4(cmd) do |pid, stdin, stdout, stderr|
+ stdout.each { |line| stdout_result << line }
+ stderr.each { |line| stderr_result << line }
+ end
+ return [cmd, status, stdout_result, stderr_result]
+ end
+
+ def safe_dscl(*args)
+ result = dscl(*args)
+ return "" if ( args.first =~ /^delete/ ) && ( result[1].exitstatus != 0 )
+ raise(Chef::Exceptions::Group,"dscl error: #{result.inspect}") unless result[1].exitstatus == 0
+ raise(Chef::Exceptions::Group,"dscl error: #{result.inspect}") if result[2] =~ /No such key: /
+ return result[2]
+ end
+
+ # This is handled in providers/group.rb by Etc.getgrnam()
+ # def group_exists?(group)
+ # groups = safe_dscl("list /Groups")
+ # !! ( groups =~ Regexp.new("\n#{group}\n") )
+ # end
+
+ # get a free GID greater than 200
+ def get_free_gid(search_limit=1000)
+ gid = nil; next_gid_guess = 200
+ groups_gids = safe_dscl("list /Groups gid")
+ while(next_gid_guess < search_limit + 200)
+ if groups_gids =~ Regexp.new("#{Regexp.escape(next_gid_guess.to_s)}\n")
+ next_gid_guess += 1
+ else
+ gid = next_gid_guess
+ break
+ end
+ end
+ return gid || raise("gid not found. Exhausted. Searched #{search_limit} times")
+ end
+
+ def gid_used?(gid)
+ return false unless gid
+ groups_gids = safe_dscl("list /Groups gid")
+ !! ( groups_gids =~ Regexp.new("#{Regexp.escape(gid.to_s)}\n") )
+ end
+
+ def set_gid
+ @new_resource.gid(get_free_gid) if [nil,""].include? @new_resource.gid
+ raise(Chef::Exceptions::Group,"gid is already in use") if gid_used?(@new_resource.gid)
+ safe_dscl("create /Groups/#{@new_resource.group_name} PrimaryGroupID #{@new_resource.gid}")
+ end
+
+ def set_members
+ unless @new_resource.append
+ Chef::Log.debug("#{@new_resource} removing group members #{@current_resource.members.join(' ')}") unless @current_resource.members.empty?
+ safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers ''") # clear guid list
+ safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership ''") # clear user list
+ end
+ unless @new_resource.members.empty?
+ Chef::Log.debug("#{@new_resource} setting group members #{@new_resource.members.join(', ')}")
+ safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{@new_resource.members.join(' ')}")
+ end
+ end
+
+ def define_resource_requirements
+ super
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/dscl") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+
+ def load_current_resource
+ super
+ end
+
+ def create_group
+ dscl_create_group
+ set_gid
+ set_members
+ end
+
+ def manage_group
+ if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name)
+ dscl_create_group
+ end
+ if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+ set_gid
+ end
+ if @new_resource.members && (@current_resource.members != @new_resource.members)
+ set_members
+ end
+ end
+
+ def dscl_create_group
+ safe_dscl("create /Groups/#{@new_resource.group_name}")
+ safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'")
+ end
+
+ def remove_group
+ safe_dscl("delete /Groups/#{@new_resource.group_name}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
new file mode 100644
index 0000000000..7fb27a7777
--- /dev/null
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -0,0 +1,65 @@
+#
+# Author:: AJ Christensen (<aj@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/provider/group/groupadd'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Group
+ class Gpasswd < Chef::Provider::Group::Groupadd
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/gpasswd") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+
+ def modify_group_members
+ if(@new_resource.append)
+ unless @new_resource.members.empty?
+ @new_resource.members.each do |member|
+ Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
+ shell_out!("gpasswd -a #{member} #{@new_resource.group_name}")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add")
+ end
+ else
+ unless @new_resource.members.empty?
+ Chef::Log.debug("#{@new_resource} setting group members to #{@new_resource.members.join(', ')}")
+ shell_out!("gpasswd -M #{@new_resource.members.join(',')} #{@new_resource.group_name}")
+ else
+ Chef::Log.debug("#{@new_resource} setting group members to: none")
+ shell_out!("gpasswd -M \"\" #{@new_resource.group_name}")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb
new file mode 100644
index 0000000000..544fee4304
--- /dev/null
+++ b/lib/chef/provider/group/groupadd.rb
@@ -0,0 +1,96 @@
+#
+# Author:: AJ Christensen (<aj@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
+ class Provider
+ class Group
+ class Groupadd < Chef::Provider::Group
+
+ def required_binaries
+ [ "/usr/sbin/groupadd",
+ "/usr/sbin/groupmod",
+ "/usr/sbin/groupdel" ]
+ end
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+ required_binaries.each do |required_binary|
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?(required_binary) }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+ end
+
+ # Create the group
+ def create_group
+ command = "groupadd"
+ command << set_options
+ command << groupadd_options
+ run_command(:command => command)
+ modify_group_members
+ end
+
+ # Manage the group when it already exists
+ def manage_group
+ command = "groupmod"
+ command << set_options
+ run_command(:command => command)
+ modify_group_members
+ end
+
+ # Remove the group
+ def remove_group
+ run_command(:command => "groupdel #{@new_resource.group_name}")
+ end
+
+ def modify_group_members
+ raise Chef::Exceptions::Group, "you must override modify_group_members in #{self.to_s}"
+ end
+ # Little bit of magic as per Adam's useradd provider to pull the assign the command line flags
+ #
+ # ==== Returns
+ # <string>:: A string containing the option and then the quoted value
+ def set_options
+ opts = ""
+ { :gid => "-g" }.sort { |a,b| a[0] <=> b[0] }.each do |field, option|
+ if @current_resource.send(field) != @new_resource.send(field)
+ if @new_resource.send(field)
+ opts << " #{option} '#{@new_resource.send(field)}'"
+ Chef::Log.debug("#{@new_resource} set #{field.to_s} to #{@new_resource.send(field)}")
+ end
+ end
+ end
+ opts << " #{@new_resource.group_name}"
+ end
+
+ def groupadd_options
+ opts = ''
+ opts << " -r" if @new_resource.system
+ opts
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
new file mode 100644
index 0000000000..10fc680d78
--- /dev/null
+++ b/lib/chef/provider/group/groupmod.rb
@@ -0,0 +1,120 @@
+#
+# Author:: Dan Crosta (<dcrosta@late.am>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Group
+ class Groupmod < Chef::Provider::Group
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ super
+ [ "group", "user" ].each do |binary|
+ raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{@new_resource}" unless ::File.exists?("/usr/sbin/#{binary}")
+ end
+ end
+
+ # Create the group
+ def create_group
+ command = "group add"
+ command << set_options
+ shell_out!(command)
+
+ add_group_members(@new_resource.members)
+ end
+
+ # Manage the group when it already exists
+ def manage_group
+ if @new_resource.append
+ to_add = @new_resource.members.dup
+ to_add.reject! { |user| @current_resource.members.include?(user) }
+
+ to_delete = Array.new
+
+ Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add") if to_add.empty?
+ else
+ to_add = @new_resource.members.dup
+ to_add.reject! { |user| @current_resource.members.include?(user) }
+
+ to_delete = @current_resource.members.dup
+ to_delete.reject! { |user| @new_resource.members.include?(user) }
+
+ Chef::Log.debug("#{@new_resource} setting group members to: none") if @new_resource.members.empty?
+ end
+
+ if to_delete.empty?
+ # If we are only adding new members to this group, then
+ # call add_group_members with only those users
+ add_group_members(to_add)
+ else
+ Chef::Log.debug("#{@new_resource} removing members #{to_delete.join(', ')}")
+
+ # This is tricky, but works: rename the existing group to
+ # "<name>_bak", create a new group with the same GID and
+ # "<name>", then set correct members on that group
+ rename = "group mod -n #{@new_resource.group_name}_bak #{@new_resource.group_name}"
+ shell_out!(rename)
+
+ create = "group add"
+ create << set_options(:overwrite_gid => true)
+ shell_out!(create)
+
+ # Ignore to_add here, since we're replacing the group we
+ # have to add all members who should be in the group.
+ add_group_members(@new_resource.members)
+
+ remove = "group del #{@new_resource.group_name}_bak"
+ shell_out!(remove)
+ end
+ end
+
+ # Remove the group
+ def remove_group
+ shell_out!("group del #{@new_resource.group_name}")
+ end
+
+ # Adds a list of usernames to the group using `user mod`
+ def add_group_members(members)
+ Chef::Log.debug("#{@new_resource} adding members #{members.join(', ')}") if !members.empty?
+ members.each do |user|
+ shell_out!("user mod -G #{@new_resource.group_name} #{user}")
+ end
+ end
+
+ # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
+ #
+ # ==== Returns
+ # <string>:: A string containing the option and then the quoted value
+ def set_options(overwrite_gid=false)
+ opts = ""
+ if overwrite_gid || @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+ opts << " -g '#{@new_resource.gid}'"
+ end
+ if overwrite_gid
+ opts << " -o"
+ end
+ opts << " #{@new_resource.group_name}"
+ opts
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
new file mode 100644
index 0000000000..3bf67a515a
--- /dev/null
+++ b/lib/chef/provider/group/pw.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Provider
+ class Group
+ class Pw < Chef::Provider::Group
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/sbin/pw") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+
+ # Create the group
+ def create_group
+ command = "pw groupadd"
+ command << set_options
+ command << set_members_option
+ run_command(:command => command)
+ end
+
+ # Manage the group when it already exists
+ def manage_group
+ command = "pw groupmod"
+ command << set_options
+ command << set_members_option
+ run_command(:command => command)
+ end
+
+ # Remove the group
+ def remove_group
+ run_command(:command => "pw groupdel #{@new_resource.group_name}")
+ end
+
+ # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
+ #
+ # ==== Returns
+ # <string>:: A string containing the option and then the quoted value
+ def set_options
+ opts = " #{@new_resource.group_name}"
+ if @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+ Chef::Log.debug("#{@new_resource}: current gid (#{@current_resource.gid}) doesnt match target gid (#{@new_resource.gid}), changing it")
+ opts << " -g '#{@new_resource.gid}'"
+ end
+ opts
+ end
+
+ # Set the membership option depending on the current resource states
+ def set_members_option
+ opt = ""
+ unless @new_resource.members.empty?
+ opt << " -M #{@new_resource.members.join(',')}"
+ Chef::Log.debug("#{@new_resource} setting group members to #{@new_resource.members.join(', ')}")
+ else
+ # New member list is empty so we should delete any old group members
+ unless @current_resource.members.empty?
+ opt << " -d #{@current_resource.members.join(',')}"
+ Chef::Log.debug("#{@new_resource} removing group members #{@current_resource.members.join(', ')}")
+ else
+ Chef::Log.debug("#{@new_resource} not changing group members, the group has no members")
+ end
+ end
+ opt
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
new file mode 100644
index 0000000000..0b66c1f912
--- /dev/null
+++ b/lib/chef/provider/group/suse.rb
@@ -0,0 +1,60 @@
+#
+# Author:: AJ Christensen (<aj@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/provider/group/groupadd'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Group
+ class Suse < Chef::Provider::Group::Groupadd
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/sbin/groupmod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+ end
+
+ def modify_group_members
+ unless @new_resource.members.empty?
+ if(@new_resource.append)
+ @new_resource.members.each do |member|
+ Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
+ shell_out!("groupmod -A #{member} #{@new_resource.group_name}")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} setting group members to #{@new_resource.members.join(', ')}")
+ shell_out!("groupmod -A #{@new_resource.members.join(',')} #{@new_resource.group_name}")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} not changing group members, the group has no members")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
new file mode 100644
index 0000000000..f0a9282831
--- /dev/null
+++ b/lib/chef/provider/group/usermod.rb
@@ -0,0 +1,68 @@
+#
+# Author:: AJ Christensen (<aj@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/provider/group/groupadd'
+
+class Chef
+ class Provider
+ class Group
+ class Usermod < Chef::Provider::Group::Groupadd
+
+ def load_current_resource
+ super
+ end
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/sbin/usermod") }
+ a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}"
+ # No whyrun alternative: this component should be available in the base install of any given system that uses it
+ end
+
+ requirements.assert(:modify, :create) do |a|
+ a.assertion { @new_resource.members.empty? || @new_resource.append }
+ a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}, must set append true in group"
+ # No whyrun alternative - this action is simply not supported.
+ end
+ end
+
+ def modify_group_members
+ case node[:platform]
+ when "openbsd", "netbsd", "aix", "solaris2"
+ append_flags = "-G"
+ when "solaris"
+ append_flags = "-a -G"
+ end
+
+ unless @new_resource.members.empty?
+ if(@new_resource.append)
+ @new_resource.members.each do |member|
+ Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
+ run_command(:command => "usermod #{append_flags} #{@new_resource.group_name} #{member}" )
+ end
+ end
+ else
+ Chef::Log.debug("#{@new_resource} not changing group members, the group has no members")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
new file mode 100644
index 0000000000..88280408cd
--- /dev/null
+++ b/lib/chef/provider/group/windows.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/user'
+if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/util/windows/net_group'
+end
+
+class Chef
+ class Provider
+ class Group
+ class Windows < Chef::Provider::Group
+
+ def initialize(new_resource,run_context)
+ super
+ @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.name)
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Group.new(@new_resource.name)
+ @current_resource.group_name(@new_resource.group_name)
+
+ members = nil
+ begin
+ members = @net_group.local_get_members
+ rescue => e
+ @group_exists = false
+ Chef::Log.debug("#{@new_resource} group does not exist")
+ end
+
+ if members
+ @current_resource.members(members)
+ end
+
+ @current_resource
+ end
+
+ def create_group
+ @net_group.local_add
+ manage_group
+ end
+
+ def manage_group
+ if @new_resource.append
+ begin
+ #ERROR_MEMBER_IN_ALIAS if a member already exists in the group
+ @net_group.local_add_members(@new_resource.members)
+ rescue
+ members = @new_resource.members + @current_resource.members
+ @net_group.local_set_members(members.uniq)
+ end
+ else
+ @net_group.local_set_members(@new_resource.members)
+ end
+ end
+
+ def remove_group
+ @net_group.local_delete
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb
new file mode 100644
index 0000000000..0ea5f8289f
--- /dev/null
+++ b/lib/chef/provider/http_request.rb
@@ -0,0 +1,136 @@
+#
+# 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'
+
+class Chef
+ class Provider
+ class HttpRequest < Chef::Provider
+
+ attr_accessor :rest
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @rest = Chef::REST.new(@new_resource.url, nil, nil)
+ end
+
+ # Send a HEAD request to @new_resource.url, with ?message=@new_resource.message
+ def action_head
+ message = check_message(@new_resource.message)
+ # returns true from Chef::REST if returns 2XX (Net::HTTPSuccess)
+ modified = @rest.run_request(
+ :HEAD,
+ @rest.create_url("#{@new_resource.url}?message=#{message}"),
+ @new_resource.headers,
+ false,
+ 10,
+ false
+ )
+ Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful")
+ Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}")
+ # :head is usually used to trigger notifications, which converge_by now does
+ if modified
+ converge_by("#{@new_resource} HEAD to #{@new_resource.url} returned modified, trigger notifications") {}
+ end
+ end
+
+ # Send a GET request to @new_resource.url, with ?message=@new_resource.message
+ def action_get
+ converge_by("#{@new_resource} GET to #{@new_resource.url}") do
+
+ message = check_message(@new_resource.message)
+ body = @rest.run_request(
+ :GET,
+ @rest.create_url("#{@new_resource.url}?message=#{message}"),
+ @new_resource.headers,
+ false,
+ 10,
+ false
+ )
+ Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
+ Chef::Log.debug("#{@new_resource} GET request response: #{body}")
+ end
+ end
+
+ # Send a PUT request to @new_resource.url, with the message as the payload
+ def action_put
+ converge_by("#{@new_resource} PUT to #{@new_resource.url}") do
+ message = check_message(@new_resource.message)
+ body = @rest.run_request(
+ :PUT,
+ @rest.create_url("#{@new_resource.url}"),
+ @new_resource.headers,
+ message,
+ 10,
+ false
+ )
+ Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful")
+ Chef::Log.debug("#{@new_resource} PUT request response: #{body}")
+ end
+ end
+
+ # Send a POST request to @new_resource.url, with the message as the payload
+ def action_post
+ converge_by("#{@new_resource} POST to #{@new_resource.url}") do
+ message = check_message(@new_resource.message)
+ body = @rest.run_request(
+ :POST,
+ @rest.create_url("#{@new_resource.url}"),
+ @new_resource.headers,
+ message,
+ 10,
+ false
+ )
+ Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful")
+ Chef::Log.debug("#{@new_resource} POST request response: #{body}")
+ end
+ end
+
+ # Send a DELETE request to @new_resource.url
+ def action_delete
+ converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do
+ body = @rest.run_request(
+ :DELETE,
+ @rest.create_url("#{@new_resource.url}"),
+ @new_resource.headers,
+ false,
+ 10,
+ false
+ )
+ @new_resource.updated_by_last_action(true)
+ Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
+ Chef::Log.debug("#{@new_resource} DELETE request response: #{body}")
+ end
+ end
+
+ private
+
+ def check_message(message)
+ if message.kind_of?(Proc)
+ message.call
+ else
+ message
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
new file mode 100644
index 0000000000..86680b2229
--- /dev/null
+++ b/lib/chef/provider/ifconfig.rb
@@ -0,0 +1,214 @@
+#
+# Author:: Jason K. Jackson (jasonjackson@gmail.com)
+# Copyright:: Copyright (c) 2009 Jason K. Jackson
+# 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/mixin/command'
+require 'chef/provider'
+require 'chef/exceptions'
+require 'erb'
+
+# Recipe example:
+#
+# int = {Hash with your network settings...}
+#
+# ifconfig int['ip'] do
+# ignore_failure true
+# device int['dev']
+# mask int['mask']
+# gateway int['gateway']
+# mtu int['mtu']
+# end
+
+class Chef
+ class Provider
+ class Ifconfig < Chef::Provider
+ include Chef::Mixin::Command
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+
+ @ifconfig_success = true
+ @interfaces = {}
+
+ @status = popen4("ifconfig") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+
+ if !line[0..9].strip.empty?
+ @int_name = line[0..9].strip
+ @interfaces[@int_name] = {"hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp }
+ else
+ @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? ($1) : "nil") if line =~ /inet addr:/
+ @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? ($1) : "nil") if line =~ /Bcast:/
+ @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? ($1) : "nil") if line =~ /Mask:/
+ @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? ($1) : "nil") if line =~ /MTU:/
+ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? ($1) : "nil") if line =~ /Metric:/
+ end
+
+ if @interfaces.has_key?(@new_resource.device)
+ @interface = @interfaces.fetch(@new_resource.device)
+
+ @current_resource.target(@new_resource.target)
+ @current_resource.device(@int_name)
+ @current_resource.inet_addr(@interface["inet_addr"])
+ @current_resource.hwaddr(@interface["hwaddr"])
+ @current_resource.bcast(@interface["bcast"])
+ @current_resource.mask(@interface["mask"])
+ @current_resource.mtu(@interface["mtu"])
+ @current_resource.metric(@interface["metric"])
+ end
+ end
+ end
+ @current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @status.exitstatus == 0 }
+ a.failure_message Chef::Exceptions::Ifconfig, "ifconfig failed - #{@status.inspect}!"
+ # no whyrun - if the base ifconfig used in load_current_resource fails
+ # there's no reasonable action that could have been taken in the course of
+ # a chef run to fix it.
+ end
+ end
+
+ def action_add
+ # check to see if load_current_resource found interface in ifconfig
+ unless @current_resource.inet_addr
+ unless @new_resource.device == "lo"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command << " netmask #{@new_resource.mask}" if @new_resource.mask
+ command << " metric #{@new_resource.metric}" if @new_resource.metric
+ command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+ end
+ converge_by ("run #{command} to add #{@new_resource}") do
+ run_command(
+ :command => command
+ )
+ Chef::Log.info("#{@new_resource} added")
+ end
+ end
+
+ # Write out the config files
+ generate_config
+ end
+
+ def action_enable
+ # check to see if load_current_resource found ifconfig
+ # enables, but does not manage config files
+ unless @current_resource.inet_addr
+ unless @new_resource.device == "lo"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command << " netmask #{@new_resource.mask}" if @new_resource.mask
+ command << " metric #{@new_resource.metric}" if @new_resource.metric
+ command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+ end
+
+ converge_by ("run #{command} to enable #{@new_resource}") do
+ run_command(
+ :command => command
+ )
+ Chef::Log.info("#{@new_resource} enabled")
+ end
+ end
+ end
+
+ def action_delete
+ # check to see if load_current_resource found the interface
+ if @current_resource.device
+ command = "ifconfig #{@new_resource.device} down"
+ converge_by ("run #{command} to delete #{@new_resource}") do
+ run_command(
+ :command => command
+ )
+ delete_config
+ Chef::Log.info("#{@new_resource} deleted")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ end
+ end
+
+ def action_disable
+ # check to see if load_current_resource found the interface
+ # disables, but leaves config files in place.
+ if @current_resource.device
+ command = "ifconfig #{@new_resource.device} down"
+ converge_by ("run #{command} to disable #{@new_resource}") do
+ run_command(
+ :command => command
+ )
+ Chef::Log.info("#{@new_resource} disabled")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
+ end
+ end
+
+ def generate_config
+ b = binding
+ case node[:platform]
+ when "centos","redhat","fedora"
+ content = %{
+<% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %>
+<% if @new_resource.onboot %>ONBOOT=<%= @new_resource.onboot %><% end %>
+<% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %>
+<% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %>
+<% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %>
+<% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %>
+<% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %>
+<% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %>
+ }
+ template = ::ERB.new(content)
+ network_file_name = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ converge_by ("generate configuration file : #{network_file_name}") do
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts(template.result(b))
+ network_file.close
+ end
+ Chef::Log.info("#{@new_resource} created configuration file")
+ when "debian","ubuntu"
+ # template
+ when "slackware"
+ # template
+ end
+ end
+
+ def delete_config
+ require 'fileutils'
+ case node[:platform]
+ when "centos","redhat","fedora"
+ ifcfg_file = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ if ::File.exist?(ifcfg_file)
+ converge_by ("delete the #{ifcfg_file}") do
+ FileUtils.rm_f(ifcfg_file, :verbose => false)
+ end
+ end
+ when "debian","ubuntu"
+ # delete configs
+ when "slackware"
+ # delete configs
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb
new file mode 100644
index 0000000000..d6f333bd2f
--- /dev/null
+++ b/lib/chef/provider/link.rb
@@ -0,0 +1,130 @@
+#
+# 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/config'
+require 'chef/log'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/file_class'
+require 'chef/resource/link'
+require 'chef/provider'
+require 'chef/scan_access_control'
+
+class Chef
+ class Provider
+ class Link < Chef::Provider
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::FileClass
+
+ def negative_complement(big)
+ if big > 1073741823 # Fixnum max
+ big -= (2**32) # diminished radix wrap to negative
+ end
+ big
+ end
+
+ private :negative_complement
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Link.new(@new_resource.name)
+ @current_resource.target_file(@new_resource.target_file)
+ if file_class.symlink?(@current_resource.target_file)
+ @current_resource.link_type(:symbolic)
+ @current_resource.to(
+ canonicalize(file_class.readlink(@current_resource.target_file))
+ )
+ else
+ @current_resource.link_type(:hard)
+ if ::File.exists?(@current_resource.target_file)
+ if ::File.exists?(@new_resource.to) &&
+ file_class.stat(@current_resource.target_file).ino ==
+ file_class.stat(@new_resource.to).ino
+ @current_resource.to(canonicalize(@new_resource.to))
+ else
+ @current_resource.to("")
+ end
+ end
+ end
+ ScanAccessControl.new(@new_resource, @current_resource).set_all!
+ @current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:delete) do |a|
+ a.assertion do
+ if @current_resource.to
+ @current_resource.link_type == @new_resource.link_type and
+ (@current_resource.link_type == :symbolic or @current_resource.to != '')
+ else
+ true
+ end
+ end
+ a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type.to_s} link."
+ a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created")
+ end
+ end
+
+ def canonicalize(path)
+ Chef::Platform.windows? ? path.gsub('/', '\\') : path
+ end
+
+ def action_create
+ if @current_resource.to != canonicalize(@new_resource.to) ||
+ @current_resource.link_type != @new_resource.link_type
+ if @current_resource.to # nil if target_file does not exist
+ converge_by("unlink existing file at #{@new_resource.target_file}") do
+ ::File.unlink(@new_resource.target_file)
+ end
+ end
+ if @new_resource.link_type == :symbolic
+ converge_by("create symlink at #{@new_resource.target_file} to #{@new_resource.to}") do
+ file_class.symlink(canonicalize(@new_resource.to),@new_resource.target_file)
+ Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
+ Chef::Log.info("#{@new_resource} created")
+ end
+ elsif @new_resource.link_type == :hard
+ converge_by("create hard link at #{@new_resource.target_file} to #{@new_resource.to}") do
+ file_class.link(@new_resource.to, @new_resource.target_file)
+ Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
+ Chef::Log.info("#{@new_resource} created")
+ end
+ end
+ end
+ if @new_resource.link_type == :symbolic
+ if access_controls.requires_changes?
+ converge_by(access_controls.describe_changes) do
+ access_controls.set_all
+ end
+ end
+ end
+ end
+
+ def action_delete
+ if @current_resource.to # Exists
+ converge_by ("delete link at #{@new_resource.target_file}") do
+ ::File.delete(@new_resource.target_file)
+ Chef::Log.info("#{@new_resource} deleted")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb
new file mode 100644
index 0000000000..5d0417ebda
--- /dev/null
+++ b/lib/chef/provider/log.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Cary Penniman (<cary@rightscale.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
+
+ class Provider
+
+ class Log
+
+ # Chef log provider, allows logging to chef's logs from recipes
+ class ChefLog < Chef::Provider
+
+ # No concept of a 'current' resource for logs, this is a no-op
+ #
+ # === Return
+ # true:: Always return true
+ def load_current_resource
+ true
+ end
+
+ # Write the log to Chef's log
+ #
+ # === Return
+ # true:: Always return true
+ def action_write
+ Chef::Log.send(@new_resource.level, @new_resource.name)
+ @new_resource.updated_by_last_action(true)
+ end
+
+ end
+
+ end
+
+ end
+
+end
+
+
+
diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb
new file mode 100644
index 0000000000..d93ff69c13
--- /dev/null
+++ b/lib/chef/provider/mdadm.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/log'
+require 'chef/mixin/shell_out'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class Mdadm < Chef::Provider
+
+ include Chef::Mixin::ShellOut
+
+ def popen4
+ raise Exception, "deprecated"
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Mdadm.new(@new_resource.name)
+ @current_resource.raid_device(@new_resource.raid_device)
+ Chef::Log.debug("#{@new_resource} checking for software raid device #{@current_resource.raid_device}")
+
+ device_not_found = 4
+ mdadm = shell_out!("mdadm --detail --test #{@new_resource.raid_device}", :returns => [0,device_not_found])
+ exists = (mdadm.status == 0)
+ @current_resource.exists(exists)
+ end
+
+ def action_create
+ unless @current_resource.exists
+ converge_by("create RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --create #{@new_resource.raid_device} --chunk=#{@new_resource.chunk} --level #{@new_resource.level}"
+ command << " --metadata=#{@new_resource.metadata}"
+ command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap
+ command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}"
+ Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} raid device already exists, skipping create (#{@new_resource.raid_device})")
+ end
+ end
+
+ def action_assemble
+ unless @current_resource.exists
+ converge_by("assemble RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}"
+ Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} raid device already exists, skipping assemble (#{@new_resource.raid_device})")
+ end
+ end
+
+ def action_stop
+ if @current_resource.exists
+ converge_by("stop RAID device #{new_resource.raid_device}") do
+ command = "yes | mdadm --stop #{@new_resource.raid_device}"
+ Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+ shell_out!(command)
+ Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} raid device doesn't exist (#{@new_resource.raid_device}) - not stopping")
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
new file mode 100644
index 0000000000..fe41997d39
--- /dev/null
+++ b/lib/chef/provider/mount.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/log'
+require 'chef/mixin/command'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class Mount < Chef::Provider
+
+ include Chef::Mixin::Command
+
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ true
+ end
+
+ def action_mount
+ unless @current_resource.mounted
+ converge_by("mount #{@current_resource.device} to #{@current_resource.mount_point}") do
+ status = mount_fs()
+ if status
+ Chef::Log.info("#{@new_resource} mounted")
+ end
+ end
+ else
+ Chef::Log.debug("#{@new_resource} is already mounted")
+ end
+ end
+
+ def action_umount
+ if @current_resource.mounted
+ converge_by("unmount #{@current_resource.device}") do
+ status = umount_fs()
+ if status
+ Chef::Log.info("#{@new_resource} unmounted")
+ end
+ end
+ else
+ Chef::Log.debug("#{@new_resource} is already unmounted")
+ end
+ end
+
+ def action_remount
+ unless @new_resource.supports[:remount]
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount"
+ else
+ if @current_resource.mounted
+ converge_by("remount #{@current_resource.device}") do
+ status = remount_fs()
+ if status
+ Chef::Log.info("#{@new_resource} remounted")
+ end
+ end
+ else
+ Chef::Log.debug("#{@new_resource} not mounted, nothing to remount")
+ end
+ end
+ end
+
+ def action_enable
+ unless @current_resource.enabled
+ converge_by("remount #{@current_resource.device}") do
+ status = enable_fs
+ if status
+ Chef::Log.info("#{@new_resource} enabled")
+ else
+ Chef::Log.debug("#{@new_resource} already enabled")
+ end
+ end
+ end
+ end
+
+ def action_disable
+ if @current_resource.enabled
+ converge_by("remount #{@current_resource.device}") do
+ status = disable_fs
+ if status
+ Chef::Log.info("#{@new_resource} disabled")
+ else
+ Chef::Log.debug("#{@new_resource} already disabled")
+ end
+ end
+ end
+ end
+
+ def mount_fs
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :mount"
+ end
+
+ def umount_fs
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :umount"
+ end
+
+ def remount_fs
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount"
+ end
+
+ def enable_fs
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+ end
+
+ def disable_fs
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
new file mode 100644
index 0000000000..9a85a9058a
--- /dev/null
+++ b/lib/chef/provider/mount/mount.rb
@@ -0,0 +1,252 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/mount'
+require 'chef/log'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Mount
+ class Mount < Chef::Provider::Mount
+ include Chef::Mixin::ShellOut
+
+ def initialize(new_resource, run_context)
+ super
+ @real_device = nil
+ end
+ attr_accessor :real_device
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Mount.new(@new_resource.name)
+ @current_resource.mount_point(@new_resource.mount_point)
+ @current_resource.device(@new_resource.device)
+ mounted?
+ enabled?
+ end
+
+ def mountable?
+ # only check for existence of non-remote devices
+ if (device_should_exist? && !::File.exists?(device_real) )
+ raise Chef::Exceptions::Mount, "Device #{@new_resource.device} does not exist"
+ elsif( !::File.exists?(@new_resource.mount_point) )
+ raise Chef::Exceptions::Mount, "Mount point #{@new_resource.mount_point} does not exist"
+ end
+ return true
+ end
+
+ def enabled?
+ # Check to see if there is a entry in /etc/fstab. Last entry for a volume wins.
+ enabled = false
+ ::File.foreach("/etc/fstab") do |line|
+ case line
+ when /^[#\s]/
+ next
+ when /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/
+ enabled = true
+ @current_resource.fstype($1)
+ @current_resource.options($2)
+ @current_resource.dump($3.to_i)
+ @current_resource.pass($4.to_i)
+ Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab")
+ next
+ when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
+ enabled = false
+ Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+ end
+ end
+ @current_resource.enabled(enabled)
+ end
+
+ def mounted?
+ mounted = false
+ shell_out!("mount").stdout.each_line do |line|
+ case line
+ when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(@new_resource.mount_point)}/
+ mounted = true
+ Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
+ when /^([\/\w])+\son\s#{Regexp.escape(@new_resource.mount_point)}\s+/
+ mounted = false
+ Chef::Log.debug("Special device #{$~[1]} mounted as #{@new_resource.mount_point}")
+ end
+ end
+ @current_resource.mounted(mounted)
+ end
+
+ def mount_fs
+ unless @current_resource.mounted
+ mountable?
+ command = "mount -t #{@new_resource.fstype}"
+ command << " -o #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
+ command << case @new_resource.device_type
+ when :device
+ " #{device_real}"
+ when :label
+ " -L #{@new_resource.device}"
+ when :uuid
+ " -U #{@new_resource.device}"
+ end
+ command << " #{@new_resource.mount_point}"
+ shell_out!(command)
+ Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ else
+ Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ end
+ end
+
+ def umount_fs
+ if @current_resource.mounted
+ shell_out!("umount #{@new_resource.mount_point}")
+ Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ else
+ Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ end
+ end
+
+ def remount_fs
+ if @current_resource.mounted and @new_resource.supports[:remount]
+ shell_out!("mount -o remount #{@new_resource.mount_point}")
+ @new_resource.updated_by_last_action(true)
+ Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}")
+ elsif @current_resource.mounted
+ umount_fs
+ sleep 1
+ mount_fs
+ else
+ Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do")
+ end
+ end
+
+ def enable_fs
+ if @current_resource.enabled && mount_options_unchanged?
+ Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+ return nil
+ end
+
+ if @current_resource.enabled
+ # The current options don't match what we have, so
+ # disable, then enable.
+ disable_fs
+ end
+ ::File.open("/etc/fstab", "a") do |fstab|
+ fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}")
+ Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+ end
+ end
+
+ def disable_fs
+ if @current_resource.enabled
+ contents = []
+
+ found = false
+ ::File.readlines("/etc/fstab").reverse_each do |line|
+ if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}/
+ found = true
+ Chef::Log.debug("#{@new_resource} is removed from fstab")
+ next
+ else
+ contents << line
+ end
+ end
+
+ ::File.open("/etc/fstab", "w") do |fstab|
+ contents.reverse_each { |line| fstab.puts line}
+ end
+ else
+ Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
+ end
+ end
+
+ def network_device?
+ @new_resource.device =~ /:/ || @new_resource.device =~ /\/\//
+ end
+
+ def device_should_exist?
+ ( not network_device? ) &&
+ ( not %w[ tmpfs fuse ].include? @new_resource.fstype )
+ end
+
+ private
+
+ def device_fstab
+ case @new_resource.device_type
+ when :device
+ @new_resource.device
+ when :label
+ "LABEL=#{@new_resource.device}"
+ when :uuid
+ "UUID=#{@new_resource.device}"
+ end
+ end
+
+ def device_real
+ if @real_device == nil
+ if @new_resource.device_type == :device
+ @real_device = @new_resource.device
+ else
+ @real_device = ""
+ status = popen4("/sbin/findfs #{device_fstab}") do |pid, stdin, stdout, stderr|
+ device_line = stdout.first # stdout.first consumes
+ @real_device = device_line.chomp unless device_line.nil?
+ end
+ end
+ end
+ @real_device
+ end
+
+ def device_logstring
+ case @new_resource.device_type
+ when :device
+ "#{device_real}"
+ when :label
+ "#{device_real} with label #{@new_resource.device}"
+ when :uuid
+ "#{device_real} with uuid #{@new_resource.device}"
+ end
+ end
+
+ def device_mount_regex
+ if network_device?
+ # ignore trailing slash
+ Regexp.escape(device_real)+"/?"
+ elsif ::File.symlink?(device_real)
+ "(?:#{Regexp.escape(device_real)})|(?:#{Regexp.escape(::File.readlink(device_real))})"
+ else
+ Regexp.escape(device_real)
+ end
+ end
+
+ def device_fstab_regex
+ if @new_resource.device_type == :device
+ device_mount_regex
+ else
+ device_fstab
+ end
+ end
+
+ def mount_options_unchanged?
+ @current_resource.fstype == @new_resource.fstype and
+ @current_resource.options == @new_resource.options and
+ @current_resource.dump == @new_resource.dump and
+ @current_resource.pass == @new_resource.pass
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
new file mode 100644
index 0000000000..dced0d3596
--- /dev/null
+++ b/lib/chef/provider/mount/windows.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/mount'
+if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/util/windows/net_use'
+ require 'chef/util/windows/volume'
+end
+
+class Chef
+ class Provider
+ class Mount
+ class Windows < Chef::Provider::Mount
+
+ def is_volume(name)
+ name =~ /^\\\\\?\\Volume\{[\w-]+\}\\$/ ? true : false
+ end
+
+ def initialize(new_resource, run_context)
+ super
+ @mount = nil
+ end
+
+ def load_current_resource
+ if is_volume(@new_resource.device)
+ @mount = Chef::Util::Windows::Volume.new(@new_resource.name)
+ else #assume network drive
+ @mount = Chef::Util::Windows::NetUse.new(@new_resource.name)
+ end
+
+ @current_resource = Chef::Resource::Mount.new(@new_resource.name)
+ @current_resource.mount_point(@new_resource.mount_point)
+ Chef::Log.debug("Checking for mount point #{@current_resource.mount_point}")
+
+ begin
+ @current_resource.device(@mount.device)
+ Chef::Log.debug("#{@current_resource.device} mounted on #{@new_resource.mount_point}")
+ @current_resource.mounted(true)
+ rescue ArgumentError => e
+ @current_resource.mounted(false)
+ Chef::Log.debug("#{@new_resource.mount_point} is not mounted: #{e.message}")
+ end
+ end
+
+ def mount_fs
+ unless @current_resource.mounted
+ @mount.add(@new_resource.device)
+ Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+ else
+ Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+ end
+ end
+
+ def umount_fs
+ if @current_resource.mounted
+ @mount.delete
+ Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}")
+ else
+ Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
new file mode 100644
index 0000000000..c686f67450
--- /dev/null
+++ b/lib/chef/provider/ohai.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Michael Leianrtas (<mleinartas@gmail.com>)
+# Copyright:: Copyright (c) 2010 Michael Leinartas
+# 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 'ohai'
+
+class Chef
+ class Provider
+ class Ohai < Chef::Provider
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ true
+ end
+
+ def action_reload
+ converge_by("re-run ohai and merge results into node attributes") do
+ ohai = ::Ohai::System.new
+ if @new_resource.plugin
+ ohai.require_plugin @new_resource.plugin
+ else
+ ohai.all_plugins
+ end
+ node.automatic_attrs.merge! ohai.data
+ Chef::Log.info("#{@new_resource} reloaded")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
new file mode 100644
index 0000000000..a28a6f93fb
--- /dev/null
+++ b/lib/chef/provider/package.rb
@@ -0,0 +1,229 @@
+#
+# 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/mixin/command'
+require 'chef/log'
+require 'chef/file_cache'
+require 'chef/platform'
+
+class Chef
+ class Provider
+ class Package < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ attr_accessor :candidate_version
+ def initialize(new_resource, run_context)
+ super
+ @candidate_version = nil
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:install) do |a|
+ a.assertion { ((@new_resource.version != nil) && !(target_version_already_installed?)) \
+ || !(@current_resource.version.nil? && candidate_version.nil?) }
+ a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{@new_resource.package_name}")
+ a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+ end
+
+ requirements.assert(:upgrade) do |a|
+ # Can't upgrade what we don't have
+ a.assertion { !(@current_resource.version.nil? && candidate_version.nil?) }
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{@new_resource.package_name}")
+ a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+ end
+ end
+
+ def action_install
+ # If we specified a version, and it's not the current version, move to the specified version
+ if !@new_resource.version.nil? && !(target_version_already_installed?)
+ install_version = @new_resource.version
+ # If it's not installed at all, install it
+ elsif @current_resource.version.nil?
+ install_version = candidate_version
+ else
+ Chef::Log.debug("#{@new_resource} is already installed - nothing to do")
+ return
+ end
+
+ # We need to make sure we handle the preseed file
+ if @new_resource.response_file
+ if preseed_file = get_preseed_file(@new_resource.package_name, install_version)
+ converge_by("preseed package #{@new_resource.package_name}") do
+ preseed_package(preseed_file)
+ end
+ end
+ end
+ description = install_version ? "version #{install_version} of" : ""
+ converge_by("install #{description} package #{@new_resource.package_name}") do
+ @new_resource.version(install_version)
+ install_package(@new_resource.package_name, install_version)
+ end
+ end
+
+ def action_upgrade
+ if candidate_version.nil?
+ Chef::Log.debug("#{@new_resource} no candidate version - nothing to do")
+ elsif @current_resource.version == candidate_version
+ Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
+ else
+ @new_resource.version(candidate_version)
+ orig_version = @current_resource.version || "uninstalled"
+ converge_by("upgrade package #{@new_resource.package_name} from #{orig_version} to #{candidate_version}") do
+ status = upgrade_package(@new_resource.package_name, candidate_version)
+ Chef::Log.info("#{@new_resource} upgraded from #{orig_version} to #{candidate_version}")
+ end
+ end
+ end
+
+ def action_remove
+ if removing_package?
+ description = @new_resource.version ? "version #{@new_resource.version} of " : ""
+ converge_by("remove #{description} package #{@current_resource.package_name}") do
+ remove_package(@current_resource.package_name, @new_resource.version)
+ Chef::Log.info("#{@new_resource} removed")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} package does not exist - nothing to do")
+ end
+ end
+
+ def removing_package?
+ if @current_resource.version.nil?
+ false # nothing to remove
+ elsif @new_resource.version.nil?
+ true # remove any version of a package
+ elsif @new_resource.version == @current_resource.version
+ true # remove the version we have
+ else
+ false # we don't have the version we want to remove
+ end
+ end
+
+ def action_purge
+ if removing_package?
+ description = @new_resource.version ? "version #{@new_resource.version} of" : ""
+ converge_by("purge #{description} package #{@current_resource.package_name}") do
+ purge_package(@current_resource.package_name, @new_resource.version)
+ Chef::Log.info("#{@new_resource} purged")
+ end
+ end
+ end
+
+ def action_reconfig
+ if @current_resource.version == nil then
+ Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
+ return
+ end
+
+ unless @new_resource.response_file then
+ Chef::Log.debug("#{@new_resource} no response_file provided - nothing to do")
+ return
+ end
+
+ if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version)
+ converge_by("reconfigure package #{@new_resource.package_name}") do
+ preseed_package(preseed_file)
+ status = reconfig_package(@new_resource.package_name, @current_resource.version)
+ Chef::Log.info("#{@new_resource} reconfigured")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} preseeding has not changed - nothing to do")
+ end
+ end
+
+ def install_package(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install"
+ end
+
+ def upgrade_package(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :upgrade"
+ end
+
+ def remove_package(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remove"
+ end
+
+ def purge_package(name, version)
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :purge"
+ end
+
+ def preseed_package(file)
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions"
+ end
+
+ def reconfig_package(name, version)
+ raise( Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reconfig" )
+ end
+
+ def get_preseed_file(name, version)
+ resource = preseed_resource(name, version)
+ resource.run_action(:create)
+ Chef::Log.debug("#{@new_resource} fetched preseed file to #{resource.path}")
+
+ if resource.updated_by_last_action?
+ resource.path
+ else
+ false
+ end
+ end
+
+ def preseed_resource(name, version)
+ # A directory in our cache to store this cookbook's preseed files in
+ file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{@new_resource.cookbook_name}")
+ # The full path where the preseed file will be stored
+ cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed"
+
+ Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}")
+
+ begin
+ remote_file = Chef::Resource::Template.new(cache_seed_to, run_context)
+ remote_file.cookbook_name = @new_resource.cookbook_name
+ remote_file.source(@new_resource.response_file)
+ remote_file.backup(false)
+ provider = Chef::Platform.provider_for_resource(remote_file, :create)
+ provider.template_location
+ rescue
+ Chef::Log.debug("#{@new_resource} fetching preseed file via Template resource failed, fallback to CookbookFile resource")
+ remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context)
+ remote_file.cookbook_name = @new_resource.cookbook_name
+ remote_file.source(@new_resource.response_file)
+ remote_file.backup(false)
+ end
+
+ remote_file
+ end
+
+ def expand_options(options)
+ options ? " #{options}" : ""
+ end
+
+ def target_version_already_installed?
+ @new_resource.version == @current_resource.version
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
new file mode 100644
index 0000000000..e8939b494e
--- /dev/null
+++ b/lib/chef/provider/package/apt.rb
@@ -0,0 +1,147 @@
+#
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+
+class Chef
+ class Provider
+ class Package
+ class Apt < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+ attr_accessor :is_virtual_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ check_package_state(@new_resource.package_name)
+ @current_resource
+ end
+
+ def default_release_options
+ # Use apt::Default-Release option only if provider was explicitly defined
+ "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.provider && @new_resource.default_release
+ end
+
+ def check_package_state(package)
+ Chef::Log.debug("#{@new_resource} checking package status for #{package}")
+ installed = false
+
+ shell_out!("apt-cache#{expand_options(default_release_options)} policy #{package}").stdout.each_line do |line|
+ case line
+ when /^\s{2}Installed: (.+)$/
+ installed_version = $1
+ if installed_version == '(none)'
+ Chef::Log.debug("#{@new_resource} current version is nil")
+ @current_resource.version(nil)
+ else
+ Chef::Log.debug("#{@new_resource} current version is #{installed_version}")
+ @current_resource.version(installed_version)
+ installed = true
+ end
+ when /^\s{2}Candidate: (.+)$/
+ candidate_version = $1
+ if candidate_version == '(none)'
+ # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
+ @is_virtual_package = true
+ showpkg = shell_out!("apt-cache showpkg #{package}").stdout
+ providers = Hash.new
+ showpkg.rpartition(/Reverse Provides:? #{$/}/)[2].each_line do |line|
+ provider, version = line.split
+ providers[provider] = version
+ end
+ # Check if the package providing this virtual package is installed
+ num_providers = providers.length
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} has no candidate in the apt-cache" if num_providers == 0
+ # apt will only install a virtual package if there is a single providing package
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} is a virtual package provided by #{num_providers} packages, you must explicitly select one to install" if num_providers > 1
+ # Check if the package providing this virtual package is installed
+ Chef::Log.info("#{@new_resource} is a virtual package, actually acting on package[#{providers.keys.first}]")
+ installed = check_package_state(providers.keys.first)
+ else
+ Chef::Log.debug("#{@new_resource} candidate version is #{$1}")
+ @candidate_version = $1
+ end
+ end
+ end
+
+ return installed
+ end
+
+ def install_package(name, version)
+ package_name = "#{name}=#{version}"
+ package_name = name if @is_virtual_package
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ package_name = "#{name}"
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def purge_package(name, version)
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def preseed_package(preseed_file)
+ Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
+ run_command_with_systems_locale(
+ :command => "debconf-set-selections #{preseed_file}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def reconfig_package(name, version)
+ Chef::Log.info("#{@new_resource} reconfiguring")
+ run_command_with_systems_locale(
+ :command => "dpkg-reconfigure #{name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
new file mode 100644
index 0000000000..795a7b308b
--- /dev/null
+++ b/lib/chef/provider/package/dpkg.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Dpkg < Chef::Provider::Package::Apt
+ DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~-]+)/
+ DPKG_INSTALLED = /^Status: install ok installed/
+ DPKG_VERSION = /^Version: (.+)$/
+
+ include Chef::Mixin::GetSourceFromPackage
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion{ not @new_resource.source.nil? }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ end
+
+ # TODO this was originally written for any action in which .source is provided
+ # but would it make more sense to only look at source if the action is :install?
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @source_exists }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "Assuming it would have been previously downloaded."
+ end
+ end
+
+ def load_current_resource
+ @source_exists = true
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ @source_exists = ::File.exists?(@new_resource.source)
+ if @source_exists
+ # Get information from the package if supplied
+ Chef::Log.debug("#{@new_resource} checking dpkg status")
+ status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ if pkginfo = DPKG_INFO.match(line)
+ @current_resource.package_name(pkginfo[1])
+ @new_resource.version(pkginfo[2])
+ end
+ end
+ end
+ else
+ # Source provided but not valid means we can't safely do further processing
+ return
+ end
+
+ end
+
+ # Check to see if it is installed
+ package_installed = nil
+ Chef::Log.debug("#{@new_resource} checking install state")
+ status = popen4("dpkg -s #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ case line
+ when DPKG_INSTALLED
+ package_installed = true
+ when DPKG_VERSION
+ if package_installed
+ Chef::Log.debug("#{@new_resource} current version is #{$1}")
+ @current_resource.version($1)
+ end
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def remove_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def purge_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
new file mode 100644
index 0000000000..6c9dacc55d
--- /dev/null
+++ b/lib/chef/provider/package/easy_install.rb
@@ -0,0 +1,136 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Package
+ class EasyInstall < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+
+ def install_check(name)
+ check = false
+
+ begin
+ # first check to see if we can import it
+ output = shell_out!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
+ if output.include? "ImportError"
+ # then check to see if its on the path
+ output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ if output.downcase.include? "#{name.downcase}"
+ check = true
+ end
+ else
+ check = true
+ end
+ rescue
+ # it's probably not installed
+ end
+
+ check
+ end
+
+ def easy_install_binary_path
+ path = @new_resource.easy_install_binary
+ path ? path : 'easy_install'
+ end
+
+ def python_binary_path
+ path = @new_resource.python_binary
+ path ? path : 'python'
+ end
+
+ def module_name
+ m = @new_resource.module_name
+ m ? m : @new_resource.name
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @current_resource.version(nil)
+
+ # get the currently installed version if installed
+ package_version = nil
+ if install_check(module_name)
+ begin
+ output = shell_out!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
+ package_version = output.strip
+ rescue
+ output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+
+ output_array = output.gsub(/[\[\]]/,'').split(/\s*,\s*/)
+ package_path = ""
+
+ output_array.each do |entry|
+ if entry.downcase.include?(@new_resource.package_name)
+ package_path = entry
+ end
+ end
+
+ package_path[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
+ package_version = $3
+ end
+ end
+
+ if package_version == @new_resource.version
+ Chef::Log.debug("#{@new_resource} at version #{@new_resource.version}")
+ @current_resource.version(@new_resource.version)
+ else
+ Chef::Log.debug("#{@new_resource} at version #{package_version}")
+ @current_resource.version(package_version)
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ # do a dry run to get the latest version
+ result = shell_out!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
+ @candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
+ @candidate_version
+ end
+
+ def install_package(name, version)
+ run_command(:command => "#{easy_install_binary_path}#{expand_options(@new_resource.options)} \"#{name}==#{version}\"")
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command(:command => "#{easy_install_binary_path }#{expand_options(@new_resource.options)} -m #{name}")
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/freebsd.rb b/lib/chef/provider/package/freebsd.rb
new file mode 100644
index 0000000000..afdd0d812e
--- /dev/null
+++ b/lib/chef/provider/package/freebsd.rb
@@ -0,0 +1,149 @@
+#
+# Authors:: Bryan McLellan (btm@loftninjas.org)
+# Matthew Landauer (matthew@openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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/provider/package'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Freebsd < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(*args)
+ super
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ end
+
+ def current_installed_version
+ pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
+ pkg_info.stdout[/^#{package_name}-(.+)/, 1]
+ end
+
+ def port_path
+ case @new_resource.package_name
+ # When the package name starts with a '/' treat it as the full path to the ports directory
+ when /^\//
+ @new_resource.package_name
+ # Otherwise if the package name contains a '/' not at the start (like 'www/wordpress') treat as a relative
+ # path from /usr/ports
+ when /\//
+ "/usr/ports/#{@new_resource.package_name}"
+ # Otherwise look up the path to the ports directory using 'whereis'
+ else
+ whereis = shell_out!("whereis -s #{@new_resource.package_name}", :env => nil)
+ unless path = whereis.stdout[/^#{@new_resource.package_name}:\s+(.+)$/, 1]
+ raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}"
+ end
+ path
+ end
+ end
+
+ def ports_makefile_variable_value(variable)
+ make_v = shell_out!("make -V #{variable}", :cwd => port_path, :env => nil, :returns => [0,1])
+ make_v.stdout.strip.split($\).first # $\ is the line separator, i.e., newline
+ end
+
+ def ports_candidate_version
+ ports_makefile_variable_value("PORTVERSION")
+ end
+
+ def file_candidate_version_path
+ Dir["#{@new_resource.source}/#{@current_resource.package_name}*"][-1].to_s
+ end
+
+ def file_candidate_version
+ file_candidate_version_path.split(/-/).last.split(/.tbz/).first
+ end
+
+ def load_current_resource
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(current_installed_version)
+ Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+
+ case @new_resource.source
+ when /^http/, /^ftp/
+ @candidate_version = "0.0.0"
+ when /^\//
+ @candidate_version = file_candidate_version
+ else
+ @candidate_version = ports_candidate_version
+ end
+
+ Chef::Log.debug("#{@new_resource} ports candidate version is #{@candidate_version}") if @candidate_version
+
+ @current_resource
+ end
+
+ def latest_link_name
+ ports_makefile_variable_value("LATEST_LINK")
+ end
+
+ # The name of the package (without the version number) as understood by pkg_add and pkg_info
+ def package_name
+ if ::File.exist?("/usr/ports/Makefile")
+ if ports_makefile_variable_value("PKGNAME") =~ /^(.+)-[^-]+$/
+ $1
+ else
+ raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile"
+ end
+ else
+ @new_resource.package_name
+ end
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version
+ case @new_resource.source
+ when /^ports$/
+ shell_out!("make -DBATCH install", :timeout => 1200, :env => nil, :cwd => port_path).status
+ when /^http/, /^ftp/
+ if @new_resource.source =~ /\/$/
+ shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
+ else
+ shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
+ end
+ Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ when /^\//
+ shell_out!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
+ Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ else
+ shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
+ end
+ end
+ end
+
+ def remove_package(name, version)
+ # a version is mandatory
+ if version
+ shell_out!("pkg_delete #{package_name}-#{version}", :env => nil).status
+ else
+ shell_out!("pkg_delete #{package_name}-#{@current_resource.version}", :env => nil).status
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
new file mode 100644
index 0000000000..5beb46a20a
--- /dev/null
+++ b/lib/chef/provider/package/ips.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Jason J. W. Williams (<williamsjj@digitar.com>)
+# Author:: Stephen Nelson-Smith (<sns@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 'open3'
+require 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Package
+ class Ips < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+ attr_accessor :virtual
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ! @candidate_version.nil? }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found"
+ a.whyrun "Assuming package #{@new_resource.package_name} would have been made available."
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.name)
+ check_package_state(@new_resource.package_name)
+ @current_resource
+ end
+
+ def check_package_state(package)
+ Chef::Log.debug("Checking package status for #{package}")
+ installed = false
+ depends = false
+
+ shell_out!("pkg info -r #{package}").stdout.each_line do |line|
+ case line
+ when /^\s+State: Installed/
+ installed = true
+ when /^\s+Version: (.*)/
+ @candidate_version = $1.split[0]
+ if installed
+ @current_resource.version($1)
+ else
+ @current_resource.version(nil)
+ end
+ end
+ end
+
+ return installed
+ end
+
+ def install_package(name, version)
+ package_name = "#{name}@#{version}"
+ normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
+ if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
+ command = normal_command.gsub('-q', '-q --accept')
+ else
+ command = normal_command
+ end
+ begin
+ run_command_with_systems_locale(:command => command)
+ rescue
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ package_name = "#{name}@#{version}"
+ run_command_with_systems_locale(
+ :command => "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}"
+ )
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
new file mode 100644
index 0000000000..fd33788944
--- /dev/null
+++ b/lib/chef/provider/package/macports.rb
@@ -0,0 +1,105 @@
+class Chef
+ class Provider
+ class Package
+ class Macports < Chef::Provider::Package
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(current_installed_version)
+ Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+
+ @candidate_version = macports_candidate_version
+
+ if !@new_resource.version and !@candidate_version
+ raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!"
+ end
+
+ Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+
+ @current_resource
+ end
+
+ def current_installed_version
+ command = "port installed #{@new_resource.package_name}"
+ output = get_response_from_command(command)
+
+ response = nil
+ output.each_line do |line|
+ match = line.match(/^.+ @([^\s]+) \(active\)$/)
+ response = match[1] if match
+ end
+ response
+ end
+
+ def macports_candidate_version
+ command = "port info --version #{@new_resource.package_name}"
+ output = get_response_from_command(command)
+
+ match = output.match(/^version: (.+)$/)
+
+ match ? match[1] : nil
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version == version
+ command = "port#{expand_options(@new_resource.options)} install #{name}"
+ command << " @#{version}" if version and !version.empty?
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+ end
+
+ def purge_package(name, version)
+ command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
+ command << " @#{version}" if version and !version.empty?
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+
+ def remove_package(name, version)
+ command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
+ command << " @#{version}" if version and !version.empty?
+
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+
+ def upgrade_package(name, version)
+ # Saving this to a variable -- weird rSpec behavior
+ # happens otherwise...
+ current_version = @current_resource.version
+
+ if current_version.nil? or current_version.empty?
+ # Macports doesn't like when you upgrade a package
+ # that hasn't been installed.
+ install_package(name, version)
+ elsif current_version != version
+ run_command_with_systems_locale(
+ :command => "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}"
+ )
+ end
+ end
+
+ private
+ def get_response_from_command(command)
+ output = nil
+ status = popen4(command) do |pid, stdin, stdout, stderr|
+ begin
+ output = stdout.read
+ rescue Exception
+ raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}"
+ end
+ end
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!"
+ end
+ output
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
new file mode 100644
index 0000000000..f81486ae84
--- /dev/null
+++ b/lib/chef/provider/package/pacman.rb
@@ -0,0 +1,111 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek@web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+
+class Chef
+ class Provider
+ class Package
+ class Pacman < Chef::Provider::Package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(nil)
+
+ Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
+ status = popen4("pacman -Qi #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ line.force_encoding(Encoding::UTF_8) if line.respond_to?(:force_encoding)
+ case line
+ when /^Version(\s?)*: (.+)$/
+ Chef::Log.debug("#{@new_resource} current version is #{$2}")
+ @current_resource.version($2)
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ repos = ["extra","core","community"]
+
+ if(::File.exists?("/etc/pacman.conf"))
+ pacman = ::File.read("/etc/pacman.conf")
+ repos = pacman.scan(/\[(.+)\]/).flatten
+ end
+
+ package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
+
+ status = popen4("pacman -Ss #{@new_resource.package_name}") 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)"
+ # simply split by space and use first token
+ @candidate_version = $2.split(" ").first
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
+ end
+
+ unless @candidate_version
+ raise Chef::Exceptions::Package, "pacman does not have a version of package #{@new_resource.package_name}"
+ end
+
+ @candidate_version
+
+ end
+
+ def install_package(name, version)
+ run_command_with_systems_locale(
+ :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command_with_systems_locale(
+ :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
+ )
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
new file mode 100644
index 0000000000..eb13e9855a
--- /dev/null
+++ b/lib/chef/provider/package/portage.rb
@@ -0,0 +1,138 @@
+#
+# Author:: Ezra Zygmuntowicz (<ezra@engineyard.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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+
+class Chef
+ class Provider
+ class Package
+ class Portage < Chef::Provider::Package
+ PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(nil)
+
+ category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1,2]
+
+ possibilities = Dir["/var/db/pkg/#{category || "*"}/#{pkg}-*"].map {|d| d.sub(%r{/var/db/pkg/}, "") }
+ versions = possibilities.map do |entry|
+ if(entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)})
+ [$&, $1]
+ end
+ end.compact
+
+ if versions.size > 1
+ atoms = versions.map {|v| v.first }.sort
+ categories = atoms.map {|v| v.split('/')[0] }.uniq
+ if !category && categories.size > 1
+ raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
+ end
+ elsif versions.size == 1
+ @current_resource.version(versions.first.last)
+ Chef::Log.debug("#{@new_resource} current version #{$1}")
+ end
+
+ @current_resource
+ end
+
+
+ def parse_emerge(package, txt)
+ availables = {}
+ package_without_category = package.split("/").last
+ found_package_name = nil
+
+ txt.each_line do |line|
+ if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
+ found_package_name = $&.strip
+ if found_package_name == package || found_package_name.split("/").last == package_without_category
+ availables[found_package_name] = nil
+ end
+ end
+
+ if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name)
+ availables[found_package_name] = $1.strip
+ end
+ end
+
+ if availables.size > 1
+ # shouldn't happen if a category is specified so just use `package`
+ raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category."
+ end
+
+ availables.values.first
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ status = popen4("emerge --color n --nospinner --search #{@new_resource.package_name.split('/').last}") do |pid, stdin, stdout, stderr|
+ available, installed = parse_emerge(@new_resource.package_name, stdout.read)
+ @candidate_version = available
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!"
+ end
+
+ @candidate_version
+
+ end
+
+
+ def install_package(name, version)
+ pkg = "=#{name}-#{version}"
+
+ if(version =~ /^\~(.+)/)
+ # If we start with a tilde
+ pkg = "~#{name}-#{$1}"
+ end
+
+ run_command_with_systems_locale(
+ :command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if(version)
+ pkg = "=#{@new_resource.package_name}-#{version}"
+ else
+ pkg = "#{@new_resource.package_name}"
+ end
+
+ run_command_with_systems_locale(
+ :command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
+ )
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
new file mode 100644
index 0000000000..033ce8efb9
--- /dev/null
+++ b/lib/chef/provider/package/rpm.rb
@@ -0,0 +1,121 @@
+#
+# Author:: Joshua Timberman (<joshua@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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Rpm < Chef::Provider::Package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @package_source_exists }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "Assuming package #{@new_resource.name} would have been made available."
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) }
+ a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}"
+ a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}."
+ end
+ end
+
+ def load_current_resource
+ @package_source_provided = true
+ @package_source_exists = true
+
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ unless ::File.exists?(@new_resource.source)
+ @package_source_exists = false
+ return
+ end
+
+ Chef::Log.debug("#{@new_resource} checking rpm status")
+ status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ @current_resource.package_name($1)
+ @new_resource.version($2)
+ end
+ end
+ end
+ else
+ if Array(@new_resource.action).include?(:install)
+ @package_source_exists = false
+ return
+ end
+ end
+
+ Chef::Log.debug("#{@new_resource} checking install state")
+ @rpm_status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ Chef::Log.debug("#{@new_resource} current version is #{$2}")
+ @current_resource.version($2)
+ end
+ end
+ end
+
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -i #{@new_resource.source}"
+ )
+ else
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -U #{@new_resource.source}"
+ )
+ end
+ end
+
+ alias_method :upgrade_package, :install_package
+
+ def remove_package(name, version)
+ if version
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -e #{name}-#{version}"
+ )
+ else
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -e #{name}"
+ )
+ end
+ end
+
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
new file mode 100644
index 0000000000..e60d73ab62
--- /dev/null
+++ b/lib/chef/provider/package/rubygems.rb
@@ -0,0 +1,548 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2008, 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+# Class methods on Gem are defined in rubygems
+require 'rubygems'
+# Ruby 1.9's gem_prelude can interact poorly with loading the full rubygems
+# explicitly like this. Make sure rubygems/specification is always last in this
+# list
+require 'rubygems/version'
+require 'rubygems/dependency'
+require 'rubygems/spec_fetcher'
+require 'rubygems/platform'
+require 'rubygems/format'
+require 'rubygems/dependency_installer'
+require 'rubygems/uninstaller'
+require 'rubygems/specification'
+
+class Chef
+ class Provider
+ class Package
+ class Rubygems < Chef::Provider::Package
+ class GemEnvironment
+ # HACK: trigger gem config load early. Otherwise it can get lazy
+ # loaded during operations where we've set Gem.sources to an
+ # alternate value and overwrite it with the defaults.
+ Gem.configuration
+
+ DEFAULT_UNINSTALLER_OPTS = {:ignore => true, :executables => true}
+
+ ##
+ # The paths where rubygems should search for installed gems.
+ # Implemented by subclasses.
+ def gem_paths
+ raise NotImplementedError
+ end
+
+ ##
+ # A rubygems source index containing the list of gemspecs for all
+ # available gems in the gem installation.
+ # Implemented by subclasses
+ # === Returns
+ # Gem::SourceIndex
+ def gem_source_index
+ raise NotImplementedError
+ end
+
+ ##
+ # A rubygems specification object containing the list of gemspecs for all
+ # available gems in the gem installation.
+ # Implemented by subclasses
+ # For rubygems >= 1.8.0
+ # === Returns
+ # Gem::Specification
+ def gem_specification
+ raise NotImplementedError
+ end
+
+ ##
+ # Lists the installed versions of +gem_name+, constrained by the
+ # version spec in +gem_dep+
+ # === Arguments
+ # Gem::Dependency +gem_dep+ is a Gem::Dependency object, its version
+ # specification constrains which gems are returned.
+ # === Returns
+ # [Gem::Specification] an array of Gem::Specification objects
+ def installed_versions(gem_dep)
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
+ else
+ gem_source_index.search(gem_dep)
+ end
+ end
+
+ ##
+ # Yields to the provided block with rubygems' source list set to the
+ # list provided. Always resets the list when the block returns or
+ # raises an exception.
+ def with_gem_sources(*sources)
+ sources.compact!
+ original_sources = Gem.sources
+ Gem.sources = sources unless sources.empty?
+ yield
+ ensure
+ Gem.sources = original_sources
+ end
+
+ ##
+ # Determines the candidate version for a gem from a .gem file on disk
+ # and checks if it matches the version contraints in +gem_dependency+
+ # === Returns
+ # Gem::Version a singular gem version object is returned if the gem
+ # is available
+ # nil returns nil if the gem on disk doesn't match the
+ # version constraints for +gem_dependency+
+ def candidate_version_from_file(gem_dependency, source)
+ spec = Gem::Format.from_file_by_path(source).spec
+ if spec.satisfies_requirement?(gem_dependency)
+ logger.debug {"#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}"}
+ spec.version
+ else
+ # This is probably going to end badly...
+ logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency.to_s}" }
+ nil
+ end
+ end
+
+ ##
+ # Finds the newest version that satisfies the constraints of
+ # +gem_dependency+. The version is determined from the cache or a
+ # round-trip to the server as needed. The architecture and gem
+ # sources will be set before making the query.
+ # === Returns
+ # Gem::Version a singular gem version object is returned if the gem
+ # is available
+ # nil returns nil if the gem could not be found
+ def candidate_version_from_remote(gem_dependency, *sources)
+ raise NotImplementedError
+ end
+
+ ##
+ # Find the newest gem version available from Gem.sources that satisfies
+ # the constraints of +gem_dependency+
+ def find_newest_remote_version(gem_dependency, *sources)
+ # DependencyInstaller sorts the results such that the last one is
+ # always the one it considers best.
+ spec_with_source = dependency_installer.find_gems_with_sources(gem_dependency).last
+
+ spec = spec_with_source && spec_with_source[0]
+ version = spec && spec_with_source[0].version
+ if version
+ logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{spec_with_source[1]}" }
+ version
+ else
+ source_list = sources.compact.empty? ? "[#{Gem.sources.join(', ')}]" : "[#{sources.join(', ')}]"
+ logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" }
+ nil
+ end
+ end
+
+ ##
+ # Installs a gem via the rubygems ruby API.
+ # === Options
+ # :sources rubygems servers to use
+ # Other options are passed to Gem::DependencyInstaller.new
+ def install(gem_dependency, options={})
+ with_gem_sources(*options.delete(:sources)) do
+ with_correct_verbosity do
+ dependency_installer(options).install(gem_dependency)
+ end
+ end
+ end
+
+ ##
+ # Uninstall the gem +gem_name+ via the rubygems ruby API. If
+ # +gem_version+ is provided, only that version will be uninstalled.
+ # Otherwise, all versions are uninstalled.
+ # === Options
+ # Options are passed to Gem::Uninstaller.new
+ def uninstall(gem_name, gem_version=nil, opts={})
+ gem_version ? opts[:version] = gem_version : opts[:all] = true
+ with_correct_verbosity do
+ uninstaller(gem_name, opts).uninstall
+ end
+ end
+
+ ##
+ # Set rubygems' user interaction to ConsoleUI or SilentUI depending
+ # on our current debug level
+ def with_correct_verbosity
+ Gem::DefaultUserInteraction.ui = Chef::Log.debug? ? Gem::ConsoleUI.new : Gem::SilentUI.new
+ yield
+ end
+
+ def dependency_installer(opts={})
+ Gem::DependencyInstaller.new(opts)
+ end
+
+ def uninstaller(gem_name, opts={})
+ Gem::Uninstaller.new(gem_name, DEFAULT_UNINSTALLER_OPTS.merge(opts))
+ end
+
+ private
+
+ def logger
+ Chef::Log.logger
+ end
+
+ end
+
+ class CurrentGemEnvironment < GemEnvironment
+
+ def gem_paths
+ Gem.path
+ end
+
+ def gem_source_index
+ Gem.source_index
+ end
+
+ def gem_specification
+ Gem::Specification
+ end
+
+ def candidate_version_from_remote(gem_dependency, *sources)
+ with_gem_sources(*sources) do
+ find_newest_remote_version(gem_dependency, *sources)
+ end
+ end
+
+ end
+
+ class AlternateGemEnvironment < GemEnvironment
+ JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/
+
+ def self.gempath_cache
+ @gempath_cache ||= {}
+ end
+
+ def self.platform_cache
+ @platform_cache ||= {}
+ end
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :gem_binary_location
+
+ def initialize(gem_binary_location)
+ @gem_binary_location = gem_binary_location
+ end
+
+ def gem_paths
+ if self.class.gempath_cache.key?(@gem_binary_location)
+ self.class.gempath_cache[@gem_binary_location]
+ else
+ # shellout! is a fork/exec which won't work on windows
+ shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
+ # on windows, the path separator is (usually? always?) semicolon
+ paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
+ self.class.gempath_cache[@gem_binary_location] = paths
+ end
+ end
+
+ def gem_source_index
+ @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + '/specifications' })
+ end
+
+ def gem_specification
+ # Only once, dirs calls a reset
+ unless @specification
+ Gem::Specification.dirs = gem_paths
+ @specification = Gem::Specification
+ end
+ @specification
+ end
+
+ ##
+ # Attempt to detect the correct platform settings for the target gem
+ # environment.
+ #
+ # In practice, this only makes a difference if different versions are
+ # available depending on platform, and only if the target gem
+ # environment has a radically different platform (i.e., jruby), so we
+ # just try to detect jruby and fall back to the current platforms
+ # (Gem.platforms) if we don't detect it.
+ #
+ # === Returns
+ # [String|Gem::Platform] returns an array of Gem::Platform-compatible
+ # objects, i.e., Strings that are valid for Gem::Platform or actual
+ # Gem::Platform objects.
+ def gem_platforms
+ if self.class.platform_cache.key?(@gem_binary_location)
+ self.class.platform_cache[@gem_binary_location]
+ else
+ gem_environment = shell_out!("#{@gem_binary_location} env").stdout
+ if jruby = gem_environment[JRUBY_PLATFORM]
+ self.class.platform_cache[@gem_binary_location] = ['ruby', Gem::Platform.new(jruby)]
+ else
+ self.class.platform_cache[@gem_binary_location] = Gem.platforms
+ end
+ end
+ end
+
+ def with_gem_platforms(*alt_gem_platforms)
+ alt_gem_platforms.flatten!
+ original_gem_platforms = Gem.platforms
+ Gem.platforms = alt_gem_platforms
+ yield
+ ensure
+ Gem.platforms = original_gem_platforms
+ end
+
+ def candidate_version_from_remote(gem_dependency, *sources)
+ with_gem_sources(*sources) do
+ with_gem_platforms(*gem_platforms) do
+ find_newest_remote_version(gem_dependency, *sources)
+ end
+ end
+ end
+
+ end
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :gem_env
+ attr_reader :cleanup_gem_env
+
+ def logger
+ Chef::Log.logger
+ end
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(new_resource, run_context=nil)
+ super
+ @cleanup_gem_env = true
+ if new_resource.gem_binary
+ if new_resource.options && new_resource.options.kind_of?(Hash)
+ msg = "options cannot be given as a hash when using an explicit gem_binary\n"
+ msg << "in #{new_resource} from #{new_resource.source_line}"
+ raise ArgumentError, msg
+ end
+ @gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
+ Chef::Log.debug("#{@new_resource} using gem '#{new_resource.gem_binary}'")
+ elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem)
+ # Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
+ # Default to installing somewhere more functional
+ if new_resource.options && new_resource.options.kind_of?(Hash)
+ msg = "options should be a string instead of a hash\n"
+ msg << "in #{new_resource} from #{new_resource.source_line}"
+ raise ArgumentError, msg
+ end
+ gem_location = find_gem_by_path
+ @new_resource.gem_binary gem_location
+ @gem_env = AlternateGemEnvironment.new(gem_location)
+ Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'")
+ else
+ @gem_env = CurrentGemEnvironment.new
+ @cleanup_gem_env = false
+ Chef::Log.debug("#{@new_resource} using gem from running ruby environment")
+ end
+ end
+
+ def is_omnibus?
+ if RbConfig::CONFIG['bindir'] =~ %r!/opt/(opscode|chef)/embedded/bin!
+ Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ # Omnibus installs to a static path because of linking on unix, find it.
+ true
+ elsif RbConfig::CONFIG['bindir'].sub(/^[\w]:/, '') == "/opscode/chef/embedded/bin"
+ Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ # windows, with the drive letter removed
+ true
+ else
+ false
+ end
+ end
+
+ def find_gem_by_path
+ Chef::Log.debug("#{@new_resource} searching for 'gem' binary in path: #{ENV['PATH']}")
+ separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR
+ path_to_first_gem = ENV['PATH'].split(::File::PATH_SEPARATOR).select { |path| ::File.exists?(path + separator + "gem") }.first
+ raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil?
+ path_to_first_gem + separator + "gem"
+ end
+
+ def gem_dependency
+ Gem::Dependency.new(@new_resource.package_name, @new_resource.version)
+ end
+
+ def source_is_remote?
+ return true if @new_resource.source.nil?
+ scheme = URI.parse(@new_resource.source).scheme
+ # URI.parse gets confused by MS Windows paths with forward slashes.
+ scheme = nil if scheme =~ /^[a-z]$/
+ %w{http https}.include?(scheme)
+ end
+
+ def current_version
+ #raise 'todo'
+ # If one or more matching versions are installed, the newest of them
+ # is the current version
+ if !matching_installed_versions.empty?
+ gemspec = matching_installed_versions.last
+ logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}"}
+ gemspec
+ # If no version matching the requirements exists, the latest installed
+ # version is the current version.
+ elsif !all_installed_versions.empty?
+ gemspec = all_installed_versions.last
+ logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
+ gemspec
+ else
+ logger.debug { "#{@new_resource} no installed version found for #{gem_dependency.to_s}"}
+ nil
+ end
+ end
+
+ def matching_installed_versions
+ @matching_installed_versions ||= @gem_env.installed_versions(gem_dependency)
+ end
+
+ def all_installed_versions
+ @all_installed_versions ||= begin
+ @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, '>= 0'))
+ end
+ end
+
+ def gem_sources
+ @new_resource.source ? Array(@new_resource.source) : nil
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ if current_spec = current_version
+ @current_resource.version(current_spec.version.to_s)
+ end
+ @current_resource
+ end
+
+ def cleanup_after_converge
+ if @cleanup_gem_env
+ logger.debug { "#{@new_resource} resetting gem environment to default" }
+ Gem.clear_paths
+ end
+ end
+
+ def candidate_version
+ @candidate_version ||= begin
+ if target_version_already_installed?
+ nil
+ elsif source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
+ end
+ end
+ end
+
+ def target_version_already_installed?
+ return false unless @current_resource && @current_resource.version
+ return false if @current_resource.version.nil?
+
+ Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version))
+ end
+
+ ##
+ # Installs the gem, using either the gems API or shelling out to `gem`
+ # according to the following criteria:
+ # 1. Use gems API (Gem::DependencyInstaller) by default
+ # 2. shell out to `gem install` when a String of options is given
+ # 3. use gems API with options if a hash of options is given
+ def install_package(name, version)
+ if source_is_remote? && @new_resource.gem_binary.nil?
+ if @new_resource.options.nil?
+ @gem_env.install(gem_dependency, :sources => gem_sources)
+ elsif @new_resource.options.kind_of?(Hash)
+ options = @new_resource.options
+ options[:sources] = gem_sources
+ @gem_env.install(gem_dependency, options)
+ else
+ install_via_gem_command(name, version)
+ end
+ elsif @new_resource.gem_binary.nil?
+ @gem_env.install(@new_resource.source)
+ else
+ install_via_gem_command(name,version)
+ end
+ true
+ end
+
+ def gem_binary_path
+ @new_resource.gem_binary || 'gem'
+ end
+
+ def install_via_gem_command(name, version)
+ if @new_resource.source =~ /\.gem$/i
+ name = @new_resource.source
+ else
+ src = @new_resource.source && " --source=#{@new_resource.source} --source=http://rubygems.org"
+ end
+ if version
+ shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+ else
+ shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if @new_resource.gem_binary.nil?
+ if @new_resource.options.nil?
+ @gem_env.uninstall(name, version)
+ elsif @new_resource.options.kind_of?(Hash)
+ @gem_env.uninstall(name, version, @new_resource.options)
+ else
+ uninstall_via_gem_command(name, version)
+ end
+ else
+ uninstall_via_gem_command(name, version)
+ end
+ end
+
+ def uninstall_via_gem_command(name, version)
+ if version
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+ else
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+ end
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ private
+
+ def opts
+ expand_options(@new_resource.options)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
new file mode 100644
index 0000000000..a3ef1e5e86
--- /dev/null
+++ b/lib/chef/provider/package/smartos.rb
@@ -0,0 +1,84 @@
+#
+# Authors:: Trevor O (trevoro@joyent.com)
+# Bryan McLellan (btm@loftninjas.org)
+# Matthew Landauer (matthew@openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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.
+#
+# Notes
+#
+# * Supports installing using a local package name
+# * Otherwise reverts to installing from the pkgsrc repositories URL
+
+require 'chef/provider/package'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class SmartOS < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ attr_accessor :is_virtual_package
+
+
+ def load_current_resource
+ Chef::Log.debug("#{@new_resource} loading current resource")
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @current_resource.version(nil)
+ check_package_state(@new_resource.package_name)
+ @current_resource # modified by check_package_state
+ end
+
+ def check_package_state(name)
+ Chef::Log.debug("#{@new_resource} checking package #{name}")
+ # XXX
+ version = nil
+ info = shell_out!("pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
+
+ if info.stdout
+ version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+ end
+
+ if !version
+ @current_resource.version(nil)
+ else
+ @current_resource.version(version)
+ end
+ end
+
+ def install_package(name, version)
+ Chef::Log.debug("#{@new_resource} installing package #{name}-#{version}")
+ package = "#{name}-#{version}"
+ out = shell_out!("pkgin -y install #{package}", :env => nil)
+ end
+
+ def upgrade_package(name, version)
+ Chef::Log.debug("#{@new_resource} upgrading package #{name}-#{version}")
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ Chef::Log.debug("#{@new_resource} removing package #{name}-#{version}")
+ package = "#{name}-#{version}"
+ out = shell_out!("pkgin -y remove #{package}", :env => nil)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
new file mode 100644
index 0000000000..f502a0dc96
--- /dev/null
+++ b/lib/chef/provider/package/solaris.rb
@@ -0,0 +1,139 @@
+#
+# Author:: Toomas Pelberg (<toomasp@gmx.net>)
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Solaris < Chef::Provider::Package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ # def initialize(*args)
+ # super
+ # @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ # end
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion { @new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !@new_resource.source || @package_source_found }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ @package_source_found = ::File.exists?(@new_resource.source)
+ if @package_source_found
+ Chef::Log.debug("#{@new_resource} checking pkg status")
+ status = popen4("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ @new_resource.version($1)
+ end
+ end
+ end
+ end
+ end
+
+ Chef::Log.debug("#{@new_resource} checking install state")
+ status = popen4("pkginfo -l #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ Chef::Log.debug("#{@new_resource} version #{$1} is already installed")
+ @current_resource.version($1)
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!"
+ end
+
+ unless @current_resource.version.nil?
+ @current_resource.version(nil)
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+ status = popen4("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ @candidate_version = $1
+ @new_resource.version($1)
+ Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
+ end
+ end
+ end
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "pkginfo -l -d #{@new_resource.source} - #{status.inspect}!"
+ end
+ @candidate_version
+ end
+
+ def install_package(name, version)
+ Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
+ if @new_resource.options.nil?
+ run_command_with_systems_locale(
+ :command => "pkgadd -n -d #{@new_resource.source} all"
+ )
+ Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ else
+ run_command_with_systems_locale(
+ :command => "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
+ )
+ Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ end
+ end
+
+ def remove_package(name, version)
+ if @new_resource.options.nil?
+ run_command_with_systems_locale(
+ :command => "pkgrm -n #{name}"
+ )
+ Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ else
+ run_command_with_systems_locale(
+ :command => "pkgrm -n#{expand_options(@new_resource.options)} #{name}"
+ )
+ Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum-dump.py
new file mode 100644
index 0000000000..99136eceec
--- /dev/null
+++ b/lib/chef/provider/package/yum-dump.py
@@ -0,0 +1,287 @@
+#
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Copyright:: Copyright (c) 2009, 2011 Matthew Kent
+# 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.
+#
+
+# yum-dump.py
+# Inspired by yumhelper.py by David Lutterkort
+#
+# Produce a list of installed, available and re-installable packages using yum
+# and dump the results to stdout.
+#
+# yum-dump invokes yum similarly to the command line interface which makes it
+# subject to most of the configuration paramaters in yum.conf. yum-dump will
+# also load yum plugins in the same manor as yum - these can affect the output.
+#
+# Can be run as non root, but that won't update the cache.
+#
+# Intended to support yum 2.x and 3.x
+
+import os
+import sys
+import time
+import yum
+import re
+import errno
+
+from yum import Errors
+from optparse import OptionParser
+from distutils import version
+
+YUM_PID_FILE='/var/run/yum.pid'
+
+# Seconds to wait for exclusive access to yum
+LOCK_TIMEOUT = 10
+
+YUM_VER = version.StrictVersion(yum.__version__)
+YUM_MAJOR = YUM_VER.version[0]
+
+if YUM_MAJOR > 3 or YUM_MAJOR < 2:
+ print >> sys.stderr, "yum-dump Error: Can't match supported yum version" \
+ " (%s)" % yum.__version__
+ sys.exit(1)
+
+# Required for Provides output
+if YUM_MAJOR == 2:
+ import rpm
+ import rpmUtils.miscutils
+
+def setup(yb, options):
+ # Only want our output
+ #
+ if YUM_MAJOR == 3:
+ try:
+ if YUM_VER >= version.StrictVersion("3.2.22"):
+ yb.preconf.errorlevel=0
+ yb.preconf.debuglevel=0
+
+ # initialize the config
+ yb.conf
+ else:
+ yb.doConfigSetup(errorlevel=0, debuglevel=0)
+ except yum.Errors.ConfigError, e:
+ # supresses an ignored exception at exit
+ yb.preconf = None
+ print >> sys.stderr, "yum-dump Config Error: %s" % e
+ return 1
+ except ValueError, e:
+ yb.preconf = None
+ print >> sys.stderr, "yum-dump Options Error: %s" % e
+ return 1
+ elif YUM_MAJOR == 2:
+ yb.doConfigSetup()
+
+ def __log(a,b): pass
+
+ yb.log = __log
+ yb.errorlog = __log
+
+ # Give Chef every possible package version, it can decide what to do with them
+ if YUM_MAJOR == 3:
+ yb.conf.showdupesfromrepos = True
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('showdupesfromrepos', True)
+
+ # Optionally run only on cached repositories, but non root must use the cache
+ if os.geteuid() != 0:
+ if YUM_MAJOR == 3:
+ yb.conf.cache = True
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('cache', True)
+ else:
+ if YUM_MAJOR == 3:
+ yb.conf.cache = options.cache
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('cache', options.cache)
+
+ return 0
+
+def dump_packages(yb, list, output_provides):
+ packages = {}
+
+ if YUM_MAJOR == 2:
+ yb.doTsSetup()
+ yb.doRepoSetup()
+ yb.doSackSetup()
+
+ db = yb.doPackageLists(list)
+
+ for pkg in db.installed:
+ pkg.type = 'i'
+ packages[str(pkg)] = pkg
+
+ if YUM_VER >= version.StrictVersion("3.2.21"):
+ for pkg in db.available:
+ pkg.type = 'a'
+ packages[str(pkg)] = pkg
+
+ # These are both installed and available
+ for pkg in db.reinstall_available:
+ pkg.type = 'r'
+ packages[str(pkg)] = pkg
+ else:
+ # Old style method - no reinstall list
+ for pkg in yb.pkgSack.returnPackages():
+
+ if str(pkg) in packages:
+ if packages[str(pkg)].type == "i":
+ packages[str(pkg)].type = 'r'
+ continue
+
+ pkg.type = 'a'
+ packages[str(pkg)] = pkg
+
+ unique_packages = packages.values()
+
+ unique_packages.sort(lambda x, y: cmp(x.name, y.name))
+
+ for pkg in unique_packages:
+ if output_provides == "all" or \
+ (output_provides == "installed" and (pkg.type == "i" or pkg.type == "r")):
+
+ # yum 2 doesn't have provides_print, implement it ourselves using methods
+ # based on requires gathering in packages.py
+ if YUM_MAJOR == 2:
+ provlist = []
+
+ # Installed and available are gathered in different ways
+ if pkg.type == 'i' or pkg.type == 'r':
+ names = pkg.hdr[rpm.RPMTAG_PROVIDENAME]
+ flags = pkg.hdr[rpm.RPMTAG_PROVIDEFLAGS]
+ ver = pkg.hdr[rpm.RPMTAG_PROVIDEVERSION]
+ if names is not None:
+ tmplst = zip(names, flags, ver)
+
+ for (n, f, v) in tmplst:
+ prov = rpmUtils.miscutils.formatRequire(n, v, f)
+ provlist.append(prov)
+ # This is slow :(
+ elif pkg.type == 'a':
+ for prcoTuple in pkg.returnPrco('provides'):
+ prcostr = pkg.prcoPrintable(prcoTuple)
+ provlist.append(prcostr)
+
+ provides = provlist
+ else:
+ provides = pkg.provides_print
+ else:
+ provides = "[]"
+
+ print '%s %s %s %s %s %s %s %s' % (
+ pkg.name,
+ pkg.epoch,
+ pkg.version,
+ pkg.release,
+ pkg.arch,
+ provides,
+ pkg.type,
+ pkg.repoid )
+
+ return 0
+
+def yum_dump(options):
+ lock_obtained = False
+
+ yb = yum.YumBase()
+
+ status = setup(yb, options)
+ if status != 0:
+ return status
+
+ if options.output_options:
+ print "[option installonlypkgs] %s" % " ".join(yb.conf.installonlypkgs)
+
+ # Non root can't handle locking on rhel/centos 4
+ if os.geteuid() != 0:
+ return dump_packages(yb, options.package_list, options.output_provides)
+
+ # Wrap the collection and output of packages in yum's global lock to prevent
+ # any inconsistencies.
+ try:
+ # Spin up to LOCK_TIMEOUT
+ countdown = LOCK_TIMEOUT
+ while True:
+ try:
+ yb.doLock(YUM_PID_FILE)
+ lock_obtained = True
+ except Errors.LockError, e:
+ time.sleep(1)
+ countdown -= 1
+ if countdown == 0:
+ print >> sys.stderr, "yum-dump Locking Error! Couldn't obtain an " \
+ "exclusive yum lock in %d seconds. Giving up." % LOCK_TIMEOUT
+ return 200
+ else:
+ break
+
+ return dump_packages(yb, options.package_list, options.output_provides)
+
+ # Ensure we clear the lock and cleanup any resources
+ finally:
+ try:
+ yb.closeRpmDB()
+ if lock_obtained == True:
+ yb.doUnlock(YUM_PID_FILE)
+ except Errors.LockError, e:
+ print >> sys.stderr, "yum-dump Unlock Error: %s" % e
+ return 200
+
+def main():
+ usage = "Usage: %prog [options]\n" + \
+ "Output a list of installed, available and re-installable packages via yum"
+ parser = OptionParser(usage=usage)
+ parser.add_option("-C", "--cache",
+ action="store_true", dest="cache", default=False,
+ help="run entirely from cache, don't update cache")
+ parser.add_option("-o", "--options",
+ action="store_true", dest="output_options", default=False,
+ help="output select yum options useful to Chef")
+ parser.add_option("-p", "--installed-provides",
+ action="store_const", const="installed", dest="output_provides", default="none",
+ help="output Provides for installed packages, big/wide output")
+ parser.add_option("-P", "--all-provides",
+ action="store_const", const="all", dest="output_provides", default="none",
+ help="output Provides for all package, slow, big/wide output")
+ parser.add_option("-i", "--installed",
+ action="store_const", const="installed", dest="package_list", default="all",
+ help="output only installed packages")
+ parser.add_option("-a", "--available",
+ action="store_const", const="available", dest="package_list", default="all",
+ help="output only available and re-installable packages")
+
+ (options, args) = parser.parse_args()
+
+ try:
+ return yum_dump(options)
+
+ except yum.Errors.RepoError, e:
+ print >> sys.stderr, "yum-dump Repository Error: %s" % e
+ return 1
+
+ except yum.Errors.YumBaseError, e:
+ print >> sys.stderr, "yum-dump General Error: %s" % e
+ return 1
+
+try:
+ status = main()
+# Suppress a nasty broken pipe error when output is piped to utilities like 'head'
+except IOError, e:
+ if e.errno == errno.EPIPE:
+ sys.exit(1)
+ else:
+ raise
+
+sys.exit(status)
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
new file mode 100644
index 0000000000..9048048b83
--- /dev/null
+++ b/lib/chef/provider/package/yum.rb
@@ -0,0 +1,1214 @@
+#
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'singleton'
+require 'chef/mixin/get_source_from_package'
+
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+
+ class RPMUtils
+ class << self
+
+ # RPM::Version version_parse equivalent
+ def version_parse(evr)
+ return if evr.nil?
+
+ epoch = nil
+ # assume this is a version
+ version = evr
+ release = nil
+
+ lead = 0
+ tail = evr.size
+
+ if evr =~ %r{^([\d]+):}
+ epoch = $1.to_i
+ lead = $1.length + 1
+ elsif evr[0].ord == ":".ord
+ epoch = 0
+ lead = 1
+ end
+
+ if evr =~ %r{:?.*-(.*)$}
+ release = $1
+ tail = evr.length - release.length - lead - 1
+
+ if release.empty?
+ release = nil
+ end
+ end
+
+ version = evr[lead,tail]
+ if version.empty?
+ version = nil
+ end
+
+ [ epoch, version, release ]
+ end
+
+ # verify
+ def isalnum(x)
+ isalpha(x) or isdigit(x)
+ end
+
+ def isalpha(x)
+ v = x.ord
+ (v >= 65 and v <= 90) or (v >= 97 and v <= 122)
+ end
+
+ def isdigit(x)
+ v = x.ord
+ v >= 48 and v <= 57
+ end
+
+ # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
+ def rpmvercmp(x, y)
+ # easy! :)
+ return 0 if x == y
+
+ if x.nil?
+ x = ""
+ end
+
+ if y.nil?
+ y = ""
+ end
+
+ # not so easy :(
+ #
+ # takes 2 strings like
+ #
+ # x = "1.20.b18.el5"
+ # y = "1.20.b17.el5"
+ #
+ # breaks into purely alpha and numeric segments and compares them using
+ # some rules
+ #
+ # * 10 > 1
+ # * 1 > a
+ # * z > a
+ # * Z > A
+ # * z > Z
+ # * leading zeros are ignored
+ # * separators (periods, commas) are ignored
+ # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
+
+ x_pos = 0 # overall string element reference position
+ x_pos_max = x.length - 1 # number of elements in string, starting from 0
+ x_seg_pos = 0 # segment string element reference position
+ x_comp = nil # segment to compare
+
+ y_pos = 0
+ y_seg_pos = 0
+ y_pos_max = y.length - 1
+ y_comp = nil
+
+ while (x_pos <= x_pos_max and y_pos <= y_pos_max)
+ # first we skip over anything non alphanumeric
+ while (x_pos <= x_pos_max) and (isalnum(x[x_pos]) == false)
+ x_pos += 1 # +1 over pos_max if end of string
+ end
+ while (y_pos <= y_pos_max) and (isalnum(y[y_pos]) == false)
+ y_pos += 1
+ end
+
+ # if we hit the end of either we are done matching segments
+ if (x_pos == x_pos_max + 1) or (y_pos == y_pos_max + 1)
+ break
+ end
+
+ # we are now at the start of a alpha or numeric segment
+ x_seg_pos = x_pos
+ y_seg_pos = y_pos
+
+ # grab segment so we can compare them
+ if isdigit(x[x_seg_pos].ord)
+ x_seg_is_num = true
+
+ # already know it's a digit
+ x_seg_pos += 1
+
+ # gather up our digits
+ while (x_seg_pos <= x_pos_max) and isdigit(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ # copy the segment but not the unmatched character that x_seg_pos will
+ # refer to
+ x_comp = x[x_pos,x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) and isdigit(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos,y_seg_pos - y_pos]
+ else
+ # we are comparing strings
+ x_seg_is_num = false
+
+ while (x_seg_pos <= x_pos_max) and isalpha(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ x_comp = x[x_pos,x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) and isalpha(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos,y_seg_pos - y_pos]
+ end
+
+ # if y_seg_pos didn't advance in the above loop it means the segments are
+ # different types
+ if y_pos == y_seg_pos
+ # numbers always win over letters
+ return x_seg_is_num ? 1 : -1
+ end
+
+ # move the ball forward before we mess with the segments
+ x_pos += x_comp.length # +1 over pos_max if end of string
+ y_pos += y_comp.length
+
+ # we are comparing numbers - simply convert them
+ if x_seg_is_num
+ x_comp = x_comp.to_i
+ y_comp = y_comp.to_i
+ end
+
+ # compares ints or strings
+ # don't return if equal - try the next segment
+ if x_comp > y_comp
+ return 1
+ elsif x_comp < y_comp
+ return -1
+ end
+
+ # if we've reached here than the segments are the same - try again
+ end
+
+ # we must have reached the end of one or both of the strings and they
+ # matched up until this point
+
+ # segments matched completely but the segment separators were different -
+ # rpm reference code treats these as equal.
+ if (x_pos == x_pos_max + 1) and (y_pos == y_pos_max + 1)
+ return 0
+ end
+
+ # the most unprocessed characters left wins
+ if (x_pos_max - x_pos) > (y_pos_max - y_pos)
+ return 1
+ else
+ return -1
+ end
+ end
+
+ end # self
+ end # RPMUtils
+
+ class RPMVersion
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 1
+ @e, @v, @r = RPMUtils.version_parse(args[0])
+ elsif args.size == 3
+ @e = args[0].to_i
+ @v = args[1]
+ @r = args[2]
+ else
+ raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
+ "version, release'"
+ end
+ end
+ attr_reader :e, :v, :r
+ alias :epoch :e
+ alias :version :v
+ alias :release :r
+
+ def self.parse(*args)
+ self.new(*args)
+ end
+
+ def <=>(y)
+ compare_versions(y)
+ end
+
+ def compare(y)
+ compare_versions(y, false)
+ end
+
+ def partial_compare(y)
+ compare_versions(y, true)
+ end
+
+ # RPM::Version rpm_version_to_s equivalent
+ def to_s
+ if @r.nil?
+ @v
+ else
+ "#{@v}-#{@r}"
+ end
+ end
+
+ def evr
+ "#{@e}:#{@v}-#{@r}"
+ end
+
+ private
+
+ # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
+ #
+ # partial lets epoch and version segment equality be good enough to return equal, eg:
+ #
+ # 2:1.2-1 == 2:1.2
+ # 2:1.2-1 == 2:
+ #
+ def compare_versions(y, partial=false)
+ x = self
+
+ # compare epoch
+ if (x.e.nil? == false and x.e > 0) and y.e.nil?
+ return 1
+ elsif x.e.nil? and (y.e.nil? == false and y.e > 0)
+ return -1
+ elsif x.e.nil? == false and y.e.nil? == false
+ if x.e < y.e
+ return -1
+ elsif x.e > y.e
+ return 1
+ end
+ end
+
+ # compare version
+ if partial and (x.v.nil? or y.v.nil?)
+ return 0
+ elsif x.v.nil? == false and y.v.nil?
+ return 1
+ elsif x.v.nil? and y.v.nil? == false
+ return -1
+ elsif x.v.nil? == false and y.v.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.v, y.v)
+ return cmp if cmp != 0
+ end
+
+ # compare release
+ if partial and (x.r.nil? or y.r.nil?)
+ return 0
+ elsif x.r.nil? == false and y.r.nil?
+ return 1
+ elsif x.r.nil? and y.r.nil? == false
+ return -1
+ elsif x.r.nil? == false and y.r.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.r, y.r)
+ return cmp
+ end
+
+ return 0
+ end
+ end
+
+ class RPMPackage
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 4
+ @n = args[0]
+ @version = RPMVersion.new(args[1])
+ @a = args[2]
+ @provides = args[3]
+ elsif args.size == 6
+ @n = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e,v,r)
+ @a = args[4]
+ @provides = args[5]
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
+ "or 'name, epoch, version, release, arch, provides'"
+ end
+
+ # We always have one, ourselves!
+ if @provides.empty?
+ @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
+ end
+ end
+ attr_reader :n, :a, :version, :provides
+ alias :name :n
+ alias :arch :a
+
+ def <=>(y)
+ compare(y)
+ end
+
+ def compare(y)
+ x = self
+
+ # easy! :)
+ return 0 if x.nevra == y.nevra
+
+ # compare name
+ if x.n.nil? == false and y.n.nil?
+ return 1
+ elsif x.n.nil? and y.n.nil? == false
+ return -1
+ elsif x.n.nil? == false and y.n.nil? == false
+ if x.n < y.n
+ return -1
+ elsif x.n > y.n
+ return 1
+ end
+ end
+
+ # compare version
+ if x.version > y.version
+ return 1
+ elsif x.version < y.version
+ return -1
+ end
+
+ # compare arch
+ if x.a.nil? == false and y.a.nil?
+ return 1
+ elsif x.a.nil? and y.a.nil? == false
+ return -1
+ elsif x.a.nil? == false and y.a.nil? == false
+ if x.a < y.a
+ return -1
+ elsif x.a > y.a
+ return 1
+ end
+ end
+
+ return 0
+ end
+
+ def to_s
+ nevra
+ end
+
+ def nevra
+ "#{@n}-#{@version.evr}.#{@a}"
+ end
+ end
+
+ # Simple implementation from rpm and ruby-rpm reference code
+ class RPMDependency
+ def initialize(*args)
+ if args.size == 3
+ @name = args[0]
+ @version = RPMVersion.new(args[1])
+ # Our requirement to other dependencies
+ @flag = args[2] || :==
+ elsif args.size == 5
+ @name = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e,v,r)
+ @flag = args[4] || :==
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
+ "'name, epoch, version, release, flag'"
+ end
+ end
+ attr_reader :name, :version, :flag
+
+ # Parses 2 forms:
+ #
+ # "mtr >= 2:0.71-3.0"
+ # "mta"
+ def self.parse(string)
+ if string =~ %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}
+ name = $1
+ if $2 == "="
+ flag = :==
+ else
+ flag = :"#{$2}"
+ end
+ version = $3
+
+ return self.new(name, version, flag)
+ else
+ name = string
+ return self.new(name, nil, nil)
+ end
+ end
+
+ # Test if another RPMDependency satisfies our requirements
+ def satisfy?(y)
+ unless y.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ x = self
+
+ # Easy!
+ if x.name != y.name
+ return false
+ end
+
+ # Partial compare
+ #
+ # eg: x.version 2.3 == y.version 2.3-1
+ sense = x.version.partial_compare(y.version)
+
+ # Thanks to rpmdsCompare() rpmds.c
+ if sense < 0 and (x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<)
+ return true
+ elsif sense > 0 and (x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>)
+ return true
+ elsif sense == 0 and (
+ ((x.flag == :== or x.flag == :<= or x.flag == :>=) and (y.flag == :== or y.flag == :<= or y.flag == :>=)) or
+ (x.flag == :< and y.flag == :<) or
+ (x.flag == :> and y.flag == :>)
+ )
+ return true
+ end
+
+ return false
+ end
+ end
+
+ class RPMProvide < RPMDependency; end
+ class RPMRequire < RPMDependency; end
+
+ class RPMDbPackage < RPMPackage
+ # <rpm parts>, installed, available
+ def initialize(*args)
+ @repoid = args.pop
+ # state
+ @available = args.pop
+ @installed = args.pop
+ super(*args)
+ end
+ attr_reader :repoid, :available, :installed
+ end
+
+ # Simple storage for RPMPackage objects - keeps them unique and sorted
+ class RPMDb
+ def initialize
+ # package name => [ RPMPackage, RPMPackage ] of different versions
+ @rpms = Hash.new
+ # package nevra => RPMPackage for lookups
+ @index = Hash.new
+ # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
+ @provides = Hash.new
+ # RPMPackages listed as available
+ @available = Set.new
+ # RPMPackages listed as installed
+ @installed = Set.new
+ end
+
+ def [](package_name)
+ self.lookup(package_name)
+ end
+
+ # Lookup package_name and return a descending array of package objects
+ def lookup(package_name)
+ pkgs = @rpms[package_name]
+ if pkgs
+ return pkgs.sort.reverse
+ else
+ return nil
+ end
+ end
+
+ def lookup_provides(provide_name)
+ @provides[provide_name]
+ end
+
+ # Using the package name as a key, and nevra for an index, keep a unique list of packages.
+ # The available/installed state can be overwritten for existing packages.
+ def push(*args)
+ args.flatten.each do |new_rpm|
+ unless new_rpm.kind_of?(RPMDbPackage)
+ raise ArgumentError, "Expecting an RPMDbPackage object"
+ end
+
+ @rpms[new_rpm.n] ||= Array.new
+
+ # we may already have this one, like when the installed list is refreshed
+ idx = @index[new_rpm.nevra]
+ if idx
+ # grab the existing package if it's not
+ curr_rpm = idx
+ else
+ @rpms[new_rpm.n] << new_rpm
+
+ new_rpm.provides.each do |provide|
+ @provides[provide.name] ||= Array.new
+ @provides[provide.name] << new_rpm
+ end
+
+ curr_rpm = new_rpm
+ end
+
+ # Track the nevra -> RPMPackage association to avoid having to compare versions
+ # with @rpms[new_rpm.n] on the next round
+ @index[new_rpm.nevra] = curr_rpm
+
+ # these are overwritten for existing packages
+ if new_rpm.available
+ @available << curr_rpm
+ end
+ if new_rpm.installed
+ @installed << curr_rpm
+ end
+ end
+ end
+
+ def <<(*args)
+ self.push(args)
+ end
+
+ def clear
+ @rpms.clear
+ @index.clear
+ @provides.clear
+ clear_available
+ clear_installed
+ end
+
+ def clear_available
+ @available.clear
+ end
+
+ def clear_installed
+ @installed.clear
+ end
+
+ def size
+ @rpms.size
+ end
+ alias :length :size
+
+ def available_size
+ @available.size
+ end
+
+ def installed_size
+ @installed.size
+ end
+
+ def available?(package)
+ @available.include?(package)
+ end
+
+ def installed?(package)
+ @installed.include?(package)
+ end
+
+ def whatprovides(rpmdep)
+ unless rpmdep.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ what = []
+
+ packages = lookup_provides(rpmdep.name)
+ if packages
+ packages.each do |pkg|
+ pkg.provides.each do |provide|
+ if provide.satisfy?(rpmdep)
+ what << pkg
+ end
+ end
+ end
+ end
+
+ return what
+ end
+ end
+
+ # Cache for our installed and available packages, pulled in from yum-dump.py
+ class YumCache
+ include Chef::Mixin::Command
+ include Singleton
+
+ def initialize
+ @rpmdb = RPMDb.new
+
+ # Next time @rpmdb is accessed:
+ # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
+ # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
+ # dependency data for installed packages only - this data is slow to
+ # gather.
+ # :provides - Same as :all but pulls in Provides data for available packages as well.
+ # Used as a last resort when we can't find a Provides match.
+ # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
+ # db. Used between client runs for a quick refresh.
+ # :none - Do nothing, a call to one of the reload methods is required.
+ @next_refresh = :all
+
+ @allow_multi_install = []
+
+ # these are for subsequent runs if we are on an interval
+ Chef::Client.when_run_starts do
+ YumCache.instance.reload
+ end
+ end
+
+ # Cache management
+ #
+
+ def refresh
+ case @next_refresh
+ when :none
+ return nil
+ when :installed
+ reset_installed
+ # fast
+ opts=" --installed"
+ when :all
+ reset
+ # medium
+ opts=" --options --installed-provides"
+ when :provides
+ reset
+ # slow!
+ opts=" --options --all-provides"
+ else
+ raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
+ end
+
+ one_line = false
+ error = nil
+
+ helper = ::File.join(::File.dirname(__FILE__), 'yum-dump.py')
+
+ status = popen4("/usr/bin/python #{helper}#{opts}", :waitlast => true) do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ one_line = true
+
+ line.chomp!
+
+ if line =~ %r{\[option (.*)\] (.*)}
+ if $1 == "installonlypkgs"
+ @allow_multi_install = $2.split
+ else
+ raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
+ end
+ next
+ end
+
+ if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
+ name = $1
+ epoch = $2
+ version = $3
+ release = $4
+ arch = $5
+ provides = parse_provides($6)
+ type = $7
+ repoid = $8
+ else
+ Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
+ "Please check your yum configuration.")
+ next
+ end
+
+ case type
+ when "i"
+ # if yum-dump was called with --installed this may not be true, but it's okay
+ # since we don't touch the @available Set in reload_installed
+ available = false
+ installed = true
+ when "a"
+ available = true
+ installed = false
+ when "r"
+ available = true
+ installed = true
+ end
+
+ pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
+ @rpmdb << pkg
+ end
+
+ error = stderr.readlines
+ end
+
+ if status.exitstatus != 0
+ raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
+ else
+ unless one_line
+ Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
+ "your yum configuration.")
+ end
+ end
+
+ # A reload method must be called before the cache is altered
+ @next_refresh = :none
+ end
+
+ def reload
+ @next_refresh = :all
+ end
+
+ def reload_installed
+ @next_refresh = :installed
+ end
+
+ def reload_provides
+ @next_refresh = :provides
+ end
+
+ def reset
+ @rpmdb.clear
+ end
+
+ def reset_installed
+ @rpmdb.clear_installed
+ end
+
+ # Querying the cache
+ #
+
+ # Check for package by name or name+arch
+ def package_available?(package_name)
+ refresh
+
+ if @rpmdb.lookup(package_name)
+ return true
+ else
+ if package_name =~ %r{^(.*)\.(.*)$}
+ pkg_name = $1
+ pkg_arch = $2
+
+ if matches = @rpmdb.lookup(pkg_name)
+ matches.each do |m|
+ return true if m.arch == pkg_arch
+ end
+ end
+ end
+ end
+
+ return false
+ end
+
+ # Returns a array of packages satisfying an RPMDependency
+ def packages_from_require(rpmdep)
+ refresh
+ @rpmdb.whatprovides(rpmdep)
+ end
+
+ # Check if a package-version.arch is available to install
+ def version_available?(package_name, desired_version, arch=nil)
+ version(package_name, arch, true, false) do |v|
+ return true if desired_version == v
+ end
+
+ return false
+ end
+
+ # Return the source repository for a package-version.arch
+ def package_repository(package_name, desired_version, arch=nil)
+ package(package_name, arch, true, false) do |pkg|
+ return pkg.repoid if desired_version == pkg.version.to_s
+ end
+
+ return nil
+ end
+
+ # Return the latest available version for a package.arch
+ def available_version(package_name, arch=nil)
+ version(package_name, arch, true, false)
+ end
+ alias :candidate_version :available_version
+
+ # Return the currently installed version for a package.arch
+ def installed_version(package_name, arch=nil)
+ version(package_name, arch, false, true)
+ end
+
+ # Return an array of packages allowed to be installed multiple times, such as the kernel
+ def allow_multi_install
+ refresh
+ @allow_multi_install
+ end
+
+ private
+
+ def version(package_name, arch=nil, is_available=false, is_installed=false)
+ package(package_name, arch, is_available, is_installed) do |pkg|
+ if block_given?
+ yield pkg.version.to_s
+ else
+ # first match is latest version
+ return pkg.version.to_s
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ def package(package_name, arch=nil, is_available=false, is_installed=false)
+ refresh
+ packages = @rpmdb[package_name]
+ if packages
+ packages.each do |pkg|
+ if is_available
+ next unless @rpmdb.available?(pkg)
+ end
+ if is_installed
+ next unless @rpmdb.installed?(pkg)
+ end
+ if arch
+ next unless pkg.arch == arch
+ end
+
+ if block_given?
+ yield pkg
+ else
+ # first match is latest version
+ return pkg
+ end
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ # Parse provides from yum-dump.py output
+ def parse_provides(string)
+ ret = []
+ # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
+ string.split(", ").each do |seg|
+ # 'atk = 1.12.2-1.fc6'
+ if seg =~ %r{^'(.*)'$}
+ ret << RPMProvide.parse($1)
+ end
+ end
+
+ return ret
+ end
+
+ end # YumCache
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(new_resource, run_context)
+ super
+
+ @yum = YumCache.instance
+ end
+
+ # Extra attributes
+ #
+
+ def arch
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch
+ else
+ nil
+ end
+ end
+
+ def flush_cache
+ if @new_resource.respond_to?("flush_cache")
+ @new_resource.flush_cache
+ else
+ { :before => false, :after => false }
+ end
+ end
+
+ def allow_downgrade
+ if @new_resource.respond_to?("allow_downgrade")
+ @new_resource.allow_downgrade
+ else
+ false
+ end
+ end
+
+ # Helpers
+ #
+
+ def yum_arch
+ arch ? ".#{arch}" : nil
+ end
+
+ def yum_command(command)
+ status, stdout, stderr = output_of_command(command, {})
+
+ # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
+ # considered fatal - meaning the rpm is still successfully installed. These issue
+ # cause yum to emit a non fatal warning but still exit(1). As there's currently no
+ # way to suppress this behavior and an exit(1) will break a Chef run we make an
+ # effort to trap these and re-run the same install command - it will either fail a
+ # second time or succeed.
+ #
+ # A cleaner solution would have to be done in python and better hook into
+ # yum/rpm to handle exceptions as we see fit.
+ if status.exitstatus == 1
+ stdout.each_line do |l|
+ # rpm-4.4.2.3 lib/psm.c line 2182
+ if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
+ Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
+ "so running install again to verify.")
+ status, stdout, stderr = output_of_command(command, {})
+ break
+ end
+ end
+ end
+
+ if status.exitstatus > 0
+ command_output = "STDOUT: #{stdout}"
+ command_output << "STDERR: #{stderr}"
+ handle_command_failures(status, command_output, {})
+ end
+ end
+
+ # Standard Provider methods for Parent
+ #
+
+ def load_current_resource
+ if flush_cache[:before]
+ @yum.reload
+ end
+
+ # At this point package_name could be:
+ #
+ # 1) a package name, eg: "foo"
+ # 2) a package name.arch, eg: "foo.i386"
+ # 3) or a dependency, eg: "foo >= 1.1"
+
+ # Check if we have name or name+arch which has a priority over a dependency
+ unless @yum.package_available?(@new_resource.package_name)
+ # If they aren't in the installed packages they could be a dependency
+ parse_dependency
+ end
+
+ # Don't overwrite an existing arch
+ unless arch
+ parse_arch
+ end
+
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ if @new_resource.source
+ unless ::File.exists?(@new_resource.source)
+ raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ end
+
+ Chef::Log.debug("#{@new_resource} checking rpm status")
+ status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ @current_resource.package_name($1)
+ @new_resource.version($2)
+ end
+ end
+ end
+ end
+
+ if @new_resource.version
+ new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
+ else
+ new_resource = "#{@new_resource.package_name}#{yum_arch}"
+ end
+
+ Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+
+ installed_version = @yum.installed_version(@new_resource.package_name, arch)
+ @current_resource.version(installed_version)
+
+ @candidate_version = @yum.candidate_version(@new_resource.package_name, arch)
+
+ Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
+ "#{@candidate_version || "(none)"}")
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ if @new_resource.source
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ else
+ # Work around yum not exiting with an error if a package doesn't exist for CHEF-2062
+ if @yum.version_available?(name, version, arch)
+ method = "install"
+ log_method = "installing"
+
+ # More Yum fun:
+ #
+ # yum install of an old name+version will exit(1)
+ # yum install of an old name+version+arch will exit(0) for some reason
+ #
+ # Some packages can be installed multiple times like the kernel
+ unless @yum.allow_multi_install.include?(name)
+ if RPMVersion.parse(@current_resource.version) > RPMVersion.parse(version)
+ # Unless they want this...
+ if allow_downgrade
+ method = "downgrade"
+ log_method = "downgrading"
+ else
+ # we bail like yum when the package is older
+ raise Chef::Exceptions::Package, "Installed package #{name}-#{@current_resource.version} is newer " +
+ "than candidate package #{name}-#{version}"
+ end
+ end
+ end
+
+ repo = @yum.package_repository(name, version, arch)
+ Chef::Log.info("#{@new_resource} #{log_method} #{name}-#{version}#{yum_arch} from #{repo} repository")
+
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{name}-#{version}#{yum_arch}")
+ else
+ raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
+ "and release? (version-release, e.g. 1.84-10.fc6)"
+ end
+ end
+
+ if flush_cache[:after]
+ @yum.reload
+ else
+ @yum.reload_installed
+ end
+ end
+
+ # Keep upgrades from trying to install an older candidate version. Can happen when a new
+ # version is installed then removed from a repository, now the older available version
+ # shows up as a viable install candidate.
+ #
+ # Can be done in upgrade_package but an upgraded from->to log message slips out
+ #
+ # Hacky - better overall solution? Custom compare in Package provider?
+ def action_upgrade
+ # Could be uninstalled or have no candidate
+ if @current_resource.version.nil? || candidate_version.nil?
+ super
+ # Ensure the candidate is newer
+ elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
+ super
+ else
+ Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if version
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}-#{version}#{yum_arch}")
+ else
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}#{yum_arch}")
+ end
+
+ if flush_cache[:after]
+ @yum.reload
+ else
+ @yum.reload_installed
+ end
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ private
+
+ def parse_arch
+ # Allow for foo.x86_64 style package_name like yum uses in it's output
+ #
+ if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+ new_package_name = $1
+ new_arch = $2
+ # foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
+ # Ensure we don't have an existing package matching package_name, then ensure we at
+ # least have a match for the new_package+new_arch before we overwrite. If neither
+ # then fall through to standard package handling.
+ if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
+ (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
+ @new_resource.package_name(new_package_name)
+ @new_resource.arch(new_arch)
+ end
+ end
+ end
+
+ # If we don't have the package we could have been passed a 'whatprovides' feature
+ #
+ # eg: yum install "perl(Config)"
+ # yum install "mtr = 2:0.71-3.1"
+ # yum install "mtr > 2:0.71"
+ #
+ # We support resolving these out of the Provides data imported from yum-dump.py and
+ # matching them up with an actual package so the standard resource handling can apply.
+ #
+ # There is currently no support for filename matching.
+ def parse_dependency
+ # Transform the package_name into a requirement
+ yum_require = RPMRequire.parse(@new_resource.package_name)
+ # and gather all the packages that have a Provides feature satisfying the requirement.
+ # It could be multiple be we can only manage one
+ packages = @yum.packages_from_require(yum_require)
+
+ if packages.empty?
+ # Don't bother if we are just ensuring a package is removed - we don't need Provides data
+ actions = Array(@new_resource.action)
+ unless actions.size == 1 and (actions[0] == :remove || actions[0] == :purge)
+ Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " +
+ "installed Provides, loading available Provides - this may take a moment")
+ @yum.reload_provides
+ packages = @yum.packages_from_require(yum_require)
+ end
+ end
+
+ unless packages.empty?
+ new_package_name = packages.first.name
+ Chef::Log.debug("#{@new_resource} no package found for #{@new_resource.package_name} " +
+ "but matched Provides for #{new_package_name}")
+
+ # Ensure it's not the same package under a different architecture
+ unique_names = []
+ packages.each do |pkg|
+ unique_names << "#{pkg.name}-#{pkg.version.evr}"
+ end
+ unique_names.uniq!
+
+ if unique_names.size > 1
+ Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " +
+ "but we can only use the first match: #{new_package_name}. Please use a more " +
+ "specific version.")
+ end
+
+ @new_resource.package_name(new_package_name)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
new file mode 100644
index 0000000000..43727466e2
--- /dev/null
+++ b/lib/chef/provider/package/zypper.rb
@@ -0,0 +1,144 @@
+#
+# 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/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'singleton'
+
+class Chef
+ class Provider
+ class Package
+ class Zypper < Chef::Provider::Package
+
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ is_installed=false
+ is_out_of_date=false
+ version=''
+ oud_version=''
+ Chef::Log.debug("#{@new_resource} checking zypper")
+ status = popen4("zypper info #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /^Version: (.+)$/
+ version = $1
+ Chef::Log.debug("#{@new_resource} version #{$1}")
+ when /^Installed: Yes$/
+ is_installed=true
+ Chef::Log.debug("#{@new_resource} is installed")
+
+ when /^Installed: No$/
+ is_installed=false
+ Chef::Log.debug("#{@new_resource} is not installed")
+ when /^Status: out-of-date \(version (.+) installed\)$/
+ is_out_of_date=true
+ oud_version=$1
+ Chef::Log.debug("#{@new_resource} out of date version #{$1}")
+ end
+ end
+ end
+
+ if is_installed==false
+ @candidate_version=version
+ @current_resource.version(nil)
+ end
+
+ if is_installed==true
+ if is_out_of_date==true
+ @current_resource.version(oud_version)
+ @candidate_version=version
+ else
+ @current_resource.version(version)
+ @candidate_version=version
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ #Gets the zypper Version from command output (Returns Floating Point number)
+ def zypper_version()
+ `zypper -V 2>&1`.scan(/\d+/).join(".").to_f
+ end
+
+ def install_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper install -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}"
+ )
+ end
+ end
+
+ def upgrade_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper install -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}"
+ )
+ end
+ end
+
+ def remove_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper remove -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks remove #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks remove #{name}"
+ )
+ end
+
+
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
new file mode 100644
index 0000000000..9ccd7ea056
--- /dev/null
+++ b/lib/chef/provider/remote_directory.rb
@@ -0,0 +1,174 @@
+#
+# 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/provider/file'
+require 'chef/provider/directory'
+require 'chef/resource/directory'
+require 'chef/resource/remote_file'
+require 'chef/mixin/file_class'
+require 'chef/platform'
+require 'uri'
+require 'tempfile'
+require 'net/https'
+require 'set'
+
+class Chef
+ class Provider
+ class RemoteDirectory < Chef::Provider::Directory
+ include Chef::Mixin::FileClass
+
+ def action_create
+ super
+
+ files_to_purge = Set.new(
+ Dir.glob(::File.join(@new_resource.path, '**', '*'), ::File::FNM_DOTMATCH).select do |name|
+ name !~ /(?:^|#{Regexp.escape(::File::SEPARATOR)})\.\.?$/
+ end
+ )
+ 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]))
+ end
+ end
+ purge_unmanaged_files(files_to_purge)
+ end
+
+ def action_create_if_missing
+ # if this action is called, ignore the existing overwrite flag
+ @new_resource.overwrite(false)
+ action_create
+ end
+
+ protected
+
+ def purge_unmanaged_files(unmanaged_files)
+ if @new_resource.purge
+ unmanaged_files.sort.reverse.each do |f|
+ # file_class comes from Chef::Mixin::FileClass
+ if ::File.directory?(f) && !Chef::Platform.windows? && !file_class.symlink?(f.dup)
+ # Linux treats directory symlinks as files
+ # Remove a directory as a directory when not on windows if it is not a symlink
+ purge_directory(f)
+ elsif ::File.directory?(f) && Chef::Platform.windows?
+ # Windows treats directory symlinks as directories so we delete them here
+ purge_directory(f)
+ else
+ converge_by("delete unmanaged file #{f}") do
+ ::File.delete(f)
+ Chef::Log.debug("#{@new_resource} deleted file #{f}")
+ end
+ end
+ end
+ end
+ end
+
+ def purge_directory(dir)
+ converge_by("delete unmanaged directory #{dir}") do
+ Dir::rmdir(dir)
+ Chef::Log.debug("#{@new_resource} removed directory #{dir}")
+ end
+ end
+
+ def files_to_transfer
+ cookbook = run_context.cookbook_collection[resource_cookbook]
+ files = cookbook.relative_filenames_in_preferred_directory(node, :files, @new_resource.source)
+ files.sort.reverse
+ end
+
+ def directory_root_in_cookbook_cache
+ @directory_root_in_cookbook_cache ||= begin
+ cookbook = run_context.cookbook_collection[resource_cookbook]
+ cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
+ end
+ end
+
+ # Determine the cookbook to get the file from. If new resource sets an
+ # explicit cookbook, use it, otherwise fall back to the implicit cookbook
+ # i.e., the cookbook the resource was declared in.
+ def resource_cookbook
+ @new_resource.cookbook || @new_resource.cookbook_name
+ end
+
+ def create_cookbook_file(cookbook_file_relative_path)
+ full_path = ::File.join(@new_resource.path, cookbook_file_relative_path)
+
+ ensure_directory_exists(::File.dirname(full_path))
+
+ file_to_fetch = cookbook_file_resource(full_path, cookbook_file_relative_path)
+ if @new_resource.overwrite
+ file_to_fetch.run_action(:create)
+ else
+ file_to_fetch.run_action(:create_if_missing)
+ end
+ @new_resource.updated_by_last_action(true) if file_to_fetch.updated?
+ end
+
+ def cookbook_file_resource(target_path, relative_source_path)
+ cookbook_file = Chef::Resource::CookbookFile.new(target_path, run_context)
+ cookbook_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
+ cookbook_file.source(::File.join(@new_resource.source, relative_source_path))
+ if Chef::Platform.windows? && @new_resource.files_rights
+ @new_resource.files_rights.each_pair do |permission, *args|
+ cookbook_file.rights(permission, *args)
+ end
+ end
+ cookbook_file.mode(@new_resource.files_mode) if @new_resource.files_mode
+ cookbook_file.group(@new_resource.files_group) if @new_resource.files_group
+ cookbook_file.owner(@new_resource.files_owner) if @new_resource.files_owner
+ cookbook_file.backup(@new_resource.files_backup) if @new_resource.files_backup
+
+ cookbook_file
+ end
+
+ def ensure_directory_exists(path)
+ unless ::File.directory?(path)
+ directory_to_create = resource_for_directory(path)
+ directory_to_create.run_action(:create)
+ @new_resource.updated_by_last_action(true) if directory_to_create.updated?
+ end
+ end
+
+ def resource_for_directory(path)
+ dir = Chef::Resource::Directory.new(path, run_context)
+ dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
+ if Chef::Platform.windows? && @new_resource.rights
+ # rights are only meant to be applied to the toppest-level directory;
+ # Windows will handle inheritance.
+ if path == @new_resource.path
+ @new_resource.rights.each do |rights| #rights is a hash
+ permissions = rights.delete(:permissions) #delete will return the value or nil if not found
+ principals = rights.delete(:principals)
+ dir.rights(permissions, principals, rights)
+ end
+ end
+ end
+ dir.mode(@new_resource.mode) if @new_resource.mode
+ dir.group(@new_resource.group)
+ dir.owner(@new_resource.owner)
+ dir.recursive(true)
+ dir
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
new file mode 100644
index 0000000000..90e367f558
--- /dev/null
+++ b/lib/chef/provider/remote_file.rb
@@ -0,0 +1,138 @@
+#
+# 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/provider/file'
+require 'chef/rest'
+require 'uri'
+require 'tempfile'
+require 'net/https'
+
+class Chef
+ class Provider
+ class RemoteFile < Chef::Provider::File
+
+ def load_current_resource
+ @current_resource = Chef::Resource::RemoteFile.new(@new_resource.name)
+ super
+ end
+
+ def action_create
+ Chef::Log.debug("#{@new_resource} checking for changes")
+
+ if current_resource_matches_target_checksum?
+ Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
+ else
+ sources = @new_resource.source
+ source = sources.shift
+ begin
+ rest = Chef::REST.new(source, nil, nil, http_client_opts(source))
+ raw_file = rest.streaming_request(rest.create_url(source), {})
+ rescue SocketError, Errno::ECONNREFUSED, Timeout::Error, Net::HTTPFatalError => e
+ Chef::Log.debug("#{@new_resource} cannot be downloaded from #{source}")
+ if source = sources.shift
+ Chef::Log.debug("#{@new_resource} trying to download from another mirror")
+ retry
+ else
+ raise e
+ end
+ end
+ if matches_current_checksum?(raw_file)
+ Chef::Log.debug "#{@new_resource} target and source checksums are the same - not updating"
+ else
+ description = []
+ description << "copy file downloaded from #{@new_resource.source} into #{@new_resource.path}"
+ description << diff_current(raw_file.path)
+ converge_by(description) do
+ backup_new_resource
+ FileUtils.cp raw_file.path, @new_resource.path
+ Chef::Log.info "#{@new_resource} updated"
+ raw_file.close!
+ end
+ # whyrun mode cleanup - the temp file will never be used,
+ # so close/unlink it here.
+ if whyrun_mode?
+ raw_file.close!
+ end
+ end
+ end
+ set_all_access_controls
+ end
+
+ def current_resource_matches_target_checksum?
+ @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/
+ end
+
+ def matches_current_checksum?(candidate_file)
+ Chef::Log.debug "#{@new_resource} checking for file existence of #{@new_resource.path}"
+ if ::File.exists?(@new_resource.path)
+ Chef::Log.debug "#{@new_resource} file exists at #{@new_resource.path}"
+ @new_resource.checksum(checksum(candidate_file.path))
+ Chef::Log.debug "#{@new_resource} target checksum: #{@current_resource.checksum}"
+ Chef::Log.debug "#{@new_resource} source checksum: #{@new_resource.checksum}"
+
+ @new_resource.checksum == @current_resource.checksum
+ else
+ Chef::Log.debug "#{@new_resource} creating #{@new_resource.path}"
+ false
+ end
+ end
+
+ def backup_new_resource
+ if ::File.exists?(@new_resource.path)
+ Chef::Log.debug "#{@new_resource} checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}"
+ backup @new_resource.path
+ end
+ end
+
+ def source_file(source, current_checksum, &block)
+ if absolute_uri?(source)
+ fetch_from_uri(source, &block)
+ elsif !Chef::Config[:solo]
+ fetch_from_chef_server(source, current_checksum, &block)
+ else
+ fetch_from_local_cookbook(source, &block)
+ end
+ end
+
+ def http_client_opts(source)
+ opts={}
+ # CHEF-3140
+ # 1. If it's already compressed, trying to compress it more will
+ # probably be counter-productive.
+ # 2. Some servers are misconfigured so that you GET $URL/file.tgz but
+ # they respond with content type of tar and content encoding of gzip,
+ # which tricks Chef::REST into decompressing the response body. In this
+ # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
+ # which is not what you wanted.
+ if @new_resource.path =~ /gz$/ or source =~ /gz$/
+ opts[:disable_gzip] = true
+ end
+ opts
+ end
+
+ private
+
+ def absolute_uri?(source)
+ URI.parse(source).absolute?
+ rescue URI::InvalidURIError
+ false
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/resource_update.rb b/lib/chef/provider/resource_update.rb
new file mode 100644
index 0000000000..e2c6bffca4
--- /dev/null
+++ b/lib/chef/provider/resource_update.rb
@@ -0,0 +1,55 @@
+
+class Chef
+ class Provider
+
+ # {
+ # "run_id" : "1000",
+ # "resource" : {
+ # "type" : "file",
+ # "name" : "/etc/passwd",
+ # "start_time" : "2012-01-09T08:15:30-05:00",
+ # "end_time" : "2012-01-09T08:15:30-05:00",
+ # "status" : "modified",
+ # "initial_state" : "exists",
+ # "final_state" : "modified",
+ # "before" : {
+ # "group" : "root",
+ # "owner" : "root",
+ # "checksum" : "xyz"
+ # },
+ # "after" : {
+ # "group" : "root",
+ # "owner" : "root",
+ # "checksum" : "abc"
+ # },
+ # "delta" : "escaped delta goes here"
+ # },
+ # "event_data" : ""
+ # }
+
+ class ResourceUpdate
+
+ attr_accessor :type
+ attr_accessor :name
+ attr_accessor :duration #ms
+ attr_accessor :status
+ attr_accessor :initial_state
+ attr_accessor :final_state
+ attr_accessor :initial_properties
+ attr_accessor :final_properties
+ attr_accessor :event_data # e.g., a diff.
+
+ def initial_state_from_resource(resource)
+ @initial_properties = resource.to_hash
+ end
+
+ def updated_state_from_resource(resource)
+ @final_properties = resource.to_hash
+ end
+
+ end
+ end
+end
+
+
+
diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb
new file mode 100644
index 0000000000..5aedcb99ec
--- /dev/null
+++ b/lib/chef/provider/route.rb
@@ -0,0 +1,223 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org), Jesse Nelson (spheromak@gmail.com)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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/mixin/command'
+require 'chef/provider'
+require 'ipaddr'
+
+class Chef::Provider::Route < Chef::Provider
+ include Chef::Mixin::Command
+
+ attr_accessor :is_running
+
+ MASK = {'0.0.0.0' => '0',
+ '128.0.0.0' => '1',
+ '192.0.0.0' => '2',
+ '224.0.0.0' => '3',
+ '240.0.0.0' => '4',
+ '248.0.0.0' => '5',
+ '252.0.0.0' => '6',
+ '254.0.0.0' => '7',
+ '255.0.0.0' => '8',
+ '255.128.0.0' => '9',
+ '255.192.0.0' => '10',
+ '255.224.0.0' => '11',
+ '255.240.0.0' => '12',
+ '255.248.0.0' => '13',
+ '255.252.0.0' => '14',
+ '255.254.0.0' => '15',
+ '255.255.0.0' => '16',
+ '255.255.128.0' => '17',
+ '255.255.192.0' => '18',
+ '255.255.224.0' => '19',
+ '255.255.240.0' => '20',
+ '255.255.248.0' => '21',
+ '255.255.252.0' => '22',
+ '255.255.254.0' => '23',
+ '255.255.255.0' => '24',
+ '255.255.255.128' => '25',
+ '255.255.255.192' => '26',
+ '255.255.255.224' => '27',
+ '255.255.255.240' => '28',
+ '255.255.255.248' => '29',
+ '255.255.255.252' => '30',
+ '255.255.255.254' => '31',
+ '255.255.255.255' => '32' }
+
+ def hex2ip(hex_data)
+ # Cleanup hex data
+ hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, '')
+
+ # Check hex data format (IP is a 32bit integer, so should be 8 chars long)
+ return nil if hex_ip.length != hex_data.length || hex_ip.length != 8
+
+ # Extract octets from hex data
+ octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack('H2').unpack("C").first }
+
+ # Validate IP
+ ip = octets.join('.')
+ begin
+ IPAddr.new(ip, Socket::AF_INET).to_s
+ rescue ArgumentError
+ Chef::Log.debug("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}")
+ return nil
+ end
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ self.is_running = false
+
+ # cidr or quad dot mask
+ if @new_resource.netmask
+ new_ip = IPAddr.new("#{@new_resource.target}/#{@new_resource.netmask}")
+ else
+ new_ip = IPAddr.new(@new_resource.target)
+ end
+
+ # For linux, we use /proc/net/route file to read proc table info
+ if node[:os] == "linux"
+ route_file = ::File.open("/proc/net/route", "r")
+
+ # Read all routes
+ while (line = route_file.gets)
+ # Get all the fields for a route
+ iface,destination,gateway,flags,refcnt,use,metric,mask,mtu,window,irtt = line.split
+
+ # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0)
+ destination = hex2ip(destination)
+ gateway = hex2ip(gateway)
+ mask = hex2ip(mask)
+
+ # Skip formatting lines (header, etc)
+ next unless destination && gateway && mask
+ Chef::Log.debug("#{@new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}")
+
+ # check if what were trying to configure is already there
+ # use an ipaddr object with ip/mask this way we can have
+ # a new resource be in cidr format (i don't feel like
+ # expanding bitmask by hand.
+ #
+ running_ip = IPAddr.new("#{destination}/#{mask}")
+ Chef::Log.debug("#{@new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}")
+ self.is_running = true if running_ip == new_ip && gateway == @new_resource.gateway
+ end
+
+ route_file.close
+ end
+ end
+
+ def action_add
+ # check to see if load_current_resource found the route
+ if is_running
+ Chef::Log.debug("#{@new_resource} route already active - nothing to do")
+ else
+ command = generate_command(:add)
+ converge_by ("run #{ command } to add route") do
+ run_command( :command => command )
+ Chef::Log.info("#{@new_resource} added")
+ end
+ end
+
+ #for now we always write the file (ugly but its what it is)
+ generate_config
+ end
+
+ def action_delete
+ if is_running
+ command = generate_command(:delete)
+ converge_by ("run #{ command } to delete route ") do
+ run_command( :command => command )
+ Chef::Log.info("#{@new_resource} removed")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} route does not exist - nothing to do")
+ end
+ end
+
+ def generate_config
+ conf = Hash.new
+ case node[:platform]
+ when "centos", "redhat", "fedora"
+ # walk the collection
+ run_context.resource_collection.each do |resource|
+ if resource.is_a? Chef::Resource::Route
+ # default to eth0
+ if resource.device
+ dev = resource.device
+ else
+ dev = "eth0"
+ end
+
+ conf[dev] = String.new if conf[dev].nil?
+ if resource.action == :add
+ conf[dev] << config_file_contents(:add, :target => resource.target, :netmask => resource.netmask, :gateway => resource.gateway)
+ else
+ # need to do this for the case when the last route on an int
+ # is removed
+ conf[dev] << config_file_contents(:delete)
+ end
+ end
+ end
+ conf.each do |k, v|
+ network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
+ converge_by ("write route route.#{k}\n#{conf[k]} to #{ network_file_name }") do
+ network_file = ::File.new(network_file_name, "w")
+ network_file.puts(conf[k])
+ Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}")
+ network_file.close
+ end
+ end
+ end
+ end
+
+ def generate_command(action)
+ common_route_items = ''
+ common_route_items << "/#{MASK[@new_resource.netmask.to_s]}" if @new_resource.netmask
+ common_route_items << " via #{@new_resource.gateway} " if @new_resource.gateway
+
+ case action
+ when :add
+ command = "ip route replace #{@new_resource.target}"
+ command << common_route_items
+ command << " dev #{@new_resource.device} " if @new_resource.device
+ when :delete
+ command = "ip route delete #{@new_resource.target}"
+ command << common_route_items
+ end
+
+ return command
+ end
+
+ def config_file_contents(action, options={})
+ content = ''
+ case action
+ when :add
+ content << "#{options[:target]}"
+ content << "/#{options[:netmask]}" if options[:netmask]
+ content << " via #{options[:gateway]}" if options[:gateway]
+ content << "\n"
+ end
+
+ return content
+ end
+end
diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb
new file mode 100644
index 0000000000..16908b0eff
--- /dev/null
+++ b/lib/chef/provider/ruby_block.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode
+# 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 Provider
+ class RubyBlock < Chef::Provider
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ true
+ end
+
+ def action_run
+ converge_by("execute the ruby block #{@new_resource.name}") do
+ @new_resource.block.call
+ Chef::Log.info("#{@new_resource} called")
+ end
+ end
+
+ alias :action_create :action_run
+
+ end
+ end
+end
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
new file mode 100644
index 0000000000..9e5a7d7fe1
--- /dev/null
+++ b/lib/chef/provider/script.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.
+#
+
+require 'tempfile'
+require 'chef/provider/execute'
+
+class Chef
+ class Provider
+ class Script < Chef::Provider::Execute
+
+ def action_run
+ script_file.puts(@new_resource.code)
+ script_file.close
+
+ set_owner_and_group
+
+ @new_resource.command("\"#{@new_resource.interpreter}\" #{@new_resource.flags} \"#{script_file.path}\"")
+ super
+ converge_by(nil) do
+ # ensure script is unlinked at end of converge!
+ unlink_script_file
+ end
+ end
+
+ def set_owner_and_group
+ # FileUtils itself implements a no-op if +user+ or +group+ are nil
+ # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file')
+ # as an unprivileged user.
+ FileUtils.chown(@new_resource.user, @new_resource.group, script_file.path)
+ end
+
+ def script_file
+ @script_file ||= Tempfile.open("chef-script")
+ end
+
+ def unlink_script_file
+ @script_file && @script_file.close!
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
new file mode 100644
index 0000000000..decca7fd7c
--- /dev/null
+++ b/lib/chef/provider/service.rb
@@ -0,0 +1,158 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.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/mixin/command'
+require 'chef/provider'
+
+class Chef
+ class Provider
+ class Service < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ def initialize(new_resource, run_context)
+ super
+ @enabled = nil
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_new_resource_state
+ # If the user didn't specify a change in enabled state,
+ # it will be the same as the old resource
+ if ( @new_resource.enabled.nil? )
+ @new_resource.enabled(@current_resource.enabled)
+ end
+ if ( @new_resource.running.nil? )
+ @new_resource.running(@current_resource.running)
+ end
+ end
+
+ def shared_resource_requirements
+ end
+
+ def define_resource_requirements
+ requirements.assert(:reload) do |a|
+ a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+ # if a service is not declared to support reload, that won't
+ # typically change during the course of a run - so no whyrun
+ # alternative here.
+ end
+ end
+
+ def action_enable
+ if @current_resource.enabled
+ Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+ else
+ converge_by("enable service #{@new_resource}") do
+ enable_service
+ Chef::Log.info("#{@new_resource} enabled")
+ end
+ end
+ load_new_resource_state
+ @new_resource.enabled(true)
+ end
+
+ def action_disable
+ if @current_resource.enabled
+ converge_by("disable service #{@new_resource}") do
+ disable_service
+ Chef::Log.info("#{@new_resource} disabled")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
+ end
+ load_new_resource_state
+ @new_resource.enabled(false)
+ end
+
+ def action_start
+ unless @current_resource.running
+ converge_by("start service #{@new_resource}") do
+ start_service
+ Chef::Log.info("#{@new_resource} started")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already running - nothing to do")
+ end
+ load_new_resource_state
+ @new_resource.running(true)
+ end
+
+ def action_stop
+ if @current_resource.running
+ converge_by("stop service #{@new_resource}") do
+ stop_service
+ Chef::Log.info("#{@new_resource} stopped")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already stopped - nothing to do")
+ end
+ load_new_resource_state
+ @new_resource.running(false)
+ end
+
+ def action_restart
+ converge_by("restart service #{@new_resource}") do
+ restart_service
+ Chef::Log.info("#{@new_resource} restarted")
+ end
+ load_new_resource_state
+ @new_resource.running(true)
+ end
+
+ def action_reload
+ if @current_resource.running
+ converge_by("disable service #{@new_resource}") do
+ reload_service
+ Chef::Log.info("#{@new_resource} reloaded")
+ end
+ end
+ load_new_resource_state
+ end
+
+ def enable_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+ end
+
+ def disable_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+ end
+
+ def start_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :start"
+ end
+
+ def stop_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :stop"
+ end
+
+ def restart_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart"
+ end
+
+ def reload_service
+ raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart"
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb
new file mode 100644
index 0000000000..8c8216c37f
--- /dev/null
+++ b/lib/chef/provider/service/arch.rb
@@ -0,0 +1,113 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek@web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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/provider/service/init'
+require 'chef/mixin/command'
+
+class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
+
+ def initialize(new_resource, run_context)
+ super
+ @init_command = "/etc/rc.d/#{@new_resource.service_name}"
+ end
+
+ def load_current_resource
+ raise Chef::Exceptions::Service, "Could not find /etc/rc.conf" unless ::File.exists?("/etc/rc.conf")
+ raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf" unless ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m)
+ super
+
+ @current_resource.enabled(daemons.include?(@current_resource.service_name))
+ @current_resource
+ end
+
+ # Get list of all daemons from the file '/etc/rc.conf'.
+ # Mutiple lines and background form are supported. Example:
+ # DAEMONS=(\
+ # foobar \
+ # @example \
+ # !net \
+ # )
+ def daemons
+ entries = []
+ if ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m)
+ entries += $1.gsub(/\\?[\r\n]/, ' ').gsub(/# *[^ ]+/,' ').split(' ') if $1.length > 0
+ end
+
+ yield(entries) if block_given?
+
+ entries
+ end
+
+ # FIXME: Multiple entries of DAEMONS will cause very bad results :)
+ def update_daemons(entries)
+ content = ::File.read("/etc/rc.conf").gsub(/DAEMONS=\((.*)\)/m, "DAEMONS=(#{entries.join(' ')})")
+ ::File.open("/etc/rc.conf", "w") do |f|
+ f.write(content)
+ end
+ end
+
+ def enable_service()
+ new_daemons = []
+ entries = daemons
+
+ if entries.include?(new_resource.service_name) or entries.include?("@#{new_resource.service_name}")
+ # exists and already enabled (or already enabled as a background service)
+ # new_daemons += entries
+ else
+ if entries.include?("!#{new_resource.service_name}")
+ # exists but disabled
+ entries.each do |daemon|
+ if daemon == "!#{new_resource.service_name}"
+ new_daemons << new_resource.service_name
+ else
+ new_daemons << daemon
+ end
+ end
+ else
+ # does not exist
+ new_daemons += entries
+ new_daemons << new_resource.service_name
+ end
+ update_daemons(new_daemons)
+ end
+ end
+
+ def disable_service()
+ new_daemons = []
+ entries = daemons
+
+ if entries.include?("!#{new_resource.service_name}")
+ # exists and disabled
+ # new_daemons += entries
+ else
+ if entries.include?(new_resource.service_name) or entries.include?("@#{new_resource.service_name}")
+ # exists but enabled (or enabled as a back-ground service)
+ # FIXME: Does arch support !@foobar ?
+ entries.each do |daemon|
+ if [new_resource.service_name, "@#{new_resource.service_name}"].include?(daemon)
+ new_daemons << "!#{new_resource.service_name}"
+ else
+ new_daemons << daemon
+ end
+ end
+ end
+ update_daemons(new_daemons)
+ end
+ end
+
+end
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
new file mode 100644
index 0000000000..e2a0f60d91
--- /dev/null
+++ b/lib/chef/provider/service/debian.rb
@@ -0,0 +1,152 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service'
+require 'chef/provider/service/init'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Debian < Chef::Provider::Service::Init
+ UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
+ UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
+
+ def load_current_resource
+ super
+ @priority_success = true
+ @rcd_status = nil
+ @current_resource.priority(get_priority)
+ @current_resource.enabled(service_currently_enabled?(@current_resource.priority))
+ @current_resource
+ end
+
+ def define_resource_requirements
+ # do not call super here, inherit only shared_requirements
+ shared_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ update_rcd = "/usr/sbin/update-rc.d"
+ a.assertion { ::File.exists? update_rcd }
+ a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!"
+ # no whyrun recovery - this is a base system component of debian
+ # distros and must be present
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @priority_success }
+ a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{@rcd_status.inspect}"
+ # This can happen if the service is not yet installed,so we'll fake it.
+ a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.",
+ "Assigning temporary priorities to continue.",
+ "If this service is not properly installed prior to this point, this will fail."] do
+ temp_priorities = {"6"=>[:stop, "20"],
+ "0"=>[:stop, "20"],
+ "1"=>[:stop, "20"],
+ "2"=>[:start, "20"],
+ "3"=>[:start, "20"],
+ "4"=>[:start, "20"],
+ "5"=>[:start, "20"]}
+ @current_resource.priority(temp_priorities)
+ end
+ end
+ end
+
+ def get_priority
+ priority = {}
+
+ @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
+
+ [stdout, stderr].each do |iop|
+ iop.each_line do |line|
+ if UPDATE_RC_D_PRIORITIES =~ line
+ # priority[runlevel] = [ S|K, priority ]
+ # S = Start, K = Kill
+ # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
+ priority[$1] = [($2 == "S" ? :start : :stop), $3]
+ end
+ if line =~ UPDATE_RC_D_ENABLED_MATCHES
+ enabled = true
+ end
+ end
+ end
+ end
+
+ unless @rcd_status.exitstatus == 0
+ @priority_success = false
+ end
+ priority
+ end
+
+ def service_currently_enabled?(priority)
+ enabled = false
+ priority.each { |runlevel, arguments|
+ Chef::Log.debug("#{@new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
+ # if we are in a update-rc.d default startup runlevel && we start in this runlevel
+ if (2..5).include?(runlevel.to_i) && arguments[0] == :start
+ enabled = true
+ end
+ }
+
+ enabled
+ end
+
+ def enable_service()
+ if @new_resource.priority.is_a? Integer
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}")
+ elsif @new_resource.priority.is_a? Hash
+ # we call the same command regardless of we're enabling or disabling
+ # users passing a Hash are responsible for setting their own start priorities
+ set_priority()
+ else # No priority, go with update-rc.d defaults
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults")
+ end
+
+ end
+
+ def disable_service()
+ if @new_resource.priority.is_a? Integer
+ # Stop processes in reverse order of start using '100 - start_priority'
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .")
+ elsif @new_resource.priority.is_a? Hash
+ # we call the same command regardless of we're enabling or disabling
+ # users passing a Hash are responsible for setting their own stop priorities
+ set_priority()
+ else
+ # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .")
+ end
+ end
+
+ def set_priority()
+ args = ""
+ @new_resource.priority.each do |level, o|
+ action = o[0]
+ priority = o[1]
+ args += "#{action} #{priority} #{level} . "
+ end
+ run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
+ run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} #{args}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
new file mode 100644
index 0000000000..b875838ec2
--- /dev/null
+++ b/lib/chef/provider/service/freebsd.rb
@@ -0,0 +1,175 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/shell_out'
+require 'chef/provider/service'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Freebsd < Chef::Provider::Service::Init
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ @rcd_script_found = true
+ @enabled_state_found = false
+ # Determine if we're talking about /etc/rc.d or /usr/local/etc/rc.d
+ if ::File.exists?("/etc/rc.d/#{current_resource.service_name}")
+ @init_command = "/etc/rc.d/#{current_resource.service_name}"
+ elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}")
+ @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}"
+ else
+ @rcd_script_found = false
+ return
+ end
+ Chef::Log.debug("#{@current_resource} found at #{@init_command}")
+ determine_current_status!
+ # Default to disabled if the service doesn't currently exist
+ # at all
+ var_name = service_enable_variable_name
+ if ::File.exists?("/etc/rc.conf") && var_name
+ read_rc_conf.each do |line|
+ case line
+ when /#{Regexp.escape(var_name)}="(\w+)"/
+ @enabled_state_found = true
+ if $1 =~ /[Yy][Ee][Ss]/
+ @current_resource.enabled true
+ elsif $1 =~ /[Nn][Oo][Nn]?[Oo]?[Nn]?[Ee]?/
+ @current_resource.enabled false
+ end
+ end
+ end
+ end
+ unless @current_resource.enabled
+ Chef::Log.debug("#{@new_resource.name} enable/disable state unknown")
+ @current_resource.enabled false
+ end
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ shared_resource_requirements
+ requirements.assert(:start, :enable, :reload, :restart) do |a|
+ a.assertion { @rcd_script_found }
+ a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script"
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @enabled_state_found }
+ # for consistentcy with original behavior, this will not fail in non-whyrun mode;
+ # rather it will silently set enabled state=>false
+ a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled."
+ end
+
+ requirements.assert(:start, :enable, :reload, :restart) do |a|
+ a.assertion { @rcd_script_found && service_enable_variable_name != nil }
+ a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar"
+ # No recovery in whyrun mode - the init file is present but not correct.
+ end
+ end
+
+ def start_service
+ if @new_resource.start_command
+ super
+ else
+ shell_out!("#{@init_command} faststart")
+ end
+ end
+
+ def stop_service
+ if @new_resource.stop_command
+ super
+ else
+ shell_out!("#{@init_command} faststop")
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+
+ super
+ elsif @new_resource.supports[:restart]
+ shell_out!("#{@init_command} fastrestart")
+ else
+ stop_service
+ sleep 1
+ start_service
+ end
+ end
+
+ def read_rc_conf
+ ::File.open("/etc/rc.conf", 'r') { |file| file.readlines }
+ end
+
+ def write_rc_conf(lines)
+ ::File.open("/etc/rc.conf", 'w') do |file|
+ lines.each { |line| file.puts(line) }
+ end
+ end
+
+ # The variable name used in /etc/rc.conf for enabling this service
+ def service_enable_variable_name
+ # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf
+ # corresponding to this service
+ # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need
+ # to set mysql_enable="YES" in /etc/rc.conf$
+ if @rcd_script_found
+ ::File.open(@init_command) do |rcscript|
+ rcscript.each_line do |line|
+ if line =~ /^name="?(\w+)"?/
+ return $1 + "_enable"
+ end
+ end
+ end
+ # some scripts support multiple instances through symlinks such as openvpn.
+ # We should get the service name from rcvar.
+ Chef::Log.debug("name=\"service\" not found at #{@init_command}. falling back to rcvar")
+ sn = shell_out!("#{@init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ return sn
+ end
+ # Fallback allows us to keep running in whyrun mode when
+ # the script does not exist.
+ @new_resource.service_name
+ end
+
+ def set_service_enable(value)
+ lines = read_rc_conf
+ # Remove line that set the old value
+ lines.delete_if { |line| line =~ /#{Regexp.escape(service_enable_variable_name)}/ }
+ # And append the line that sets the new value at the end
+ lines << "#{service_enable_variable_name}=\"#{value}\""
+ write_rc_conf(lines)
+ end
+
+ def enable_service()
+ set_service_enable("YES") unless @current_resource.enabled
+ end
+
+ def disable_service()
+ set_service_enable("NO") if @current_resource.enabled
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
new file mode 100644
index 0000000000..45b5a21f9b
--- /dev/null
+++ b/lib/chef/provider/service/gentoo.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Lee Jensen (<ljensen@engineyard.com>)
+# Author:: AJ Christensen (<aj@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/provider/service'
+require 'chef/mixin/command'
+
+class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
+ def load_current_resource
+
+ @new_resource.supports[:status] = true
+ @new_resource.supports[:restart] = true
+ @found_script = false
+ super
+
+ @current_resource.enabled(
+ Dir.glob("/etc/runlevels/**/#{@current_resource.service_name}").any? do |file|
+ @found_script = true
+ exists = ::File.exists? file
+ readable = ::File.readable? file
+ Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}"
+ exists and readable
+ end
+ )
+ Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/sbin/rc-update") }
+ a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist"
+ # no whyrun recovery -t his is a core component whose presence is
+ # unlikely to be affected by what we do in the course of a chef run
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @found_script }
+ # No failure, just informational output from whyrun
+ a.whyrun "Could not find service #{@new_resource.service_name} under any runlevel"
+ end
+ end
+
+ def enable_service()
+ run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default")
+ end
+
+ def disable_service()
+ run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default")
+ end
+end
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
new file mode 100644
index 0000000000..ab843d764d
--- /dev/null
+++ b/lib/chef/provider/service/init.rb
@@ -0,0 +1,87 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.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/mixin/shell_out'
+require 'chef/provider/service'
+require 'chef/provider/service/simple'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Init < Chef::Provider::Service::Simple
+
+ include Chef::Mixin::ShellOut
+
+ def initialize(new_resource, run_context)
+ super
+ @init_command = "/etc/init.d/#{@new_resource.service_name}"
+ end
+
+ def define_resource_requirements
+ # do not call super here, inherit only shared_requirements
+ shared_resource_requirements
+ requirements.assert(:start, :stop, :restart, :reload) do |a|
+ a.assertion { ::File.exist?(@init_command) }
+ a.failure_message(Chef::Exceptions::Service, "#{@init_command} does not exist!")
+ a.whyrun("Init script '#{@init_command}' 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:
+ @status_load_success = false
+ end
+ end
+ end
+
+ def start_service
+ if @new_resource.start_command
+ super
+ else
+ shell_out!("#{@init_command} start")
+ end
+ end
+
+ def stop_service
+ if @new_resource.stop_command
+ super
+ else
+ shell_out!("#{@init_command} stop")
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ elsif @new_resource.supports[:restart]
+ shell_out!("#{@init_command} restart")
+ else
+ stop_service
+ sleep 1
+ start_service
+ end
+ end
+
+ def reload_service
+ if @new_resource.reload_command
+ super
+ elsif @new_resource.supports[:reload]
+ shell_out!("#{@init_command} reload")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
new file mode 100644
index 0000000000..32152376ee
--- /dev/null
+++ b/lib/chef/provider/service/insserv.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# 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/provider/service'
+require 'chef/provider/service/init'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Insserv < Chef::Provider::Service::Init
+
+ def load_current_resource
+ super
+
+ # Look for a /etc/rc.*/SnnSERVICE link to signifiy that the service would be started in a runlevel
+ if Dir.glob("/etc/rc**/S*#{@current_resource.service_name}").empty?
+ @current_resource.enabled false
+ else
+ @current_resource.enabled true
+ end
+
+ @current_resource
+ end
+
+ def enable_service()
+ run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}")
+ run_command(:command => "/sbin/insserv -d -f #{@new_resource.service_name}")
+ end
+
+ def disable_service()
+ run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
new file mode 100644
index 0000000000..69a17bb4fb
--- /dev/null
+++ b/lib/chef/provider/service/invokercd.rb
@@ -0,0 +1,35 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service'
+require 'chef/provider/service/init'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Invokercd < Chef::Provider::Service::Init
+
+ def initialize(new_resource, run_context)
+ super
+ @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
new file mode 100644
index 0000000000..72c02779c6
--- /dev/null
+++ b/lib/chef/provider/service/macosx.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Igor Afonov <afonov@gmail.com>
+# Copyright:: Copyright (c) 2011 Igor Afonov
+# 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/provider/service'
+
+class Chef
+ class Provider
+ class Service
+ class Macosx < Chef::Provider::Service::Simple
+ include Chef::Mixin::ShellOut
+
+ PLIST_DIRS = %w{~/Library/LaunchAgents
+ /Library/LaunchAgents
+ /Library/LaunchDaemons
+ /System/Library/LaunchAgents
+ /System/Library/LaunchDaemons }
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ @plist_size = 0
+ @plist = find_service_plist
+ set_service_status
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ #super
+ requirements.assert(:enable) do |a|
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+ end
+
+ requirements.assert(:disable) do |a|
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+ end
+
+ requirements.assert(:reload) do |a|
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @plist_size < 2 }
+ a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name."
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @plist_size > 0 }
+ # No failrue here in original code - so we also will not
+ # fail. Instead warn that the service is potentially missing
+ a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do
+ @current_resource.enabled(false)
+ @current_resource.running(false)
+ end
+ end
+
+ end
+
+ def start_service
+ if @current_resource.running
+ Chef::Log.debug("#{@new_resource} already running, not starting")
+ else
+ if @new_resource.start_command
+ super
+ else
+ shell_out!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ end
+ end
+ end
+
+ def stop_service
+ unless @current_resource.running
+ Chef::Log.debug("#{@new_resource} not running, not stopping")
+ else
+ if @new_resource.stop_command
+ super
+ else
+ shell_out!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ end
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ else
+ stop_service
+ sleep 1
+ start_service
+ end
+ end
+
+
+ def set_service_status
+ return if @plist == nil
+
+ @current_resource.enabled(!@plist.nil?)
+
+ if @current_resource.enabled
+ @owner_uid = ::File.stat(@plist).uid
+ @owner_gid = ::File.stat(@plist).gid
+
+ shell_out!("launchctl list", :user => @owner_uid, :group => @owner_gid).stdout.each_line do |line|
+ case line
+ when /(\d+|-)\s+(?:\d+|-)\s+(.*\.?)#{@current_resource.service_name}/
+ pid = $1
+ @current_resource.running(!pid.to_i.zero?)
+ end
+ end
+ else
+ @current_resource.running(false)
+ end
+ end
+
+ private
+
+ def find_service_plist
+ plists = PLIST_DIRS.inject([]) do |results, dir|
+ entries = Dir.glob("#{::File.expand_path(dir)}/*#{@current_resource.service_name}*.plist")
+ entries.any? ? results << entries : results
+ end
+ plists.flatten!
+ @plist_size = plists.size
+ plists.first
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
new file mode 100644
index 0000000000..629e4ee0c3
--- /dev/null
+++ b/lib/chef/provider/service/redhat.rb
@@ -0,0 +1,77 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service'
+require 'chef/provider/service/init'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Service
+ class Redhat < Chef::Provider::Service::Init
+ include Chef::Mixin::ShellOut
+
+ CHKCONFIG_ON = /\d:on/
+ CHKCONFIG_MISSING = /No such/
+
+ def initialize(new_resource, run_context)
+ super
+ @init_command = "/sbin/service #{@new_resource.service_name}"
+ @new_resource.supports[:status] = true
+ @service_missing = false
+ end
+
+ def define_resource_requirements
+ shared_resource_requirements
+
+ requirements.assert(:all_actions) do |a|
+ chkconfig_file = "/sbin/chkconfig"
+ a.assertion { ::File.exists? chkconfig_file }
+ a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+ end
+
+ requirements.assert(:start, :enable, :reload, :restart) do |a|
+ a.assertion { !@service_missing }
+ a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+ a.whyrun "Assuming service would be disabled. The init script is not presently installed."
+ end
+ end
+
+ def load_current_resource
+ super
+
+ if ::File.exists?("/sbin/chkconfig")
+ chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
+ @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
+ end
+
+ @current_resource
+ end
+
+ def enable_service()
+ shell_out! "/sbin/chkconfig #{@new_resource.service_name} on"
+ end
+
+ def disable_service()
+ shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
new file mode 100644
index 0000000000..670c62d480
--- /dev/null
+++ b/lib/chef/provider/service/simple.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Mathieu Sauve-Frankel <msf@kisoku.net>
+# Copyright:: Copyright (c) 2009 Mathieu Sauve-Frankel
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/shell_out'
+require 'chef/provider/service'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Simple < Chef::Provider::Service
+
+ include Chef::Mixin::ShellOut
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+
+ @status_load_success = true
+ @ps_command_failed = false
+
+ determine_current_status!
+
+ @current_resource
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def shared_resource_requirements
+ super
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @status_load_success }
+ a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."]
+ end
+ end
+
+ def define_resource_requirements
+ # FIXME? need reload from service.rb
+ shared_resource_requirements
+ requirements.assert(:start) do |a|
+ a.assertion { @new_resource.start_command }
+ a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that start_command be set"
+ end
+ requirements.assert(:stop) do |a|
+ a.assertion { @new_resource.stop_command }
+ a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that stop_command be set"
+ end
+
+ requirements.assert(:restart) do |a|
+ a.assertion { @new_resource.restart_command || ( @new_resource.start_command && @new_resource.stop_command ) }
+ a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires a restart_command or both start_command and stop_command be set in order to perform a restart"
+ end
+
+ requirements.assert(:reload) do |a|
+ a.assertion { @new_resource.reload_command }
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} requires a reload_command be set in order to perform a reload"
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+ (!ps_cmd.nil? and !ps_cmd.empty?) }
+ a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !@ps_command_failed }
+ a.failure_message Chef::Exceptions::Service, "Command #{ps_cmd} failed to execute, cannot determine service current status"
+ end
+ end
+
+ def start_service
+ shell_out!(@new_resource.start_command)
+ end
+
+ def stop_service
+ shell_out!(@new_resource.stop_command)
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ shell_out!(@new_resource.restart_command)
+ else
+ stop_service
+ sleep 1
+ start_service
+ end
+ end
+
+ def reload_service
+ shell_out!(@new_resource.reload_command)
+ end
+
+ protected
+ def determine_current_status!
+ if @new_resource.status_command
+ Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+
+ begin
+ if shell_out(@new_resource.status_command).exitstatus == 0
+ @current_resource.running true
+ Chef::Log.debug("#{@new_resource} is running")
+ end
+ rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+ # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+ # Temporarily catching different types of exceptions here until we get Shellout fixed.
+ # TODO: Remove the line before one we get the ShellOut fix.
+ @status_load_success = false
+ @current_resource.running false
+ nil
+ end
+
+ elsif @new_resource.supports[:status]
+ Chef::Log.debug("#{@new_resource} supports status, running")
+ begin
+ if shell_out("#{@init_command} status").exitstatus == 0
+ @current_resource.running true
+ Chef::Log.debug("#{@new_resource} is running")
+ end
+ # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+ # Temporarily catching different types of exceptions here until we get Shellout fixed.
+ # TODO: Remove the line before one we get the ShellOut fix.
+ rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+ @status_load_success = false
+ @current_resource.running false
+ nil
+ end
+ else
+ Chef::Log.debug "#{@new_resource} falling back to process table inspection"
+ r = Regexp.new(@new_resource.pattern)
+ Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
+ begin
+ shell_out!(ps_cmd).stdout.each_line do |line|
+ if r.match(line)
+ @current_resource.running true
+ break
+ end
+ end
+
+ @current_resource.running false unless @current_resource.running
+ Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+ # Temporarily catching different types of exceptions here until we get Shellout fixed.
+ # TODO: Remove the line before one we get the ShellOut fix.
+ rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+ @ps_command_failed = true
+ end
+ end
+ end
+
+ def ps_cmd
+ @run_context.node[:command] && @run_context.node[:command][:ps]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
new file mode 100644
index 0000000000..8e131590e8
--- /dev/null
+++ b/lib/chef/provider/service/solaris.rb
@@ -0,0 +1,86 @@
+#
+# Author:: Toomas Pelberg (<toomasp@gmx.net>)
+# 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/provider/service'
+require 'chef/mixin/command'
+
+class Chef
+ class Provider
+ class Service
+ class Solaris < Chef::Provider::Service
+
+ def initialize(new_resource, run_context=nil)
+ super
+ @init_command = "/usr/sbin/svcadm"
+ @status_command = "/bin/svcs -l"
+ end
+
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ unless ::File.exists? "/bin/svcs"
+ raise Chef::Exceptions::Service, "/bin/svcs does not exist!"
+ end
+ @status = service_status.enabled
+ @current_resource
+ end
+
+ def enable_service
+ run_command(:command => "#{@init_command} enable #{@new_resource.service_name}")
+ return service_status.enabled
+ end
+
+ def disable_service
+ run_command(:command => "#{@init_command} disable #{@new_resource.service_name}")
+ return service_status.enabled
+ end
+
+ alias_method :stop_service, :disable_service
+ alias_method :start_service, :enable_service
+
+ def reload_service
+ run_command(:command => "#{@init_command} refresh #{@new_resource.service_name}")
+ end
+
+ def restart_service
+ disable_service
+ return enable_service
+ end
+
+ def service_status
+ status = popen4("#{@status_command} #{@current_resource.service_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /state\s+online/
+ @current_resource.enabled(true)
+ @current_resource.running(true)
+ end
+ end
+ end
+ unless @current_resource.enabled
+ @current_resource.enabled(false)
+ @current_resource.running(false)
+ end
+ @current_resource
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
new file mode 100644
index 0000000000..59b4fe1564
--- /dev/null
+++ b/lib/chef/provider/service/systemd.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.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/provider/service'
+require 'chef/provider/service/simple'
+require 'chef/mixin/command'
+
+class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ @status_check_success = true
+
+ if @new_resource.status_command
+ Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+
+ begin
+ if run_command_with_systems_locale(:command => @new_resource.status_command) == 0
+ @current_resource.running(true)
+ end
+ rescue Chef::Exceptions::Exec
+ @status_check_success = false
+ @current_resource.running(false)
+ @current_resource.enabled(false)
+ nil
+ end
+ else
+ @current_resource.running(is_active?)
+ end
+
+ @current_resource.enabled(is_enabled?)
+ @current_resource
+ end
+
+ def define_resource_requirements
+ shared_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @status_check_success }
+ # We won't stop in any case, but in whyrun warn and tell what we're doing.
+ a.whyrun ["Failed to determine status of #{@new_resource}, using command #{@new_resource.status_command}.",
+ "Assuming service would have been installed and is disabled"]
+ end
+ end
+
+ def start_service
+ if @current_resource.running
+ Chef::Log.debug("#{@new_resource} already running, not starting")
+ else
+ if @new_resource.start_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/bin/systemctl start #{@new_resource.service_name}")
+ end
+ end
+ end
+
+ def stop_service
+ unless @current_resource.running
+ Chef::Log.debug("#{@new_resource} not running, not stopping")
+ else
+ if @new_resource.stop_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/bin/systemctl stop #{@new_resource.service_name}")
+ end
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/bin/systemctl restart #{@new_resource.service_name}")
+ end
+ end
+
+ def reload_service
+ if @new_resource.reload_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/bin/systemctl reload #{@new_resource.service_name}")
+ end
+ end
+
+ def enable_service
+ run_command_with_systems_locale(:command => "/bin/systemctl enable #{@new_resource.service_name}")
+ end
+
+ def disable_service
+ run_command_with_systems_locale(:command => "/bin/systemctl disable #{@new_resource.service_name}")
+ end
+
+ def is_active?
+ run_command_with_systems_locale({:command => "/bin/systemctl is-active #{@new_resource.service_name}", :ignore_failure => true}) == 0
+ end
+
+ def is_enabled?
+ run_command_with_systems_locale({:command => "/bin/systemctl is-enabled #{@new_resource.service_name}", :ignore_failure => true}) == 0
+ end
+end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
new file mode 100644
index 0000000000..763a2aa92b
--- /dev/null
+++ b/lib/chef/provider/service/upstart.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2010 Bryan McLellan
+# 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/provider/service'
+require 'chef/provider/service/simple'
+require 'chef/mixin/command'
+require 'chef/util/file_edit'
+
+class Chef
+ class Provider
+ class Service
+ class Upstart < Chef::Provider::Service::Simple
+ UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
+
+ # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in.
+ # In chef, when we ask a service to start, we expect it to have started before performing the next step
+ # since we have top down dependencies. Which is to say we may follow witha resource next that requires
+ # that service to be running. According to [2] we can trust that sending a 'goal' such as start will not
+ # return until that 'goal' is reached, or some error has occured.
+ #
+ # [1] http://upstart.ubuntu.com/wiki/JobStates
+ # [2] http://www.netsplit.com/2008/04/27/upstart-05-events/
+
+ def initialize(new_resource, run_context)
+ # TODO: re-evaluate if this is needed after integrating cookbook fix
+ raise ArgumentError, "run_context cannot be nil" unless run_context
+ super
+
+ run_context.node
+
+ @job = @new_resource.service_name
+
+ if @new_resource.parameters
+ @new_resource.parameters.each do |key, value|
+ @job << " #{key}=#{value}"
+ end
+ end
+
+ platform, version = Chef::Platform.find_platform_and_version(run_context.node)
+ if platform == "ubuntu" && (8.04..9.04).include?(version.to_f)
+ @upstart_job_dir = "/etc/event.d"
+ @upstart_conf_suffix = ""
+ else
+ @upstart_job_dir = "/etc/init"
+ @upstart_conf_suffix = ".conf"
+ end
+
+ @command_success = true # new_resource.status_command= false, means upstart used
+ @config_file_found = true
+ @upstart_command_success = true
+ end
+
+ def define_resource_requirements
+ # Do not call super, only call shared requirements
+ shared_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ if !@command_success
+ whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." :
+ "Could not determine upstart state for service"
+ end
+ a.assertion { @command_success }
+ # no failure here, just document the assumptions made.
+ a.whyrun "#{whyrun_msg} Assuming service installed and not running."
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @config_file_found }
+ # no failure here, just document the assumptions made.
+ a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled."
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+
+ # Get running/stopped state
+ # We do not support searching for a service via ps when using upstart since status is a native
+ # upstart function. We will however support status_command in case someone wants to do something special.
+ if @new_resource.status_command
+ Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+
+ begin
+ if run_command_with_systems_locale(:command => @new_resource.status_command) == 0
+ @current_resource.running true
+ end
+ rescue Chef::Exceptions::Exec
+ @command_success = false
+ @current_resource.running false
+ nil
+ end
+ else
+ begin
+ if upstart_state == "running"
+ @current_resource.running true
+ else
+ @current_resource.running false
+ end
+ rescue Chef::Exceptions::Exec
+ @command_success = false
+ @current_resource.running false
+ nil
+ end
+ end
+ # Get enabled/disabled state by reading job configuration file
+ if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}",'r') do |file|
+ while line = file.gets
+ case line
+ when /^start on/
+ Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}")
+ @current_resource.enabled true
+ break
+ when /^#start on/
+ Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}")
+ @current_resource.enabled false
+ break
+ end
+ end
+ end
+ else
+ @config_file_found = false
+ Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ @current_resource.enabled false
+ end
+
+ @current_resource
+ end
+
+ def start_service
+ # Calling start on a service that is already started will return 1
+ # Our 'goal' when we call start is to ensure the service is started
+ if @current_resource.running
+ Chef::Log.debug("#{@new_resource} already running, not starting")
+ else
+ if @new_resource.start_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/sbin/start #{@job}")
+ end
+ end
+ end
+
+ def stop_service
+ # Calling stop on a service that is already stopped will return 1
+ # Our 'goal' when we call stop is to ensure the service is stopped
+ unless @current_resource.running
+ Chef::Log.debug("#{@new_resource} not running, not stopping")
+ else
+ if @new_resource.stop_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/sbin/stop #{@job}")
+ end
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
+ # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
+ else @new_resource.supports[:restart]
+ if @current_resource.running
+ run_command_with_systems_locale(:command => "/sbin/restart #{@job}")
+ else
+ start_service
+ end
+ end
+ end
+
+ def reload_service
+ if @new_resource.reload_command
+ super
+ else
+ # upstart >= 0.6.3-4 supports reload (HUP)
+ run_command_with_systems_locale(:command => "/sbin/reload #{@job}")
+ end
+ end
+
+ # https://bugs.launchpad.net/upstart/+bug/94065
+
+ def enable_service
+ Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
+ conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ conf.search_file_replace(/^#start on/, "start on")
+ conf.write_file
+ end
+
+ def disable_service
+ Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
+ conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ conf.search_file_replace(/^start on/, "#start on")
+ conf.write_file
+ end
+
+ def upstart_state
+ command = "/sbin/status #{@job}"
+ status = popen4(command) do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ # rsyslog stop/waiting
+ # service goal/state
+ # OR
+ # rsyslog (stop) waiting
+ # service (goal) state
+ line =~ UPSTART_STATE_FORMAT
+ data = Regexp.last_match
+ return data[2]
+ end
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
new file mode 100644
index 0000000000..ba51e53bed
--- /dev/null
+++ b/lib/chef/provider/service/windows.rb
@@ -0,0 +1,163 @@
+#
+# Author:: Nuo Yan <nuo@opscode.com>
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Author:: Seth Chisamore <schisamo@opscode.com>
+# Copyright:: Copyright (c) 2010-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/mixin/shell_out'
+require 'chef/provider/service/simple'
+if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'win32/service'
+end
+
+class Chef::Provider::Service::Windows < Chef::Provider::Service
+
+ include Chef::Mixin::ShellOut
+
+ RUNNING = 'running'
+ STOPPED = 'stopped'
+ AUTO_START = 'auto start'
+ DISABLED = 'disabled'
+
+ def whyrun_supported?
+ false
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ @current_resource.running(current_state == RUNNING)
+ Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
+ @current_resource.enabled(start_type == AUTO_START)
+ Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
+ @current_resource
+ end
+
+ def start_service
+ if Win32::Service.exists?(@new_resource.service_name)
+ if current_state == RUNNING
+ Chef::Log.debug "#{@new_resource} already started - nothing to do"
+ else
+ if @new_resource.start_command
+ Chef::Log.debug "#{@new_resource} starting service using the given start_command"
+ shell_out!(@new_resource.start_command)
+ else
+ spawn_command_thread do
+ Win32::Service.start(@new_resource.service_name)
+ wait_for_state(RUNNING)
+ end
+ end
+ @new_resource.updated_by_last_action(true)
+ end
+ else
+ Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ def stop_service
+ if Win32::Service.exists?(@new_resource.service_name)
+ if current_state == RUNNING
+ if @new_resource.stop_command
+ Chef::Log.debug "#{@new_resource} stopping service using the given stop_command"
+ shell_out!(@new_resource.stop_command)
+ else
+ spawn_command_thread do
+ Win32::Service.stop(@new_resource.service_name)
+ wait_for_state(STOPPED)
+ end
+ end
+ @new_resource.updated_by_last_action(true)
+ else
+ Chef::Log.debug "#{@new_resource} already stopped - nothing to do"
+ end
+ else
+ Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ def restart_service
+ if Win32::Service.exists?(@new_resource.service_name)
+ if @new_resource.restart_command
+ Chef::Log.debug "#{@new_resource} restarting service using the given restart_command"
+ shell_out!(@new_resource.restart_command)
+ else
+ stop_service
+ start_service
+ end
+ @new_resource.updated_by_last_action(true)
+ else
+ Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ def enable_service
+ if Win32::Service.exists?(@new_resource.service_name)
+ if start_type == AUTO_START
+ Chef::Log.debug "#{@new_resource} already enabled - nothing to do"
+ else
+ Win32::Service.configure(
+ :service_name => @new_resource.service_name,
+ :start_type => Win32::Service::AUTO_START
+ )
+ @new_resource.updated_by_last_action(true)
+ end
+ else
+ Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ def disable_service
+ if Win32::Service.exists?(@new_resource.service_name)
+ if start_type == AUTO_START
+ Win32::Service.configure(
+ :service_name => @new_resource.service_name,
+ :start_type => Win32::Service::DISABLED
+ )
+ @new_resource.updated_by_last_action(true)
+ else
+ Chef::Log.debug "#{@new_resource} already disabled - nothing to do"
+ end
+ else
+ Chef::Log.debug "#{@new_resource} does not exist - nothing to do"
+ end
+ end
+
+ private
+ def current_state
+ Win32::Service.status(@new_resource.service_name).current_state
+ end
+
+ def start_type
+ Win32::Service.config_info(@new_resource.service_name).start_type
+ end
+
+ # Helper method that waits for a status to change its state since state
+ # changes aren't usually instantaneous.
+ def wait_for_state(desired_state)
+ sleep 1 until current_state == desired_state
+ end
+
+ # There ain't no party like a thread party...
+ def spawn_command_thread
+ worker = Thread.new do
+ yield
+ end
+ Timeout.timeout(60) do
+ worker.join
+ end
+ end
+end
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
new file mode 100644
index 0000000000..e1f87b4dd8
--- /dev/null
+++ b/lib/chef/provider/subversion.rb
@@ -0,0 +1,214 @@
+#
+# Author:: Daniel DeLeo (<dan@kallistec.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.
+#
+
+
+#TODO subversion and git should both extend from a base SCM provider.
+
+require 'chef/log'
+require 'chef/provider'
+require 'chef/mixin/command'
+require 'fileutils'
+
+class Chef
+ class Provider
+ class Subversion < Chef::Provider
+
+ SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/
+
+ include Chef::Mixin::Command
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Subversion.new(@new_resource.name)
+
+ unless [:export, :force_export].include?(Array(@new_resource.action).first)
+ if current_revision = find_current_revision
+ @current_resource.revision current_revision
+ end
+ end
+ end
+
+ def define_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ # Make sure the parent dir exists, or else fail.
+ # for why run, print a message explaining the potential error.
+ parent_directory = ::File.dirname(@new_resource.destination)
+ a.assertion { ::File.directory?(parent_directory) }
+ a.failure_message(Chef::Exceptions::MissingParentDirectory,
+ "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
+ a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created")
+ end
+ end
+
+ def action_checkout
+ if target_dir_non_existent_or_empty?
+ converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do
+ run_command(run_options(:command => checkout_command))
+ end
+ else
+ Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ end
+ end
+
+ def action_export
+ if target_dir_non_existent_or_empty?
+ action_force_export
+ else
+ Chef::Log.debug "#{@new_resource} export destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
+ end
+ end
+
+ def action_force_export
+ converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do
+ run_command(run_options(:command => export_command))
+ end
+ end
+
+ def action_sync
+ assert_target_directory_valid!
+ if ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ current_rev = find_current_revision
+ Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}"
+ unless current_revision_matches_target_revision?
+ converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do
+ run_command(run_options(:command => sync_command))
+ Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
+ end
+ end
+ else
+ action_checkout
+ end
+ end
+
+ def sync_command
+ c = scm :update, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.destination
+ Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}"
+ c
+ end
+
+ def checkout_command
+ c = scm :checkout, @new_resource.svn_arguments, verbose, authentication,
+ "-r#{revision_int}", @new_resource.repository, @new_resource.destination
+ Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ c
+ end
+
+ def export_command
+ args = ["--force"]
+ args << @new_resource.svn_arguments << verbose << authentication <<
+ "-r#{revision_int}" << @new_resource.repository << @new_resource.destination
+ c = scm :export, *args
+ Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}"
+ c
+ end
+
+ # If the specified revision isn't an integer ("HEAD" for example), look
+ # up the revision id by asking the server
+ # If the specified revision is an integer, trust it.
+ def revision_int
+ @revision_int ||= begin
+ if @new_resource.revision =~ /^\d+$/
+ @new_resource.revision
+ else
+ command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}")
+ status, svn_info, error_message = output_of_command(command, run_options)
+ handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
+ extract_revision_info(svn_info)
+ end
+ end
+ end
+
+ alias :revision_slug :revision_int
+
+ def find_current_revision
+ return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn"))
+ command = scm(:info)
+ status, svn_info, error_message = output_of_command(command, run_options(:cwd => cwd))
+
+ unless [0,1].include?(status.exitstatus)
+ handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
+ end
+ extract_revision_info(svn_info)
+ end
+
+ def current_revision_matches_target_revision?
+ (!@current_resource.revision.nil?) && (revision_int.strip.to_i == @current_resource.revision.strip.to_i)
+ end
+
+ def run_options(run_opts={})
+ run_opts[:user] = @new_resource.user if @new_resource.user
+ run_opts[:group] = @new_resource.group if @new_resource.group
+ run_opts
+ end
+
+ private
+
+ def cwd
+ @new_resource.destination
+ end
+
+ def verbose
+ "-q"
+ end
+
+ def extract_revision_info(svn_info)
+ repo_attrs = svn_info.lines.inject({}) do |attrs, line|
+ if line =~ SVN_INFO_PATTERN
+ property, value = $1, $2
+ attrs[property] = value
+ end
+ attrs
+ end
+ rev = (repo_attrs['Last Changed Rev'] || repo_attrs['Revision'])
+ raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty?
+ Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}"
+ rev
+ end
+
+ # If a username is configured for the SCM, return the command-line
+ # switches for that. Note that we don't need to return the password
+ # switch, since Capistrano will check for that prompt in the output
+ # and will respond appropriately.
+ def authentication
+ return "" unless @new_resource.svn_username
+ result = "--username #{@new_resource.svn_username} "
+ result << "--password #{@new_resource.svn_password} "
+ result
+ end
+
+ def scm(*args)
+ ['svn', *args].compact.join(" ")
+ end
+
+
+ def target_dir_non_existent_or_empty?
+ !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
+ end
+ def assert_target_directory_valid!
+ target_parent_directory = ::File.dirname(@new_resource.destination)
+ unless ::File.directory?(target_parent_directory)
+ msg = "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
+ raise Chef::Exceptions::MissingParentDirectory, msg
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb
new file mode 100644
index 0000000000..c937b9d980
--- /dev/null
+++ b/lib/chef/provider/template.rb
@@ -0,0 +1,117 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2008, 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/provider/file'
+require 'chef/mixin/template'
+require 'chef/mixin/checksum'
+require 'chef/file_access_control'
+
+class Chef
+ class Provider
+
+ class Template < Chef::Provider::File
+
+ include Chef::Mixin::Checksum
+ include Chef::Mixin::Template
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Template.new(@new_resource.name)
+ super
+ end
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:create, :create_if_missing) do |a|
+ a.assertion { ::File::exist?(template_location) }
+ a.failure_message "Template source #{template_location} could not be found."
+ a.whyrun "Template source #{template_location} does not exist. Assuming it would have been created."
+ a.block_action!
+ end
+ end
+
+ def action_create
+ render_with_context(template_location) do |rendered_template|
+ rendered(rendered_template)
+ update = ::File.exist?(@new_resource.path)
+ if update && content_matches?
+ Chef::Log.debug("#{@new_resource} content has not changed.")
+ set_all_access_controls
+ else
+ description = []
+ action_message = update ? "update #{@current_resource} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(@new_resource.checksum)}" :
+ "create #{@new_resource}"
+ description << action_message
+ description << diff_current(rendered_template.path)
+ converge_by(description) do
+ backup
+ FileUtils.mv(rendered_template.path, @new_resource.path)
+ Chef::Log.info("#{@new_resource} updated content")
+ access_controls.set_all!
+ stat = ::File.stat(@new_resource.path)
+
+ # template depends on the checksum not changing, and updates it
+ # itself later in the code, so we cannot set it here, as we do with
+ # all other < File child provider classes
+ @new_resource.owner(stat.uid)
+ @new_resource.mode(stat.mode & 07777)
+ @new_resource.group(stat.gid)
+ end
+ end
+ end
+ end
+
+
+ def template_location
+ @template_file_cache_location ||= begin
+ if @new_resource.local
+ @new_resource.source
+ else
+ cookbook = run_context.cookbook_collection[resource_cookbook]
+ cookbook.preferred_filename_on_disk_location(node, :templates, @new_resource.source)
+ end
+ end
+ end
+
+ def resource_cookbook
+ @new_resource.cookbook || @new_resource.cookbook_name
+ end
+
+ def rendered(rendered_template)
+ @new_resource.checksum(checksum(rendered_template.path))
+ Chef::Log.debug("Current content's checksum: #{@current_resource.checksum}")
+ Chef::Log.debug("Rendered content's checksum: #{@new_resource.checksum}")
+ end
+
+ def content_matches?
+ @current_resource.checksum == @new_resource.checksum
+ end
+
+ private
+
+ def render_with_context(template_location, &block)
+ context = {}
+ context.merge!(@new_resource.variables)
+ context[:node] = node
+ render_template(IO.read(template_location), context, &block)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
new file mode 100644
index 0000000000..e73c9de57e
--- /dev/null
+++ b/lib/chef/provider/user.rb
@@ -0,0 +1,207 @@
+#
+# 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/provider'
+require 'chef/mixin/command'
+require 'chef/resource/user'
+require 'etc'
+
+class Chef
+ class Provider
+ class User < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ attr_accessor :user_exists, :locked
+
+ def initialize(new_resource, run_context)
+ super
+ @user_exists = true
+ @locked = nil
+ @shadow_lib_ok = true
+ @group_name_resolved = true
+ end
+
+ def convert_group_name
+ if @new_resource.gid.is_a? String
+ @new_resource.gid(Etc.getgrnam(@new_resource.gid).gid)
+ end
+ rescue ArgumentError => e
+ @group_name_resolved = false
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::User.new(@new_resource.name)
+ @current_resource.username(@new_resource.username)
+
+ begin
+ user_info = Etc.getpwnam(@new_resource.username)
+ rescue ArgumentError => e
+ @user_exists = false
+ Chef::Log.debug("#{@new_resource} user does not exist")
+ user_info = nil
+ end
+
+ if user_info
+ @current_resource.uid(user_info.uid)
+ @current_resource.gid(user_info.gid)
+ @current_resource.comment(user_info.gecos)
+ @current_resource.home(user_info.dir)
+ @current_resource.shell(user_info.shell)
+ @current_resource.password(user_info.passwd)
+
+ if @new_resource.password && @current_resource.password == 'x'
+ begin
+ require 'shadow'
+ rescue LoadError
+ @shadow_lib_ok = false
+ else
+ shadow_info = Shadow::Passwd.getspnam(@new_resource.username)
+ @current_resource.password(shadow_info.sp_pwdp)
+ end
+ end
+
+ if @new_resource.gid
+ convert_group_name
+ end
+ end
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @group_name_resolved }
+ a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
+ a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously."
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @shadow_lib_ok }
+ a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!"
+ a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." +
+ "Note that user update converge may report false-positive on the basis of mismatched password. "
+ end
+ requirements.assert(:modify, :lock, :unlock) do |a|
+ a.assertion { @user_exists }
+ a.failure_message(Chef::Exceptions::User, "Cannot modify user #{@new_resource} - does not exist!")
+ a.whyrun("Assuming user #{@new_resource} would have been created")
+ end
+ end
+
+ # Check to see if the user needs any changes
+ #
+ # === Returns
+ # <true>:: If a change is required
+ # <false>:: If the users are identical
+ def compare_user
+ [ :uid, :gid, :comment, :home, :shell, :password ].any? do |user_attrib|
+ !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
+ end
+ end
+
+ def action_create
+
+ if !@user_exists
+ converge_by("create user #{@new_resource}") do
+ create_user
+ Chef::Log.info("#{@new_resource} created")
+ end
+ elsif compare_user
+ converge_by("alter user #{@new_resource}") do
+ manage_user
+ Chef::Log.info("#{@new_resource} altered")
+ end
+ end
+ end
+
+ def action_remove
+ if @user_exists
+ converge_by("remove user #{@new_resource}") do
+ remove_user
+ Chef::Log.info("#{@new_resource} removed")
+ end
+ end
+ end
+
+ def remove_user
+ raise NotImplementedError
+ end
+
+ def action_manage
+ if @user_exists && compare_user
+ converge_by("manage user #{@new_resource}") do
+ manage_user
+ Chef::Log.info("#{@new_resource} managed")
+ end
+ end
+ end
+
+ def manage_user
+ raise NotImplementedError
+ end
+
+ def action_modify
+ if compare_user
+ converge_by("modify user #{@new_resource}") do
+ manage_user
+ Chef::Log.info("#{@new_resource} modified")
+ end
+ end
+ end
+
+ def action_lock
+ if check_lock() == false
+ converge_by("lock the user #{@new_resource}") do
+ lock_user
+ Chef::Log.info("#{@new_resource} locked")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already locked - nothing to do")
+ end
+ end
+
+ def check_lock
+ raise NotImplementedError
+ end
+
+ def lock_user
+ raise NotImplementedError
+ end
+
+ def action_unlock
+ if check_lock() == true
+ converge_by("unlock user #{@new_resource}") do
+ unlock_user
+ Chef::Log.info("#{@new_resource} unlocked")
+ end
+ else
+ Chef::Log.debug("#{@new_resource} already unlocked - nothing to do")
+ end
+ end
+
+ def unlock_user
+ raise NotImplementedError
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
new file mode 100644
index 0000000000..94e8420c43
--- /dev/null
+++ b/lib/chef/provider/user/dscl.rb
@@ -0,0 +1,288 @@
+#
+# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
+# Copyright:: Copyright (c) 2009 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/shell_out'
+require 'chef/provider/user'
+require 'openssl'
+
+class Chef
+ class Provider
+ class User
+ class Dscl < Chef::Provider::User
+ include Chef::Mixin::ShellOut
+
+ NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$}
+ AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$}
+
+ def dscl(*args)
+ shell_out("dscl . -#{args.join(' ')}")
+ end
+
+ def safe_dscl(*args)
+ result = dscl(*args)
+ return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
+ return result.stdout
+ end
+
+ # This is handled in providers/group.rb by Etc.getgrnam()
+ # def user_exists?(user)
+ # users = safe_dscl("list /Users")
+ # !! ( users =~ Regexp.new("\n#{user}\n") )
+ # end
+
+ # get a free UID greater than 200
+ def get_free_uid(search_limit=1000)
+ uid = nil; next_uid_guess = 200
+ users_uids = safe_dscl("list /Users uid")
+ while(next_uid_guess < search_limit + 200)
+ if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
+ next_uid_guess += 1
+ else
+ uid = next_uid_guess
+ break
+ end
+ end
+ return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
+ end
+
+ def uid_used?(uid)
+ return false unless uid
+ users_uids = safe_dscl("list /Users uid")
+ !! ( users_uids =~ Regexp.new("#{Regexp.escape(uid.to_s)}\n") )
+ end
+
+ def set_uid
+ @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
+ if uid_used?(@new_resource.uid)
+ raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+ end
+ safe_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
+ end
+
+ def modify_home
+ return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?)
+ if @new_resource.supports[:manage_home]
+ validate_home_dir_specification!
+
+ if (@current_resource.home == @new_resource.home) && !new_home_exists?
+ ditto_home
+ elsif !current_home_exists? && !new_home_exists?
+ ditto_home
+ elsif current_home_exists?
+ move_home
+ end
+ end
+ safe_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
+ end
+
+ def osx_shadow_hash?(string)
+ return !! ( string =~ /^[[:xdigit:]]{1240}$/ )
+ end
+
+ def osx_salted_sha1?(string)
+ return !! ( string =~ /^[[:xdigit:]]{48}$/ )
+ end
+
+ def guid
+ safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").strip
+ end
+
+ def shadow_hash_set?
+ user_data = safe_dscl("read /Users/#{@new_resource.username}")
+ if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/
+ true
+ else
+ false
+ end
+ end
+
+ def modify_password
+ if @new_resource.password
+ shadow_hash = nil
+
+ Chef::Log.debug("#{new_resource} updating password")
+ if osx_shadow_hash?(@new_resource.password)
+ shadow_hash = @new_resource.password.upcase
+ else
+ if osx_salted_sha1?(@new_resource.password)
+ salted_sha1 = @new_resource.password.upcase
+ else
+ hex_salt = ""
+ OpenSSL::Random.random_bytes(10).each_byte { |b| hex_salt << b.to_i.to_s(16) }
+ hex_salt = hex_salt.slice(0...8)
+ salt = [hex_salt].pack("H*")
+ sha1 = ::OpenSSL::Digest::SHA1.hexdigest(salt+@new_resource.password)
+ salted_sha1 = (hex_salt+sha1).upcase
+ end
+ shadow_hash = String.new("00000000"*155)
+ shadow_hash[168] = salted_sha1
+ end
+
+ ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output|
+ output.puts shadow_hash
+ end
+
+ unless shadow_hash_set?
+ safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'")
+ end
+ end
+ end
+
+ def load_current_resource
+ super
+ raise Chef::Exceptions::User, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl")
+ end
+
+ def create_user
+ dscl_create_user
+ dscl_create_comment
+ set_uid
+ dscl_set_gid
+ modify_home
+ dscl_set_shell
+ modify_password
+ end
+
+ def manage_user
+ dscl_create_user if diverged?(:username)
+ dscl_create_comment if diverged?(:comment)
+ set_uid if diverged?(:uid)
+ dscl_set_gid if diverged?(:gid)
+ modify_home if diverged?(:home)
+ dscl_set_shell if diverged?(:shell)
+ modify_password if diverged?(:password)
+ end
+
+ def dscl_create_user
+ safe_dscl("create /Users/#{@new_resource.username}")
+ end
+
+ def dscl_create_comment
+ safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
+ end
+
+ def dscl_set_gid
+ unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+ begin
+ possible_gid = safe_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+ rescue Chef::Exceptions::DsclCommandFailed => e
+ raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+ end
+ @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
+ end
+ safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
+ end
+
+ def dscl_set_shell
+ if @new_resource.password || ::File.exists?("#{@new_resource.shell}")
+ safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
+ else
+ safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+ end
+ end
+
+ def remove_user
+ if @new_resource.supports[:manage_home]
+ user_info = safe_dscl("read /Users/#{@new_resource.username}")
+ if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY)
+ #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory")
+ #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"")
+ nfs_home = nfs_home_match[1]
+ FileUtils.rm_rf(nfs_home)
+ end
+ end
+ # remove the user from its groups
+ groups = []
+ Etc.group do |group|
+ groups << group.name if group.mem.include?(@new_resource.username)
+ end
+ groups.each do |group_name|
+ safe_dscl("delete /Groups/#{group_name} GroupMembership '#{@new_resource.username}'")
+ end
+ # remove user account
+ safe_dscl("delete /Users/#{@new_resource.username}")
+ end
+
+ def locked?
+ user_info = safe_dscl("read /Users/#{@new_resource.username}")
+ if auth_authority_md = AUTHENTICATION_AUTHORITY.match(user_info)
+ !!(auth_authority_md[1] =~ /DisabledUser/ )
+ else
+ false
+ end
+ end
+
+ def check_lock
+ return @locked = locked?
+ end
+
+ def lock_user
+ safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ end
+
+ def unlock_user
+ auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority")
+ auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"")
+ safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ end
+
+ def validate_home_dir_specification!
+ unless @new_resource.home =~ /^\//
+ raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
+ end
+ end
+
+ def current_home_exists?
+ ::File.exist?("#{@current_resource.home}")
+ end
+
+ def new_home_exists?
+ ::File.exist?("#{@new_resource.home}")
+ end
+
+ def ditto_home
+ skel = "/System/Library/User Template/English.lproj"
+ raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
+ shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ end
+
+ def move_home
+ Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
+
+ src = @current_resource.home
+ FileUtils.mkdir_p(@new_resource.home)
+ files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
+ ::FileUtils.mv(files,@new_resource.home, :force => true)
+ ::FileUtils.rmdir(src)
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ end
+
+ def diverged?(parameter)
+ parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
+ end
+
+ def parameter_updated?(parameter)
+ not (@new_resource.send(parameter) == @current_resource.send(parameter))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
new file mode 100644
index 0000000000..4f6393da89
--- /dev/null
+++ b/lib/chef/provider/user/pw.rb
@@ -0,0 +1,113 @@
+#
+# Author:: Stephen Haynes (<sh@nomitor.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/user'
+
+class Chef
+ class Provider
+ class User
+ class Pw < Chef::Provider::User
+
+ def load_current_resource
+ super
+ raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{@new_resource}" unless ::File.exists?("/usr/sbin/pw")
+ end
+
+ def create_user
+ command = "pw useradd"
+ command << set_options
+ run_command(:command => command)
+ modify_password
+ end
+
+ def manage_user
+ command = "pw usermod"
+ command << set_options
+ run_command(:command => command)
+ modify_password
+ end
+
+ def remove_user
+ command = "pw userdel #{@new_resource.username}"
+ command << " -r" if @new_resource.supports[:manage_home]
+ run_command(:command => command)
+ end
+
+ def check_lock
+ case @current_resource.password
+ when /^\*LOCKED\*/
+ @locked = true
+ else
+ @locked = false
+ end
+ @locked
+ end
+
+ def lock_user
+ run_command(:command => "pw lock #{@new_resource.username}")
+ end
+
+ def unlock_user
+ run_command(:command => "pw unlock #{@new_resource.username}")
+ end
+
+ def set_options
+ opts = " #{@new_resource.username}"
+
+ field_list = {
+ 'comment' => "-c",
+ 'home' => "-d",
+ 'gid' => "-g",
+ 'uid' => "-u",
+ 'shell' => "-s"
+ }
+ field_list.sort{ |a,b| a[0] <=> b[0] }.each do |field, option|
+ field_symbol = field.to_sym
+ if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
+ if @new_resource.send(field_symbol)
+ Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
+ opts << " #{option} '#{@new_resource.send(field_symbol)}'"
+ end
+ end
+ end
+ if @new_resource.supports[:manage_home]
+ Chef::Log.debug("#{@new_resource} is managing the users home directory")
+ opts << " -m"
+ end
+ opts
+ end
+
+ def modify_password
+ if @current_resource.password != @new_resource.password
+ Chef::Log.debug("#{new_resource} updating password")
+ command = "pw usermod #{@new_resource.username} -H 0"
+ status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr|
+ stdin.puts "#{@new_resource.password}"
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::User, "pw failed - #{status.inspect}!"
+ end
+ else
+ Chef::Log.debug("#{new_resource} no change needed to password")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
new file mode 100644
index 0000000000..489632f722
--- /dev/null
+++ b/lib/chef/provider/user/useradd.rb
@@ -0,0 +1,144 @@
+#
+# 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 'pathname'
+require 'chef/provider/user'
+
+class Chef
+ class Provider
+ class User
+ class Useradd < Chef::Provider::User
+ UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
+
+ def create_user
+ command = compile_command("useradd") do |useradd|
+ useradd << universal_options
+ useradd << useradd_options
+ end
+ run_command(:command => command)
+ end
+
+ def manage_user
+ command = compile_command("usermod") do |u|
+ u << universal_options
+ end
+ run_command(:command => command)
+ end
+
+ def remove_user
+ command = "userdel"
+ command << " -r" if managing_home_dir?
+ command << " #{@new_resource.username}"
+ run_command(:command => command)
+ end
+
+ def check_lock
+ status = popen4("passwd -S #{@new_resource.username}") do |pid, stdin, stdout, stderr|
+ status_line = stdout.gets.split(' ')
+ case status_line[1]
+ when /^P/
+ @locked = false
+ when /^N/
+ @locked = false
+ when /^L/
+ @locked = true
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise_lock_error = false
+ # we can get an exit code of 1 even when it's successful on rhel/centos (redhat bug 578534)
+ if status.exitstatus == 1 && ['redhat', 'centos'].include?(node[:platform])
+ passwd_version_status = popen4('rpm -q passwd') do |pid, stdin, stdout, stderr|
+ passwd_version = stdout.gets.chomp
+
+ unless passwd_version == 'passwd-0.73-1'
+ raise_lock_error = true
+ end
+ end
+ else
+ raise_lock_error = true
+ end
+
+ raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if raise_lock_error
+ end
+
+ @locked
+ end
+
+ def lock_user
+ run_command(:command => "usermod -L #{@new_resource.username}")
+ end
+
+ def unlock_user
+ run_command(:command => "usermod -U #{@new_resource.username}")
+ end
+
+ def compile_command(base_command)
+ yield base_command
+ base_command << " #{@new_resource.username}"
+ base_command
+ end
+
+ def universal_options
+ opts = ''
+
+ UNIVERSAL_OPTIONS.each do |field, option|
+ if @current_resource.send(field) != @new_resource.send(field)
+ if @new_resource.send(field)
+ Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}")
+ opts << " #{option} '#{@new_resource.send(field)}'"
+ end
+ end
+ end
+ if updating_home?
+ if managing_home_dir?
+ Chef::Log.debug("#{@new_resource} managing the users home directory")
+ opts << " -m -d '#{@new_resource.home}'"
+ else
+ Chef::Log.debug("#{@new_resource} setting home to #{@new_resource.home}")
+ opts << " -d '#{@new_resource.home}'"
+ end
+ end
+ opts << " -o" if @new_resource.non_unique || @new_resource.supports[:non_unique]
+ opts
+ end
+
+ def useradd_options
+ opts = ''
+ opts << " -r" if @new_resource.system
+ opts
+ end
+
+ def updating_home?
+ # will return false if paths are equivalent
+ # Pathname#cleanpath does a better job than ::File::expand_path (on both unix and windows)
+ # ::File.expand_path("///tmp") == ::File.expand_path("/tmp") => false
+ # ::File.expand_path("\\tmp") => "C:/tmp"
+ return true if @current_resource.home.nil? && @new_resource.home
+ @new_resource.home and Pathname.new(@current_resource.home).cleanpath != Pathname.new(@new_resource.home).cleanpath
+ end
+
+ def managing_home_dir?
+ @new_resource.manage_home || @new_resource.supports[:manage_home]
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
new file mode 100644
index 0000000000..6bbb2a088c
--- /dev/null
+++ b/lib/chef/provider/user/windows.rb
@@ -0,0 +1,124 @@
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/user'
+if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/util/windows/net_user'
+end
+
+class Chef
+ class Provider
+ class User
+ class Windows < Chef::Provider::User
+
+ def initialize(new_resource,run_context)
+ super
+ @net_user = Chef::Util::Windows::NetUser.new(@new_resource.name)
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::User.new(@new_resource.name)
+ @current_resource.username(@new_resource.username)
+ user_info = nil
+ begin
+ user_info = @net_user.get_info
+ rescue
+ @user_exists = false
+ Chef::Log.debug("#{@new_resource} does not exist")
+ end
+
+ if user_info
+ @current_resource.uid(user_info[:user_id])
+ @current_resource.gid(user_info[:primary_group_id])
+ @current_resource.comment(user_info[:full_name])
+ @current_resource.home(user_info[:home_dir])
+ @current_resource.shell(user_info[:script_path])
+ end
+
+ @current_resource
+ end
+
+ # Check to see if the user needs any changes
+ #
+ # === Returns
+ # <true>:: If a change is required
+ # <false>:: If the users are identical
+ def compare_user
+ unless @net_user.validate_credentials(@new_resource.password)
+ Chef::Log.debug("#{@new_resource} password has changed")
+ return true
+ end
+ [ :uid, :gid, :comment, :home, :shell ].any? do |user_attrib|
+ !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
+ end
+ end
+
+ def create_user
+ @net_user.add(set_options)
+ end
+
+ def manage_user
+ @net_user.update(set_options)
+ end
+
+ def remove_user
+ @net_user.delete
+ end
+
+ def check_lock
+ @net_user.check_enabled
+ end
+
+ def lock_user
+ @net_user.disable_account
+ end
+
+ def unlock_user
+ @net_user.enable_account
+ end
+
+ def set_options
+ opts = {:name => @new_resource.username}
+
+ field_list = {
+ 'comment' => 'full_name',
+ 'home' => 'home_dir',
+ 'gid' => 'primary_group_id',
+ 'uid' => 'user_id',
+ 'shell' => 'script_path',
+ 'password' => 'password'
+ }
+
+ field_list.sort{ |a,b| a[0] <=> b[0] }.each do |field, option|
+ field_symbol = field.to_sym
+ if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
+ if @new_resource.send(field_symbol)
+ unless field_symbol == :password
+ Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}")
+ end
+ opts[option.to_sym] = @new_resource.send(field_symbol)
+ end
+ end
+ end
+ opts
+ end
+
+ end
+ end
+ end
+end