diff options
author | Richard Manyanza <rm@dsc.co.tz> | 2014-03-17 21:49:04 +0300 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2014-10-22 14:22:09 -0700 |
commit | cb1bcb1f08816f551f96e713624718f58da3c9b3 (patch) | |
tree | 93458a3b13ea008f596249aa7ae8b1975bd0c1f9 | |
parent | 4db0ef42910d03209c7bb4b69f14e565c8c758ae (diff) | |
download | chef-cb1bcb1f08816f551f96e713624718f58da3c9b3.tar.gz |
Initial sketch for provider resolver
-rw-r--r-- | lib/chef/provider.rb | 48 | ||||
-rw-r--r-- | lib/chef/provider/cron.rb | 2 | ||||
-rw-r--r-- | lib/chef/provider/group/pw.rb | 3 | ||||
-rw-r--r-- | lib/chef/provider/package/freebsd.rb | 154 | ||||
-rw-r--r-- | lib/chef/provider/service/freebsd.rb | 3 | ||||
-rw-r--r-- | lib/chef/provider/user/pw.rb | 3 | ||||
-rw-r--r-- | lib/chef/provider_resolver.rb | 57 | ||||
-rw-r--r-- | lib/chef/resource.rb | 4 | ||||
-rw-r--r-- | lib/chef/run_context.rb | 6 | ||||
-rw-r--r-- | lib/chef/runner.rb | 5 | ||||
-rw-r--r-- | spec/unit/provider_resolver_spec.rb | 91 |
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 |