summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Manyanza <rm@dsc.co.tz>2014-03-17 21:49:04 +0300
committerLamont Granquist <lamont@scriptkiddie.org>2014-10-22 14:22:09 -0700
commitcb1bcb1f08816f551f96e713624718f58da3c9b3 (patch)
tree93458a3b13ea008f596249aa7ae8b1975bd0c1f9
parent4db0ef42910d03209c7bb4b69f14e565c8c758ae (diff)
downloadchef-cb1bcb1f08816f551f96e713624718f58da3c9b3.tar.gz
Initial sketch for provider resolver
-rw-r--r--lib/chef/provider.rb48
-rw-r--r--lib/chef/provider/cron.rb2
-rw-r--r--lib/chef/provider/group/pw.rb3
-rw-r--r--lib/chef/provider/package/freebsd.rb154
-rw-r--r--lib/chef/provider/service/freebsd.rb3
-rw-r--r--lib/chef/provider/user/pw.rb3
-rw-r--r--lib/chef/provider_resolver.rb57
-rw-r--r--lib/chef/resource.rb4
-rw-r--r--lib/chef/run_context.rb6
-rw-r--r--lib/chef/runner.rb5
-rw-r--r--spec/unit/provider_resolver_spec.rb91
11 files changed, 374 insertions, 2 deletions
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index bdfe826944..7cfdf7cbe5 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -28,6 +28,54 @@ class Chef
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
+
+ class << self
+ include Enumerable
+
+ @@providers = []
+
+ attr_reader :implementations
+ attr_reader :supported_platforms
+
+ def inherited(klass)
+ @@providers << klass
+ end
+
+ def providers
+ @@providers
+ end
+
+ def each
+ providers.each { |provider| yield provider }
+ providers
+ end
+
+ def implements(*resources)
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
+
+ @implementations = resources.map { |resource| resource.to_sym }
+ @supported_platforms = Array(options[:on_platforms] || :all)
+ end
+
+ def implements?(resource)
+ klass_name = resource.class.to_s.split('::').last
+ resource_name = klass_name.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase
+
+ implementations && implementations.include?(resource_name.to_sym)
+ end
+
+ def supports_platform?(platform)
+ supported_platforms && (
+ supported_platforms.include?(:all) ||
+ supported_platforms.include?(platform.to_sym))
+ end
+
+ def enabled?(node)
+ true
+ end
+ end
+
+
attr_accessor :new_resource
attr_accessor :current_resource
attr_accessor :run_context
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index c3be9746df..1590c624f6 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -28,7 +28,7 @@ class Chef
SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment]
WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
-
+
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(.*)/
SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/
ENV_PATTERN = /\A(\S+)=(\S*)/
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index c39c20da67..79a4160807 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -21,6 +21,9 @@ class Chef
class Group
class Pw < Chef::Provider::Group
+ implements :group,
+ :on_platforms => :freebsd
+
def load_current_resource
super
end
diff --git a/lib/chef/provider/package/freebsd.rb b/lib/chef/provider/package/freebsd.rb
new file mode 100644
index 0000000000..f5ec0cec47
--- /dev/null
+++ b/lib/chef/provider/package/freebsd.rb
@@ -0,0 +1,154 @@
+#
+# 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
+
+ implements :package,
+ :freebsd_package,
+ :on_platforms => :freebsd
+
+
+ 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[/^#{Regexp.escape(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[/^#{Regexp.escape(@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/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 08d58232e1..e0d9696f61 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -27,6 +27,9 @@ class Chef
attr_reader :enabled_state_found
+ implements :service,
+ :on_platforms => [:freebsd, :netbsd]
+
def initialize(new_resource, run_context)
super
@enabled_state_found = false
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index fe71e93561..0dc80103da 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -23,6 +23,9 @@ class Chef
class User
class Pw < Chef::Provider::User
+ implements :user,
+ :on_platforms => :freebsd
+
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")
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
new file mode 100644
index 0000000000..91b85e3aa0
--- /dev/null
+++ b/lib/chef/provider_resolver.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Richard Manyanza (<liseki@nyikacraftsmen.com>)
+# Copyright:: Copyright (c) 2014 Richard Manyanza.
+# 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 ProviderResolver
+
+ attr_reader :node
+ attr_reader :providers
+
+ def initialize(node)
+ @node = node
+ @providers = []
+ @loaded = false
+ end
+
+ def load(reload = false)
+ return if loaded? && !reload
+
+ @providers = [] if reload
+
+ Chef::Provider.each do |provider|
+ @providers << provider if provider.supports_platform?(@node[:platform])
+ end
+
+ @loaded = true
+ end
+
+ def loaded?
+ !!@loaded
+ end
+
+ def resolve(resource)
+ self.load if !loaded?
+
+ providers = @providers.find_all do |provider|
+ provider.enabled?(node) && provider.implements?(resource)
+ end
+
+ resource.evaluate_providers(providers)
+ end
+ end
+end
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index e92ea28c69..5099446335 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -687,6 +687,10 @@ F
raise ArgumentError, "nil is not a valid action for resource #{self}" if action.nil?
end
+ def evaluate_providers(providers)
+ provider = providers.first
+ end
+
def provider_for_action(action)
# leverage new platform => short_name => resource
# which requires explicitly setting provider in
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index bbe2f9eba0..3564be62ff 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -18,6 +18,7 @@
# limitations under the License.
require 'chef/resource_collection'
+require 'chef/provider_resolver'
require 'chef/cookbook_version'
require 'chef/node'
require 'chef/role'
@@ -50,6 +51,8 @@ class Chef
# recipes, which is triggered by #load. (See also: CookbookCompiler)
attr_accessor :resource_collection
+ attr_reader :provider_resolver
+
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
attr_accessor :immediate_notification_collection
@@ -82,8 +85,8 @@ class Chef
@reboot_info = {}
@node.run_context = self
-
@cookbook_compiler = nil
+ @provider_resolver = Chef::ProviderResolver.new(@node)
end
# Triggers the compile phase of the chef run. Implemented by
@@ -91,6 +94,7 @@ class Chef
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
@cookbook_compiler.compile
+ @provider_resolver.load
end
# Adds an immediate notification to the
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index 6125fe59e1..3467cfe717 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -43,6 +43,10 @@ class Chef
@run_context.events
end
+ def provider_resolver
+ @run_context.provider_resolver
+ end
+
# Determine the appropriate provider for the given resource, then
# execute it.
def run_action(resource, action, notification_type=nil, notifying_resource=nil)
@@ -78,6 +82,7 @@ class Chef
# Execute each resource.
run_context.resource_collection.execute_each_resource do |resource|
+ provider_resolver.resolve(resource)
Array(resource.action).each {|action| run_action(resource, action)}
end
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
new file mode 100644
index 0000000000..bc65bdc13c
--- /dev/null
+++ b/spec/unit/provider_resolver_spec.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Richard Manyanza (<liseki@nyikacraftsmen.com>)
+# Copyright:: Copyright (c) 2014 Richard Manyanza.
+# 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 'spec_helper'
+
+describe Chef::ProviderResolver do
+ before(:each) do
+ @node = Chef::Node.new
+ @provider_resolver = Chef::ProviderResolver.new(@node)
+ end
+
+ describe "Initialization" do
+ it "should not load providers" do
+ @provider_resolver.loaded?.should be_false
+ end
+ end
+
+
+ describe "Loading providers" do
+ end
+
+
+ describe "on FreeBSD" do
+ before(:each) do
+ @node.normal[:platform] = :freebsd
+ end
+
+ describe "loading" do
+ before(:each) do
+ @provider_resolver.load
+ end
+
+ it "should load FreeBSD providers" do
+ providers = [
+ Chef::Provider::User::Pw,
+ Chef::Provider::Group::Pw,
+ Chef::Provider::Service::Freebsd,
+ Chef::Provider::Package::Freebsd,
+ Chef::Provider::Cron
+ ]
+
+ @provider_resolver.providers.length.should == providers.length
+ providers.each do |provider|
+ @provider_resolver.providers.include?(provider).should be_true
+ end
+ end
+ end
+
+ describe "resolving" do
+ it "should handle user" do
+ user = Chef::Resource::User.new('toor')
+ @provider_resolver.resolve(user).should == Chef::Provider::User::Pw
+ end
+
+ it "should handle group" do
+ group = Chef::Resource::Group.new('ops')
+ @provider_resolver.resolve(group).should == Chef::Provider::Group::Pw
+ end
+
+ it "should handle service" do
+ service = Chef::Resource::Service.new('nginx')
+ @provider_resolver.resolve(service).should == Chef::Provider::Service::Freebsd
+ end
+
+ it "should handle package" do
+ package = Chef::Resource::Package.new('zsh')
+ @provider_resolver.resolve(package).should == Chef::Provider::Package::Freebsd
+ end
+
+ it "should handle cron" do
+ cron = Chef::Resource::Cron.new('security_status_report')
+ @provider_resolver.resolve(cron).should == Chef::Provider::Cron
+ end
+ end
+ end
+end