diff options
author | Adam Jacob <adam@opscode.com> | 2009-05-18 14:49:53 -0700 |
---|---|---|
committer | Adam Jacob <adam@opscode.com> | 2009-05-18 14:49:53 -0700 |
commit | 4faba23f2367e15e196abef45d9e904530d7b9a8 (patch) | |
tree | ebe2020891c3fec0c28bea90153fe31faa845b96 | |
parent | b745621c8e40ff74acafeb47cca20b6edc5193e3 (diff) | |
parent | 63f026077329adaa5a59a000482ec0ed145498aa (diff) | |
download | chef-4faba23f2367e15e196abef45d9e904530d7b9a8.tar.gz |
Merge branch 'master' into mdkent/chef-272
81 files changed, 2645 insertions, 329 deletions
@@ -1,3 +1,15 @@ +Wed Apr 29 16:08:40 PDT 2009 +Release Notes - Chef - Version 0.6.2 +http://tickets.opscode.com + +** Bug + * [CHEF-255] - chef-client doesn't respect interval and stay running in foreground. + * [CHEF-257] - Rake test does not execute tests against the site-cookbooks directory + +** Improvement + * [CHEF-249] - can I has open-uri supported "-j http://blah.com/some.json" for chef-solo/client? + * [CHEF-258] - gem_package doesn't allow you to point at a custom gem binary outside of your $PATH + Tue Apr 28 16:43:43 PDT 2009 Release Notes - Chef - Version 0.6.0 http://tickets.opscode.com diff --git a/chef-server-slice/Rakefile b/chef-server-slice/Rakefile index 665681c2ed..44734103c5 100644 --- a/chef-server-slice/Rakefile +++ b/chef-server-slice/Rakefile @@ -5,7 +5,7 @@ require 'merb-core' require 'merb-core/tasks/merb' GEM_NAME = "chef-server-slice" -CHEF_SERVER_VERSION="0.6.2" +CHEF_SERVER_VERSION="0.6.3" AUTHOR = "Opscode" EMAIL = "chef@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" diff --git a/chef-server-slice/app/controllers/cookbook_attributes.rb b/chef-server-slice/app/controllers/cookbook_attributes.rb index 3072358a67..fd888448b0 100644 --- a/chef-server-slice/app/controllers/cookbook_attributes.rb +++ b/chef-server-slice/app/controllers/cookbook_attributes.rb @@ -22,6 +22,8 @@ require 'chef' / 'mixin' / 'checksum' class ChefServerSlice::CookbookAttributes < ChefServerSlice::Application provides :html, :json + + before :login_required include Chef::Mixin::Checksum diff --git a/chef-server-slice/app/controllers/cookbook_definitions.rb b/chef-server-slice/app/controllers/cookbook_definitions.rb index 0205cb1111..da5654fbcc 100644 --- a/chef-server-slice/app/controllers/cookbook_definitions.rb +++ b/chef-server-slice/app/controllers/cookbook_definitions.rb @@ -22,6 +22,8 @@ require 'chef' / 'mixin' / 'checksum' class ChefServerSlice::CookbookDefinitions < ChefServerSlice::Application provides :html, :json + + before :login_required include Chef::Mixin::Checksum diff --git a/chef-server-slice/app/controllers/cookbook_files.rb b/chef-server-slice/app/controllers/cookbook_files.rb index e70f853d87..b739366d83 100644 --- a/chef-server-slice/app/controllers/cookbook_files.rb +++ b/chef-server-slice/app/controllers/cookbook_files.rb @@ -24,6 +24,7 @@ require 'chef' / 'mixin' / 'find_preferred_file' class ChefServerSlice::CookbookFiles < ChefServerSlice::Application provides :html, :json + before :login_required include Chef::Mixin::Checksum include Chef::Mixin::FindPreferredFile diff --git a/chef-server-slice/app/controllers/cookbook_libraries.rb b/chef-server-slice/app/controllers/cookbook_libraries.rb index 88d1500a92..72b296f351 100644 --- a/chef-server-slice/app/controllers/cookbook_libraries.rb +++ b/chef-server-slice/app/controllers/cookbook_libraries.rb @@ -23,6 +23,7 @@ require 'chef' / 'cookbook_loader' class ChefServerSlice::CookbookLibraries < ChefServerSlice::Application provides :html, :json + before :login_required include Chef::Mixin::Checksum diff --git a/chef-server-slice/app/controllers/cookbook_recipes.rb b/chef-server-slice/app/controllers/cookbook_recipes.rb index cb969e6415..fbcb427eb3 100644 --- a/chef-server-slice/app/controllers/cookbook_recipes.rb +++ b/chef-server-slice/app/controllers/cookbook_recipes.rb @@ -22,6 +22,7 @@ require 'chef' / 'mixin' / 'checksum' class ChefServerSlice::CookbookRecipes < ChefServerSlice::Application provides :html, :json + before :login_required include Chef::Mixin::Checksum diff --git a/chef-server-slice/app/controllers/cookbook_templates.rb b/chef-server-slice/app/controllers/cookbook_templates.rb index 10cc5731c9..bce79bd573 100644 --- a/chef-server-slice/app/controllers/cookbook_templates.rb +++ b/chef-server-slice/app/controllers/cookbook_templates.rb @@ -24,6 +24,7 @@ require 'chef' / 'mixin' / 'find_preferred_file' class ChefServerSlice::CookbookTemplates < ChefServerSlice::Application provides :html, :json + before :login_required include Chef::Mixin::Checksum include Chef::Mixin::FindPreferredFile diff --git a/chef-server-slice/app/controllers/cookbooks.rb b/chef-server-slice/app/controllers/cookbooks.rb index c1ef997bd7..a19440937a 100644 --- a/chef-server-slice/app/controllers/cookbooks.rb +++ b/chef-server-slice/app/controllers/cookbooks.rb @@ -22,6 +22,7 @@ require 'chef' / 'cookbook_loader' class ChefServerSlice::Cookbooks < ChefServerSlice::Application provides :html, :json + before :login_required def index @cl = Chef::CookbookLoader.new diff --git a/chef-server-slice/app/controllers/nodes.rb b/chef-server-slice/app/controllers/nodes.rb index 03b343767a..472b33a453 100644 --- a/chef-server-slice/app/controllers/nodes.rb +++ b/chef-server-slice/app/controllers/nodes.rb @@ -24,7 +24,7 @@ class ChefServerSlice::Nodes < ChefServerSlice::Application provides :html, :json before :fix_up_node_id - before :login_required, :only => [ :create, :update, :destroy ] + before :login_required before :authorized_node, :only => [ :update, :destroy ] def index diff --git a/chef-server-slice/app/controllers/search.rb b/chef-server-slice/app/controllers/search.rb index bd1232062c..0f56106492 100644 --- a/chef-server-slice/app/controllers/search.rb +++ b/chef-server-slice/app/controllers/search.rb @@ -23,6 +23,7 @@ require 'chef' / 'queue' class ChefServerSlice::Search < ChefServerSlice::Application provides :html, :json + before :login_required def index @s = Chef::Search.new diff --git a/chef-server-slice/app/controllers/search_entries.rb b/chef-server-slice/app/controllers/search_entries.rb index dfbc724ad2..dfb90e0b27 100644 --- a/chef-server-slice/app/controllers/search_entries.rb +++ b/chef-server-slice/app/controllers/search_entries.rb @@ -23,6 +23,7 @@ require 'chef' / 'queue' class ChefServerSlice::SearchEntries < ChefServerSlice::Application provides :html, :json + before :login_required def index @s = Chef::Search.new diff --git a/chef-server-slice/app/controllers/status.rb b/chef-server-slice/app/controllers/status.rb index 58562672e7..30f656c76b 100644 --- a/chef-server-slice/app/controllers/status.rb +++ b/chef-server-slice/app/controllers/status.rb @@ -21,6 +21,7 @@ require 'chef' / 'node' class ChefServerSlice::Status < ChefServerSlice::Application provides :html, :json + before :login_required def index @node_list = Chef::Node.list diff --git a/chef-server-slice/config/init.rb b/chef-server-slice/config/init.rb index d8faac2b81..8ae08ff1f9 100644 --- a/chef-server-slice/config/init.rb +++ b/chef-server-slice/config/init.rb @@ -39,5 +39,7 @@ Merb::Config.use do |c| c[:session_store] = 'cookie' c[:exception_details] = true c[:reload_classes] = false + c[:log_level] = Chef::Config[:log_level] + c[:log_file] = Chef::Config[:log_location] end diff --git a/chef-server/Rakefile b/chef-server/Rakefile index 4215e4a4ad..39bac5ee69 100644 --- a/chef-server/Rakefile +++ b/chef-server/Rakefile @@ -18,7 +18,7 @@ require 'chef' unless defined?(Chef) include FileUtils GEM = "chef-server" -CHEF_SERVER_VERSION = "0.6.2" +CHEF_SERVER_VERSION = "0.6.3" AUTHOR = "Opscode" EMAIL = "chef@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" diff --git a/chef-server/config.ru b/chef-server/config.ru new file mode 100644 index 0000000000..0cd42885ed --- /dev/null +++ b/chef-server/config.ru @@ -0,0 +1,16 @@ +require 'rubygems' +require 'merb-core' + +Merb::Config.setup(:merb_root => File.expand_path(File.dirname(__FILE__)), + :environment => ENV['RACK_ENV'], :init_file => File.dirname(__FILE__) / "config/init.rb") +Merb.environment = Merb::Config[:environment] +Merb.root = Merb::Config[:merb_root] +Merb::BootLoader.run + +# Uncomment if your app is mounted at a suburi +#if prefix = ::Merb::Config[:path_prefix] +# use Merb::Rack::PathPrefix, prefix +#end + +run Merb::Rack::Application.new + diff --git a/chef-server/config/environments/production.rb b/chef-server/config/environments/production.rb index b10ca094dc..aa27f7a695 100644 --- a/chef-server/config/environments/production.rb +++ b/chef-server/config/environments/production.rb @@ -2,9 +2,8 @@ Merb.logger.info("Loaded PRODUCTION Environment...") Merb::Config.use { |c| c[:exception_details] = false c[:reload_classes] = false - c[:log_level] = :error - c[:log_stream] = Chef::Config[:log_location] - c[:log_file] = nil + c[:log_level] = Chef::Config[:log_level] + c[:log_file] = Chef::Config[:log_location] # or redirect logger using IO handle # c[:log_stream] = STDOUT } diff --git a/chef-server/config/init.rb b/chef-server/config/init.rb index 767b068675..b40cb7c233 100644 --- a/chef-server/config/init.rb +++ b/chef-server/config/init.rb @@ -2,10 +2,12 @@ require 'config/dependencies.rb' require 'chef' unless defined?(Chef) - + +Chef::Config.from_file(File.join("/etc", "chef", "server.rb")) + use_test :rspec use_template_engine :haml - + Merb::Config.use do |c| c[:use_mutex] = false c[:session_id_key] = '_chef_server_session_id' @@ -14,7 +16,7 @@ Merb::Config.use do |c| c[:exception_details] = true c[:reload_classes] = false c[:log_level] = Chef::Config[:log_level] - c[:log_stream] = Chef::Config[:log_location] + c[:log_file] = Chef::Config[:log_location] end Merb::BootLoader.before_app_loads do diff --git a/chef-server/lib/tasks/package.rake b/chef-server/lib/tasks/package.rake index 3722a2da16..f8df8096f0 100644 --- a/chef-server/lib/tasks/package.rake +++ b/chef-server/lib/tasks/package.rake @@ -32,6 +32,7 @@ spec = Gem::Specification.new do |s| [ "README.txt", "LICENSE", "NOTICE", + "config.ru", "{app}/**/*", "{config}/**/*", "{contrib}/**/*", diff --git a/chef/Rakefile b/chef/Rakefile index 3164223944..63cdec3638 100644 --- a/chef/Rakefile +++ b/chef/Rakefile @@ -4,7 +4,7 @@ require 'rake/rdoctask' require './tasks/rspec.rb' GEM = "chef" -CHEF_VERSION = "0.6.2" +CHEF_VERSION = "0.6.3" AUTHOR = "Adam Jacob" EMAIL = "adam@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" @@ -25,7 +25,7 @@ spec = Gem::Specification.new do |s| # Uncomment this to add a dependency s.add_dependency "ruby-openid" s.add_dependency "json" - s.add_dependency "erubis" + s.add_dependency "erubis" s.add_dependency "extlib" s.add_dependency "stomp" s.add_dependency "ohai" diff --git a/chef/examples/user_index.pl b/chef/examples/user_index.pl index e78a8125f4..e78a8125f4 100755..100644 --- a/chef/examples/user_index.pl +++ b/chef/examples/user_index.pl diff --git a/chef/examples/user_index.rb b/chef/examples/user_index.rb index 485cff81b8..485cff81b8 100755..100644 --- a/chef/examples/user_index.rb +++ b/chef/examples/user_index.rb diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb index 509353d625..02a1e1d8c7 100644 --- a/chef/lib/chef.rb +++ b/chef/lib/chef.rb @@ -27,7 +27,7 @@ require 'chef/config' Dir[File.join(File.dirname(__FILE__), 'chef/mixin/**/*.rb')].sort.each { |lib| require lib } class Chef - VERSION = '0.6.2' + VERSION = '0.6.3' class << self def fatal!(msg, err = -1) diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb index cc53d3c397..560b7e306b 100644 --- a/chef/lib/chef/client.rb +++ b/chef/lib/chef/client.rb @@ -34,7 +34,7 @@ class Chef include Chef::Mixin::GenerateURL include Chef::Mixin::Checksum - attr_accessor :node, :registration, :safe_name, :json_attribs, :validation_token, :node_name + attr_accessor :node, :registration, :safe_name, :json_attribs, :validation_token, :node_name, :ohai, :ohai_has_run # Creates a new Chef::Client. def initialize() @@ -44,6 +44,9 @@ class Chef @registration = nil @json_attribs = nil @node_name = nil + Ohai::Log.logger = Chef::Log.logger + @ohai = Ohai::System.new + @ohai_has_run = false @rest = Chef::REST.new(Chef::Config[:registration_url]) end @@ -65,9 +68,9 @@ class Chef start_time = Time.now Chef::Log.info("Starting Chef Run") - build_node(@node_name) register authenticate + build_node(@node_name) sync_library_files sync_attribute_files sync_definitions @@ -99,7 +102,21 @@ class Chef Chef::Log.info("Chef Run complete in #{end_time - start_time} seconds") true end - + + def run_ohai + @ohai.all_plugins unless @ohai_has_run + @ohai_has_run = true + end + + def determine_node_name + run_ohai + unless @safe_name && @node_name + @node_name ||= @ohai[:fqdn] ? @ohai[:fqdn] : @ohai[:hostname] + @safe_name = @node_name.gsub(/\./, '_') + end + @node_name + end + # Builds a new node object for this client. Starts with querying for the FQDN of the current # host (unless it is supplied), then merges in the facts from Ohai. # @@ -109,13 +126,9 @@ class Chef # === Returns # node<Chef::Node>:: Returns the created node object, also stored in @node def build_node(node_name=nil, solo=false) - Ohai::Log.logger = Chef::Log.logger - ohai = Ohai::System.new - ohai.all_plugins - - node_name ||= ohai[:fqdn] ? ohai[:fqdn] : ohai[:hostname] + node_name ||= determine_node_name + raise RuntimeError, "Unable to determine node name from ohai" unless node_name - @safe_name = node_name.gsub(/\./, '_') Chef::Log.debug("Building node object for #{@safe_name}") unless solo begin @@ -166,6 +179,7 @@ class Chef # === Returns # true:: Always returns true def register + determine_node_name Chef::Log.debug("Registering #{@safe_name} for an openid") @registration = nil begin @@ -201,6 +215,7 @@ class Chef # === Returns # true:: Always returns true def authenticate + determine_node_name Chef::Log.debug("Authenticating #{@safe_name} via openid") response = @rest.post_rest('openid/consumer/start', { "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{@safe_name}", diff --git a/chef/lib/chef/cookbook/metadata.rb b/chef/lib/chef/cookbook/metadata.rb new file mode 100644 index 0000000000..1d4165604c --- /dev/null +++ b/chef/lib/chef/cookbook/metadata.rb @@ -0,0 +1,398 @@ +# +# 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/from_file' +require 'chef/mixin/params_validate' +require 'chef/mixin/check_helper' +require 'chef/log' + +class Chef + class Cookbook + class Metadata + + include Chef::Mixin::CheckHelper + include Chef::Mixin::ParamsValidate + include Chef::Mixin::FromFile + + attr_accessor :cookbook, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, :replacing, :attributes, :recipes + + # Builds a new Chef::Cookbook::Metadata object. + # + # === Parameters + # cookbook<String>:: An optional cookbook object + # maintainer<String>:: An optional maintainer + # maintainer_email<String>:: An optional maintainer email + # license<String>::An optional license. Default is Apache v2.0 + # + # === Returns + # metadata<Chef::Cookbook::Metadata> + def initialize(cookbook=nil, maintainer='Your Name', maintainer_email='youremail@example.com', license='Apache v2.0') + @cookbook = cookbook + @name = cookbook ? cookbook.name : "" + @long_description = "" + self.maintainer(maintainer) + self.maintainer_email(maintainer_email) + self.license(license) + self.description('A fabulous new cookbook') + @platforms = Mash.new + @dependencies = Mash.new + @recommendations = Mash.new + @suggestions = Mash.new + @conflicting = Mash.new + @providing = Mash.new + @replacing = Mash.new + @attributes = Mash.new + @recipes = Mash.new + if cookbook + @recipes = cookbook.recipes.inject({}) do |r, e| + e = self.name if e =~ /::default$/ + r[e] = "" + self.provides e + r + end + end + end + + # Sets the cookbooks maintainer, or returns it. + # + # === Parameters + # maintainer<String>:: The maintainers name + # + # === Returns + # maintainer<String>:: Returns the current maintainer. + def maintainer(arg=nil) + set_or_return( + :maintainer, + arg, + :kind_of => [ String ] + ) + end + + # Sets the maintainers email address, or returns it. + # + # === Parameters + # maintainer_email<String>:: The maintainers email address + # + # === Returns + # maintainer_email<String>:: Returns the current maintainer email. + def maintainer_email(arg=nil) + set_or_return( + :maintainer_email, + arg, + :kind_of => [ String ] + ) + end + + # Sets the current license, or returns it. + # + # === Parameters + # license<String>:: The current license. + # + # === Returns + # license<String>:: Returns the current license + def license(arg=nil) + set_or_return( + :license, + arg, + :kind_of => [ String ] + ) + end + + # Sets the current description, or returns it. Should be short - one line only! + # + # === Parameters + # description<String>:: The new description + # + # === Returns + # description<String>:: Returns the description + def description(arg=nil) + set_or_return( + :description, + arg, + :kind_of => [ String ] + ) + end + + # Sets the current long description, or returns it. Might come from a README, say. + # + # === Parameters + # long_description<String>:: The new long description + # + # === Returns + # long_description<String>:: Returns the long description + def long_description(arg=nil) + set_or_return( + :long_description, + arg, + :kind_of => [ String ] + ) + end + + # Sets the current cookbook version, or returns it. Must be two digets, seperated + # by a dot. ie: '2.1', or '0.9'. + # + # === Parameters + # version<String>:: The curent version, as a string + # + # === Returns + # version<String>:: Returns the current version + def version(arg=nil) + set_or_return( + :version, + arg, + :regex => /^\d+\.\d+$/ + ) + end + + # Sets the name of the cookbook, or returns it. + # + # === Parameters + # name<String>:: The curent cookbook name. + # + # === Returns + # name<String>:: Returns the current cookbook name. + def name(arg=nil) + set_or_return( + :name, + arg, + :kind_of => [ String ] + ) + end + + # Adds a supported platform, with version checking strings. + # + # === Parameters + # platform<String>,<Symbol>:: The platform (like :ubuntu or :mac_os_x) + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def supports(platform, *versions) + versions.each { |v| _check_version_expression(v) } + @platforms[platform] = versions + @platforms[platform] + end + + # Adds a dependency on another cookbook, with version checking strings. + # + # === Parameters + # cookbook<String>:: The cookbook + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def depends(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @dependencies[cookbook] = versions + @dependencies[cookbook] + end + + # Adds a recommendation for another cookbook, with version checking strings. + # + # === Parameters + # cookbook<String>:: The cookbook + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def recommends(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @recommendations[cookbook] = versions + @recommendations[cookbook] + end + + # Adds a suggestion for another cookbook, with version checking strings. + # + # === Parameters + # cookbook<String>:: The cookbook + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def suggests(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @suggestions[cookbook] = versions + @suggestions[cookbook] + end + + # Adds a conflict for another cookbook, with version checking strings. + # + # === Parameters + # cookbook<String>:: The cookbook + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def conflicts(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @conflicting[cookbook] = versions + @conflicting[cookbook] + end + + # Adds a recipe, definition, or resource provided by this cookbook. + # + # Recipes are specified as normal + # Definitions are followed by (), and can include :params for prototyping + # Resources are the stringified version (service[apache2]) + # + # === Parameters + # recipe, definition, resource<String>:: The thing we provide + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def provides(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @providing[cookbook] = versions + @providing[cookbook] + end + + # Adds a cookbook that is replaced by this one, with version checking strings. + # + # === Parameters + # cookbook<String>:: The cookbook we replace + # *versions<String>:: A list of versions matching << <= = >= >> followed by a version. + # + # === Returns + # versions<Array>:: Returns the list of versions for the platform + def replaces(cookbook, *versions) + versions.each { |v| _check_version_expression(v) } + @replacing[cookbook] = versions + @replacing[cookbook] + end + + # Adds a cookbook that is replaced by this one, with version checking strings. + # + # === Parameters + # recipe<String>:: The recipe + # description<String>:: The description of the recipe + # + # === Returns + # description<String>:: Returns the current description + def recipe(name, description) + @recipes[name] = description + end + + # Adds an attribute that a user needs to configure for this cookbook. Takes + # a name (with the / notation for a nested attribute), followed by any of + # these options + # + # display_name<String>:: What a UI should show for this attribute + # description<String>:: A hint as to what this attr is for + # multiple_values<True>,<False>:: Whether it supports multiple values + # type<String>:: "string", "hash" or "array" - default is "string" + # required<True>,<False>:: Whether this attr is required - default false + # recipes<Array>:: An array of recipes which need this attr set. + # default<String>,<Array>,<Hash>:: The default value + # + # === Parameters + # name<String>:: The name of the attribute ('foo', or 'apache2/log_dir') + # options<Hash>:: The description of the options + # + # === Returns + # options<Hash>:: Returns the current options hash + def attribute(name, options) + validate( + options, + { + :display_name => { :kind_of => String }, + :description => { :kind_of => String }, + :multiple_values => { :equal_to => [ true, false ], :default => false }, + :type => { :equal_to => [ "string", "array", "hash" ], :default => "string" }, + :required => { :equal_to => [ true, false ], :default => false }, + :recipes => { :kind_of => [ Array ], :default => [] }, + :default => { :kind_of => [ String, Array, Hash ] } + } + ) + @attributes[name] = options + @attributes[name] + end + + def _check_version_expression(version_string) + if version_string =~ /^(>>|>=|=|<=|<<) (.+)$/ + [ $1, $2 ] + else + raise ArgumentError, "Version expression #{version_string} is invalid!" + end + end + + def _check_valid_version(to_check, version_string) + (selector, version) = _check_version_expression(version_string) + case selector + when "<<" + to_check < version + when "<=" + to_check <= version + when "=" + to_check == version + when ">=" + to_check >= version + when ">>" + to_check > version + end + end + + def to_json(*a) + result = { + :name => self.name, + :description => self.description, + :long_description => self.long_description, + :maintainer => self.maintainer, + :maintainer_email => self.maintainer_email, + :license => self.license, + :platforms => self.platforms, + :dependencies => self.dependencies, + :recommendations => self.recommendations, + :suggestions => self.suggestions, + :conflicting => self.conflicting, + :providing => self.providing, + :replacing => self.replacing, + :attributes => self.attributes, + :recipes => self.recipes + } + result.to_json(*a) + end + + def self.from_hash(o) + cm = self.new() + cm.name o['name'] + cm.description o['description'] + cm.long_description o['long_description'] + cm.maintainer o['maintainer'] + cm.maintainer_email o['maintainer_email'] + cm.license o['license'] + cm.platforms = o['platforms'] + cm.dependencies = o['dependencies'] + cm.recommendations = o['recommendations'] + cm.suggestions = o['suggestions'] + cm.conflicting = o['conflicting'] + cm.providing = o['providing'] + cm.replacing = o['replacing'] + cm.attributes = o['attributes'] + cm.recipes = o['recipes'] + cm + end + + def self.from_json(string) + o = JSON.parse(string) + self.from_hash(o) + end + + end + end +end diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb index c0a8e3a1f0..d93d272087 100644 --- a/chef/lib/chef/cookbook_loader.rb +++ b/chef/lib/chef/cookbook_loader.rb @@ -158,4 +158,4 @@ class Chef end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb index 4aa2096574..d987541aa6 100644 --- a/chef/lib/chef/platform.rb +++ b/chef/lib/chef/platform.rb @@ -27,13 +27,15 @@ class Chef class Platform @platforms = { - :mac_os_x => {}, + :mac_os_x => { + :default => { + :package => Chef::Provider::Package::Macports + } + }, :freebsd => { :default => { - :group => Chef::Provider::Group::Pw, :package => Chef::Provider::Package::Freebsd, :service => Chef::Provider::Service::Freebsd, - :user => Chef::Provider::User::Pw } }, :ubuntu => { @@ -91,7 +93,8 @@ class Chef :user => Chef::Provider::User::Useradd, :group => Chef::Provider::Group::Groupadd, :http_request => Chef::Provider::HttpRequest, - :route => Chef::Provider::Route + :route => Chef::Provider::Route, + :ifconfig => Chef::Provider::Ifconfig } } @@ -213,7 +216,11 @@ class Chef end else if @platforms.has_key?(args[:platform]) - @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + if @platforms[args[:platform]].has_key?(:default) + @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + else + @platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } + end else @platforms[args[:platform]] = { :default => { diff --git a/chef/lib/chef/provider/group.rb b/chef/lib/chef/provider/group.rb index 5552744e45..30bfcb487c 100644 --- a/chef/lib/chef/provider/group.rb +++ b/chef/lib/chef/provider/group.rb @@ -45,6 +45,7 @@ class Chef end if group_info + @new_resource.gid(group_info.gid) @current_resource.gid(group_info.gid) @current_resource.members(group_info.mem) end @@ -58,9 +59,18 @@ class Chef # <true>:: If a change is required # <false>:: If a change is not required def compare_group - !![ :gid, :members ].find do |group_attrib| - @new_resource.send(group_attrib) != @current_resource.send(group_attrib) + return true if @new_resource.gid != @current_resource.gid + + if(@new_resource.append) + @new_resource.members.each do |member| + next if @current_resource.members.include?(member) + return true + end + else + return true if @new_resource.members != @current_resource.members end + + return false end def action_create diff --git a/chef/lib/chef/provider/group/groupadd.rb b/chef/lib/chef/provider/group/groupadd.rb index 88dce3303f..54ea73702a 100644 --- a/chef/lib/chef/provider/group/groupadd.rb +++ b/chef/lib/chef/provider/group/groupadd.rb @@ -55,8 +55,15 @@ class Chef def modify_group_members unless @new_resource.members.empty? - Chef::Log.debug("#{@new_resource}: setting group members to #{@new_resource.members.join(', ')}") - run_command(:command => "gpasswd -M #{@new_resource.members.join(',')} #{@new_resource.group_name}") + 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 => "gpasswd -a #{member} #{@new_resource.group_name}") + end + else + Chef::Log.debug("#{@new_resource}: setting group members to #{@new_resource.members.join(', ')}") + run_command(:command => "gpasswd -M #{@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 diff --git a/chef/lib/chef/provider/ifconfig.rb b/chef/lib/chef/provider/ifconfig.rb new file mode 100644 index 0000000000..30fc41228d --- /dev/null +++ b/chef/lib/chef/provider/ifconfig.rb @@ -0,0 +1,131 @@ +# +# Author:: Jason Jackson (jason.jackson@monster.com) +# Copyright:: Copyright (c) 2009 Jason 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' + +class Chef + class Provider + class Ifconfig < Chef::Provider + include Chef::Mixin::Command + + def load_current_resource + @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name) + + @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 + + unless status.exitstatus == 0 + raise Chef::Exception::Ifconfig, "ifconfig failed - #{status.inspect}!" + end + + @current_resource + end + + def action_add + # check to see if load_current_resource found 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 + + run_command( + :command => command + ) + @new_resource.updated = true + + end + + # Write out the config files + generate_config + end + + def action_delete + # check to see if load_current_resource found the interface + if @current_resource.device + command = "ifconfig #{@new_resource.device} down" + + run_command( + :command => command + ) + @new_resource.updated = true + else + Chef::Log.debug("Ifconfig #{@current_resource} does not exist") + end + end + + # This is a little lame of me, as if any of these values aren't filled out it leaves blank lines + # in the file. Can refactor later to have this nice and tight. + 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 %> + } + template = ::ERB.new(content) + network_file = ::File.new("/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}", "w") + network_file.puts(template.result(b)) + network_file.close + when ("debian" || "ubantu") + # template + when ("slackware") + # template + end + end + end + end +end diff --git a/chef/lib/chef/provider/package.rb b/chef/lib/chef/provider/package.rb index 33b31dd848..e68248bc0d 100644 --- a/chef/lib/chef/provider/package.rb +++ b/chef/lib/chef/provider/package.rb @@ -36,44 +36,38 @@ class Chef end def action_install - # First, select what version we should be using - install_version = @new_resource.version - install_version ||= @candidate_version - + # If we specified a version, and it's not the current version, move to the current version + if @new_resource.version != nil && @new_resource.version != @current_resource.version + install_version = @new_resource.version + # If it's not installed at all, install it + elsif @current_resource.version == nil + install_version = candidate_version + else + return + end + unless install_version raise(Chef::Exceptions::Package, "No version specified, and no candidate version available!") end - - do_package = false - # If it's not installed at all, install it - if @current_resource.version == nil - do_package = true - # If we specified a version, and it's not the current version, move to the current version - elsif @new_resource.version != nil - if @new_resource.version != @current_resource.version - do_package = true - end - end - if do_package - Chef::Log.info("Installing #{@new_resource} version #{install_version}") + + Chef::Log.info("Installing #{@new_resource} version #{install_version}") - # We need to make sure we handle the preseed file - if @new_resource.response_file - preseed_package(@new_resource.package_name, install_version) - end + # We need to make sure we handle the preseed file + if @new_resource.response_file + preseed_package(@new_resource.package_name, install_version) + end - status = install_package(@new_resource.package_name, install_version) - if status - @new_resource.updated = true - end + status = install_package(@new_resource.package_name, install_version) + if status + @new_resource.updated = true end end def action_upgrade - if @current_resource.version != @candidate_version + if @current_resource.version != candidate_version orig_version = @current_resource.version || "uninstalled" - Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{@candidate_version}") - status = upgrade_package(@new_resource.package_name, @candidate_version) + Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}") + status = upgrade_package(@new_resource.package_name, candidate_version) if status @new_resource.updated = true end diff --git a/chef/lib/chef/provider/package/apt.rb b/chef/lib/chef/provider/package/apt.rb index b9a6e2b43e..5abe1df13a 100644 --- a/chef/lib/chef/provider/package/apt.rb +++ b/chef/lib/chef/provider/package/apt.rb @@ -64,8 +64,7 @@ class Chef run_command( :command => "apt-get -q -y install #{name}=#{version}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end @@ -78,8 +77,7 @@ class Chef run_command( :command => "apt-get -q -y remove #{@new_resource.package_name}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end @@ -88,8 +86,7 @@ class Chef run_command( :command => "apt-get -q -y purge #{@new_resource.package_name}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end @@ -101,8 +98,7 @@ class Chef run_command( :command => "debconf-set-selections #{preseed_file}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end diff --git a/chef/lib/chef/provider/package/freebsd.rb b/chef/lib/chef/provider/package/freebsd.rb index a2755de2af..5d8f5a40cb 100644 --- a/chef/lib/chef/provider/package/freebsd.rb +++ b/chef/lib/chef/provider/package/freebsd.rb @@ -136,11 +136,15 @@ class Chef end def remove_package(name, version) - if @current_resource.version + # a version is mandatory + if version + run_command( + :command => "pkg_delete #{package_name}-#{version}" + ) + else run_command( :command => "pkg_delete #{package_name}-#{@current_resource.version}" ) - Chef::Log.info("Removed package #{package_name}-#{@current_resource.version}") end end end diff --git a/chef/lib/chef/provider/package/macports.rb b/chef/lib/chef/provider/package/macports.rb new file mode 100644 index 0000000000..071d52939d --- /dev/null +++ b/chef/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("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("MacPorts 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 install #{name}" + command << " @#{version}" if version and !version.empty? + run_command( + :command => command + ) + end + end + + def purge_package(name, version) + command = "port uninstall #{name}" + command << " @#{version}" if version and !version.empty? + run_command( + :command => command + ) + end + + def remove_package(name, version) + command = "port deactivate #{name}" + command << " @#{version}" if version and !version.empty? + + run_command( + :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( + :command => "port 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/chef/lib/chef/provider/package/portage.rb b/chef/lib/chef/provider/package/portage.rb index e1ff63f3e4..07e19ff755 100644 --- a/chef/lib/chef/provider/package/portage.rb +++ b/chef/lib/chef/provider/package/portage.rb @@ -28,22 +28,24 @@ class Chef def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) - - 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) - - if installed == "[ Not Installed ]" - @current_resource.version(nil) - else - @current_resource.version(installed) - end - @candidate_version = available - end - unless status.exitstatus == 0 - raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!" + category = @new_resource.package_name.split('/').first + pkg = @new_resource.package_name.split('/').last + + @current_resource.version(nil) + + catdir = "/var/db/pkg/#{category}" + + if( ::File.exists?(catdir) ) + Dir.entries(catdir).each do |entry| + if(entry =~ /^#{Regexp.escape(pkg)}\-(.+)/) + @current_resource.version($1) + Chef::Log.debug("Got current version #{$1}") + break + end + end end - + @current_resource end @@ -65,11 +67,34 @@ class Chef available = installed unless available [available, installed] 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( - :command => "emerge -g --color n --nospinner --quiet =#{name}-#{version}" + :command => "emerge -g --color n --nospinner --quiet #{pkg}" ) end @@ -78,8 +103,14 @@ class Chef end def remove_package(name, version) + if(version) + pkg = "=#{@new_resource.package_name}-#{version}" + else + pkg = "#{@new_resource.package_name}" + end + run_command( - :command => "emerge --unmerge --color n --nospinner --quiet #{@new_resource.package_name}" + :command => "emerge --unmerge --color n --nospinner --quiet #{pkg}" ) end diff --git a/chef/lib/chef/provider/package/rpm.rb b/chef/lib/chef/provider/package/rpm.rb index 4477a542ed..ab39f47573 100644 --- a/chef/lib/chef/provider/package/rpm.rb +++ b/chef/lib/chef/provider/package/rpm.rb @@ -81,10 +81,17 @@ class Chef end def remove_package(name, version) - run_command( - :command => "rpm -e #{@new_resource.name}" - ) + if version + run_command( + :command => "rpm -e #{name}-#{version}" + ) + else + run_command( + :command => "rpm -e #{name}" + ) + end end + end end end diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb index c237f3e438..443085d8f9 100644 --- a/chef/lib/chef/provider/package/rubygems.rb +++ b/chef/lib/chef/provider/package/rubygems.rb @@ -68,6 +68,12 @@ class Chef raise Chef::Exceptions::Package, "#{gem_binary_path} list --local failed - #{status.inspect}!" end + @current_resource + end + + def candidate_version + return @candidate_version if @candidate_version + status = popen4("#{gem_binary_path} list --remote #{@new_resource.package_name}#{' --source=' + @new_resource.source if @new_resource.source}") do |pid, stdin, stdout, stderr| stdout.each do |line| installed_versions = gem_list_parse(line) @@ -79,13 +85,13 @@ class Chef @candidate_version = installed_versions.first end end + + @candidate_version end unless status.exitstatus == 0 raise Chef::Exceptions::Package, "#{gem_binary_path} list --remote failed - #{status.inspect}!" end - - @current_resource end def install_package(name, version) diff --git a/chef/lib/chef/provider/package/yum.rb b/chef/lib/chef/provider/package/yum.rb index 5eab817f0e..93eb37d542 100644 --- a/chef/lib/chef/provider/package/yum.rb +++ b/chef/lib/chef/provider/package/yum.rb @@ -142,9 +142,16 @@ class Chef end def remove_package(name, version) - run_command( - :command => "yum -q -y remove #{name}-#{version}" - ) + if version + run_command( + :command => "yum -q -y remove #{name}-#{version}" + ) + else + run_command( + :command => "yum -q -y remove #{name}" + ) + end + @yum.flush end diff --git a/chef/lib/chef/provider/remote_directory.rb b/chef/lib/chef/provider/remote_directory.rb index 5725be7296..438176f59a 100644 --- a/chef/lib/chef/provider/remote_directory.rb +++ b/chef/lib/chef/provider/remote_directory.rb @@ -19,6 +19,7 @@ require 'chef/provider/file' require 'chef/provider/directory' require 'chef/rest' +require 'chef/mixin/find_preferred_file' require 'chef/resource/directory' require 'chef/resource/remote_file' require 'chef/platform' @@ -29,6 +30,8 @@ require 'net/https' class Chef class Provider class RemoteDirectory < Chef::Provider::Directory + + include ::Chef::Mixin::FindPreferredFile def action_create super @@ -37,51 +40,130 @@ class Chef do_recursive end - def action_create_if_missing - raise Chef::Exceptions::UnsupportedAction, "Remote Directories do not support create_if_missing." - end - - def do_recursive - Chef::Log.debug("Doing a recursive directory transfer for #{@new_resource}") + protected - r = Chef::REST.new(Chef::Config[:remotefile_url]) + def do_recursive + if Chef::Config[:solo] + Chef::Log.debug("Doing a local recursive directory copy for #{@new_resource}") + files_to_transfer = files_for_directory(@new_resource.source) + else + Chef::Log.debug("Doing a remote recursive directory transfer for #{@new_resource}") + r = Chef::REST.new(Chef::Config[:remotefile_url]) + files_to_transfer = r.get_rest(generate_url(@new_resource.source, "files", { :recursive => "true" })) + end + + files_to_transfer.each do |remote_file_source| + fetch_remote_file(remote_file_source) + end + end - files_to_transfer = r.get_rest(generate_url(@new_resource.source, "files", { :recursive => "true" })) + def fetch_remote_file(remote_file_source) + full_path = ::File.join(@new_resource.path, remote_file_source) + full_dir = ::File.dirname(full_path) + + if !::File.directory?(full_dir) + create_directory(full_dir) + end + + remote_file = Chef::Resource::RemoteFile.new(full_path, nil, @node) + remote_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name + remote_file.source(::File.join(@new_resource.source, remote_file_source)) + remote_file.mode(@new_resource.files_mode) if @new_resource.files_mode + remote_file.group(@new_resource.files_group) if @new_resource.files_group + remote_file.owner(@new_resource.files_owner) if @new_resource.files_owner + remote_file.backup(@new_resource.files_backup) if @new_resource.files_backup + + rf_provider_class = Chef::Platform.find_provider_for_node(@node, remote_file) + rf_provider = rf_provider_class.new(@node, remote_file) + rf_provider.load_current_resource + rf_provider.action_create + @new_resource.updated = true if rf_provider.new_resource.updated + end - files_to_transfer.each do |remote_file_source| - full_path = ::File.join(@new_resource.path, remote_file_source) - full_dir = ::File.dirname(full_path) - unless ::File.directory?(full_dir) - new_dir = Chef::Resource::Directory.new(full_dir, nil, @node) - new_dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name - new_dir.mode(@new_resource.mode) - new_dir.group(@new_resource.group) - new_dir.owner(@new_resource.owner) - new_dir.recursive(true) + def create_directory + new_dir = Chef::Resource::Directory.new(full_dir, nil, @node) + new_dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name + new_dir.mode(@new_resource.mode) + new_dir.group(@new_resource.group) + new_dir.owner(@new_resource.owner) + new_dir.recursive(true) + + d_provider_class = Chef::Platform.find_provider_for_node(@node, new_dir) + d_provider = d_provider_class.new(@node, new_dir) + d_provider.load_current_resource + d_provider.action_create + @new_resource.updated = true if d_provider.new_resource.updated + end - d_provider_class = Chef::Platform.find_provider_for_node(@node, new_dir) - d_provider = d_provider_class.new(@node, new_dir) - d_provider.load_current_resource - d_provider.action_create - @new_resource.updated = true if d_provider.new_resource.updated - end - - remote_file = Chef::Resource::RemoteFile.new(full_path, nil, @node) - remote_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name - remote_file.source(::File.join(@new_resource.source, remote_file_source)) - remote_file.mode(@new_resource.files_mode) if @new_resource.files_mode - remote_file.group(@new_resource.files_group) if @new_resource.files_group - remote_file.owner(@new_resource.files_owner) if @new_resource.files_owner - remote_file.backup(@new_resource.files_backup) if @new_resource.files_backup - - rf_provider_class = Chef::Platform.find_provider_for_node(@node, remote_file) - rf_provider = rf_provider_class.new(@node, remote_file) - rf_provider.load_current_resource - rf_provider.action_create - @new_resource.updated = true if rf_provider.new_resource.updated + def action_create_if_missing + raise Chef::Exceptions::UnsupportedAction, "Remote Directories do not support create_if_missing." + end + # Pulled from chef-server-slice files controller + + def files_for_directory(path) + directory = find_preferred_file( + @new_resource.cookbook_name, + :remote_file, + path, + @node[:fqdn], + @node[:platform], + @node[:platform_version] + ) + + unless (directory && ::File.directory?(directory)) + raise NotFound, "Cannot find a suitable directory" + end + + directory_listing = Array.new + Dir[::File.join(directory, '**', '*')].sort { |a,b| b <=> a }.each do |file| + next if ::File.directory?(file) + file =~ /^#{directory}\/(.+)$/ + directory_listing << $1 end + directory_listing end - end + + + def do_recursive_old + Chef::Log.debug("Doing a recursive directory transfer for #{@new_resource}") + + r = Chef::REST.new(Chef::Config[:remotefile_url]) + + files_to_transfer = r.get_rest(generate_url(@new_resource.source, "files", { :recursive => "true" })) + + files_to_transfer.each do |remote_file_source| + full_path = ::File.join(@new_resource.path, remote_file_source) + full_dir = ::File.dirname(full_path) + unless ::File.directory?(full_dir) + new_dir = Chef::Resource::Directory.new(full_dir, nil, @node) + new_dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name + new_dir.mode(@new_resource.mode) + new_dir.group(@new_resource.group) + new_dir.owner(@new_resource.owner) + new_dir.recursive(true) + + d_provider_class = Chef::Platform.find_provider_for_node(@node, new_dir) + d_provider = d_provider_class.new(@node, new_dir) + d_provider.load_current_resource + d_provider.action_create + @new_resource.updated = true if d_provider.new_resource.updated + end + + remote_file = Chef::Resource::RemoteFile.new(full_path, nil, @node) + remote_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name + remote_file.source(::File.join(@new_resource.source, remote_file_source)) + remote_file.mode(@new_resource.files_mode) if @new_resource.files_mode + remote_file.group(@new_resource.files_group) if @new_resource.files_group + remote_file.owner(@new_resource.files_owner) if @new_resource.files_owner + remote_file.backup(@new_resource.files_backup) if @new_resource.files_backup + + rf_provider_class = Chef::Platform.find_provider_for_node(@node, remote_file) + rf_provider = rf_provider_class.new(@node, remote_file) + rf_provider.load_current_resource + rf_provider.action_create + @new_resource.updated = true if rf_provider.new_resource.updated + end + end end end diff --git a/chef/lib/chef/provider/route.rb b/chef/lib/chef/provider/route.rb index 16d7d9c43d..b45813b0c4 100644 --- a/chef/lib/chef/provider/route.rb +++ b/chef/lib/chef/provider/route.rb @@ -19,6 +19,7 @@ require 'chef/log' require 'chef/mixin/command' require 'chef/provider' +require 'erb' class Chef class Provider @@ -46,7 +47,7 @@ class Chef end unless status.exitstatus == 0 - raise Chef::Exceptions::Route, "route failed - #{status.inspect}!" + raise Chef::Exception::Route, "route failed - #{status.inspect}!" end @current_resource @@ -72,6 +73,8 @@ class Chef else Chef::Log.debug("Route #{@current_resource} already exists") end + # Write out the config files + generate_config end def action_delete @@ -91,6 +94,25 @@ class Chef Chef::Log.debug("Route #{@current_resource} does not exist") end end + + def generate_config + b = binding + case node[:platform] + when ("centos" || "redhat" || "fedora") + content = %{ +<% if @new_resource.networking %>NETWORKING=<%= @new_resource.networking %><% end %> +<% if @new_resource.networking_ipv6 %>NETWORKING_IPV6=<%= @new_resource.networking_ipv6 %><% end %> +<% if @new_resource.hostname %>HOSTNAME=<%= @new_resource.hostname %><% end %> +<% if @new_resource.name %>GATEWAY=<%= @new_resource.name %><% end %> +<% if @new_resource.domainname %>DOMAINNAME=<%= @new_resource.domainname %><% end %> +<% if @new_resource.domainname %>DOMAIN=<%= @new_resource.domainname %><% end %> + } + template = ::ERB.new(content) + network_file = ::File.new("/etc/sysconfig/network", "w") + network_file.puts(template.result(b)) + network_file.close + end + end end end end diff --git a/chef/lib/chef/provider/service/redhat.rb b/chef/lib/chef/provider/service/redhat.rb index 73e691a148..64bfdc4076 100644 --- a/chef/lib/chef/provider/service/redhat.rb +++ b/chef/lib/chef/provider/service/redhat.rb @@ -49,11 +49,11 @@ class Chef end def enable_service() - run_command(:command => "/sbin/chkconfig --add #{@new_resource.service_name}") + run_command(:command => "/sbin/chkconfig #{@new_resource.service_name} on") end def disable_service() - run_command(:command => "/sbin/chkconfig --del #{@new_resource.service_name}") + run_command(:command => "/sbin/chkconfig #{@new_resource.service_name} off") end end diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb index 96e6430620..7417407924 100644 --- a/chef/lib/chef/recipe.rb +++ b/chef/lib/chef/recipe.rb @@ -203,4 +203,4 @@ class Chef end end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb index dc915cac14..e2bf9fb302 100644 --- a/chef/lib/chef/resource.rb +++ b/chef/lib/chef/resource.rb @@ -136,23 +136,20 @@ class Chef ignore_failure(arg) end - def notifies(action, resources, timing=:delayed) - timing = check_timing(timing) - rarray = resources.kind_of?(Array) ? resources : [ resources ] - rarray.each do |resource| - action_sym = action.to_sym - if @actions.has_key?(action_sym) - @actions[action_sym][timing] << resource - else - @actions[action_sym] = Hash.new - @actions[action_sym][:delayed] = Array.new - @actions[action_sym][:immediate] = Array.new - @actions[action_sym][timing] << resource - end + def notifies(*args) + raise ArgumentError, "Wrong number of arguments (should be 1, 2, or 3)" unless ( args.size > 0 && args.size < 4) + if args.size > 1 + notifies_helper(*args) + else + resources_array = *args + resources_array.each do |resource| + resource.each do |key, value| + notifies_helper(value[0], key, value[1]) + end + end end - true end - + def resources(*args) @collection.resources(*args) end @@ -239,5 +236,22 @@ class Chef end timing end + + def notifies_helper(action, resources, timing=:delayed) + timing = check_timing(timing) + rarray = resources.kind_of?(Array) ? resources : [ resources ] + rarray.each do |resource| + action_sym = action.to_sym + if @actions.has_key?(action_sym) + @actions[action_sym][timing] << resource + else + @actions[action_sym] = Hash.new + @actions[action_sym][:delayed] = Array.new + @actions[action_sym][:immediate] = Array.new + @actions[action_sym][timing] << resource + end + end + true + end end end diff --git a/chef/lib/chef/resource/group.rb b/chef/lib/chef/resource/group.rb index ddf583198c..25d8598f42 100644 --- a/chef/lib/chef/resource/group.rb +++ b/chef/lib/chef/resource/group.rb @@ -27,6 +27,7 @@ class Chef @gid = nil @members = [] @action = :create + @append = false @allowed_actions.push(:create, :remove, :modify, :manage) end @@ -55,6 +56,13 @@ class Chef ) end + def append(arg=nil) + set_or_return( + :append, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end end end end diff --git a/chef/lib/chef/resource/ifconfig.rb b/chef/lib/chef/resource/ifconfig.rb new file mode 100644 index 0000000000..22096f486b --- /dev/null +++ b/chef/lib/chef/resource/ifconfig.rb @@ -0,0 +1,134 @@ +# +# Author:: Jason K. Jackson (jason.jackson@monster.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/resource' + +class Chef + class Resource + class Ifconfig < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :ifconfig + @target = name + @action = :add + @allowed_actions.push(:add, :delete) + @hwaddr = nil + @mask = nil + @inet_addr = nil + @bcast = nil + @mtu = nil + @metric = nil + @device = nil + @onboot = nil + @network = nil + @bootproto = nil + end + + def target(arg=nil) + set_or_return( + :target, + arg, + :kind_of => String + ) + end + + def device(arg=nil) + set_or_return( + :device, + arg, + :kind_of => String + ) + end + + def hwaddr(arg=nil) + set_or_return( + :hwaddr, + arg, + :kind_of => String + ) + end + + def inet_addr(arg=nil) + set_or_return( + :inet_addr, + arg, + :kind_of => String + ) + end + + def bcast(arg=nil) + set_or_return( + :bcast, + arg, + :kind_of => String + ) + end + + def mask(arg=nil) + set_or_return( + :mask, + arg, + :kind_of => String + ) + end + + def mtu(arg=nil) + set_or_return( + :mtu, + arg, + :kind_of => String + ) + end + + def metric(arg=nil) + set_or_return( + :metric, + arg, + :kind_of => String + ) + end + + def onboot(arg=nil) + set_or_return( + :onboot, + arg, + :kind_of => String + ) + end + + def network(arg=nil) + set_or_return( + :network, + arg, + :kind_of => String + ) + end + + def bootproto(arg=nil) + set_or_return( + :bootproto, + arg, + :kind_of => String + ) + end + end + end +end + + diff --git a/chef/lib/chef/resource/macports_package.rb b/chef/lib/chef/resource/macports_package.rb new file mode 100644 index 0000000000..b72832260e --- /dev/null +++ b/chef/lib/chef/resource/macports_package.rb @@ -0,0 +1,29 @@ +# +# Author:: David Balatero (<dbalatero@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 Resource + class MacportsPackage < Chef::Resource::Package + def initialize(name, collection = nil, node = nil) + super(name, collection, node) + @resource_name = :macports_package + @provider = Chef::Provider::Package::Macports + end + end + end +end diff --git a/chef/lib/chef/resource/route.rb b/chef/lib/chef/resource/route.rb index 0c124b587a..276dbedb31 100644 --- a/chef/lib/chef/resource/route.rb +++ b/chef/lib/chef/resource/route.rb @@ -33,6 +33,51 @@ class Chef @metric = nil @device = nil @route_type = :host + @networking = nil + @networking_ipv6 = nil + @hostname = nil + @domainname = nil + @domain = nil + end + + def networking(arg=nil) + set_or_return( + :networking, + arg, + :kind_of => String + ) + end + + def networking_ipv6(arg=nil) + set_or_return( + :networking_ipv6, + arg, + :kind_of => String + ) + end + + def hostname(arg=nil) + set_or_return( + :hostname, + arg, + :kind_of => String + ) + end + + def domainname(arg=nil) + set_or_return( + :domainname, + arg, + :kind_of => String + ) + end + + def domain(arg=nil) + set_or_return( + :domain, + arg, + :kind_of => String + ) end def target(arg=nil) diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb index f712d02ab7..9c51e7b9df 100644 --- a/chef/lib/chef/rest.rb +++ b/chef/lib/chef/rest.rb @@ -23,15 +23,20 @@ require 'net/https' require 'uri' require 'json' require 'tempfile' +require 'singleton' class Chef class REST + + class CookieJar < Hash + include Singleton + end attr_accessor :url, :cookies def initialize(url) @url = url - @cookies = Hash.new + @cookies = CookieJar.instance end # Register for an OpenID diff --git a/chef/lib/chef/tasks/chef_repo.rake b/chef/lib/chef/tasks/chef_repo.rake new file mode 100644 index 0000000000..0d824e2bd6 --- /dev/null +++ b/chef/lib/chef/tasks/chef_repo.rake @@ -0,0 +1,199 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'rubygems' +require 'json' +require 'chef' +require 'chef/cookbook/metadata' +require 'tempfile' + +desc "Update your repository from source control" +task :update do + puts "** Updating your repository" + + case $vcs + when :svn + sh %{svn up} + when :git + pull = false + pull = true if File.join(TOPDIR, ".git", "remotes", "origin") + IO.foreach(File.join(TOPDIR, ".git", "config")) do |line| + pull = true if line =~ /\[remote "origin"\]/ + end + if pull + sh %{git pull} + else + puts "* Skipping git pull, no origin specified" + end + else + puts "* No SCM configured, skipping update" + end +end + +desc "Test your cookbooks for syntax errors" +task :test do + puts "** Testing your cookbooks for syntax errors" + + recipes = ["*cookbooks"].map { |folder| + Dir[File.join(TOPDIR, folder, "**", "*.rb")] + }.flatten + + recipes.each do |recipe| + print "Testing recipe #{recipe}: " + sh %{ruby -c #{recipe}} do |ok, res| + if ! ok + raise "Syntax error in #{recipe}" + end + end + end +end + +desc "Install the latest copy of the repository on this Chef Server" +task :install => [ :update, :test ] do + puts "** Installing your cookbooks" + directories = [ + COOKBOOK_PATH, + SITE_COOKBOOK_PATH, + CHEF_CONFIG_PATH + ] + puts "* Creating Directories" + directories.each do |dir| + sh "sudo mkdir -p #{dir}" + sh "sudo chown root #{dir}" + end + puts "* Installing new Cookbooks" + sh "sudo rsync -rlP --delete --exclude '.svn' cookbooks/ #{COOKBOOK_PATH}" + puts "* Installing new Site Cookbooks" + sh "sudo rsync -rlP --delete --exclude '.svn' cookbooks/ #{COOKBOOK_PATH}" + puts "* Installing new Chef Server Config" + sh "sudo cp config/server.rb #{CHEF_SERVER_CONFIG}" + puts "* Installing new Chef Client Config" + sh "sudo cp config/client.rb #{CHEF_CLIENT_CONFIG}" +end + +desc "By default, run rake test" +task :default => [ :test ] + +desc "Create a new cookbook (with COOKBOOK=name)" +task :new_cookbook do + create_cookbook(File.join(TOPDIR, "cookbooks")) +end + +def create_cookbook(dir) + raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"] + puts "** Creating cookbook #{ENV["COOKBOOK"]}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}" + sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}" + unless File.exists?(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb")) + open(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb"), "w") do |file| + file.puts <<-EOH +# +# Cookbook Name:: #{ENV["COOKBOOK"]} +# Recipe:: default +# +# Copyright #{Time.now.year}, #{COMPANY_NAME} +# +EOH + case NEW_COOKBOOK_LICENSE + when :apachev2 + file.puts <<-EOH +# 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. +# +EOH + when :none + file.puts <<-EOH +# All rights reserved - Do Not Redistribute +# +EOH + end + end + end +end + +desc "Create a new self-signed SSL certificate for FQDN=foo.example.com" +task :ssl_cert do + $expect_verbose = true + fqdn = ENV["FQDN"] + fqdn =~ /^(.+?)\.(.+)$/ + hostname = $1 + domain = $2 + raise "Must provide FQDN!" unless fqdn && hostname && domain + puts "** Creating self signed SSL Certificate for #{fqdn}" + sh("(cd #{CADIR} && openssl genrsa 2048 > #{fqdn}.key)") + sh("(cd #{CADIR} && chmod 644 #{fqdn}.key)") + puts "* Generating Self Signed Certificate Request" + tf = Tempfile.new("#{fqdn}.ssl-conf") + ssl_config = <<EOH +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] +C = #{SSL_COUNTRY_NAME} +ST = #{SSL_STATE_NAME} +L = #{SSL_LOCALITY_NAME} +O = #{COMPANY_NAME} +OU = #{SSL_ORGANIZATIONAL_UNIT_NAME} +CN = #{fqdn} +emailAddress = #{SSL_EMAIL_ADDRESS} +EOH + tf.puts(ssl_config) + tf.close + sh("(cd #{CADIR} && openssl req -config '#{tf.path}' -new -x509 -nodes -sha1 -days 3650 -key #{fqdn}.key > #{fqdn}.crt)") + sh("(cd #{CADIR} && openssl x509 -noout -fingerprint -text < #{fqdn}.crt > #{fqdn}.info)") + sh("(cd #{CADIR} && cat #{fqdn}.crt #{fqdn}.key > #{fqdn}.pem)") + sh("(cd #{CADIR} && chmod 644 #{fqdn}.pem)") +end + +desc "Build cookbook metadata" +task :metadata do + Chef::Config[:cookbook_path] = [ COOKBOOK_PATH, SITE_COOKBOOK_PATH ] + cl = Chef::CookbookLoader.new + cl.each do |cookbook| + if ENV['COOKBOOK'] + next unless cookbook.name.to_s == ENV['COOKBOOK'] + end + cook_meta = Chef::Cookbook::Metadata.new(cookbook) + Chef::Config.cookbook_path.each do |cdir| + metadata_rb_file = File.join(cdir, cookbook.name.to_s, 'metadata.rb') + metadata_json_file = File.join(cdir, cookbook.name.to_s, 'metadata.json') + if File.exists?(metadata_rb_file) + puts "Generating metadata for #{cookbook.name}" + cook_meta.from_file(metadata_rb_file) + File.open(metadata_json_file, "w") do |f| + f.write(JSON.pretty_generate(cook_meta)) + end + end + end + end +end + diff --git a/chef/spec/unit/cookbook/metadata_spec.rb b/chef/spec/unit/cookbook/metadata_spec.rb new file mode 100644 index 0000000000..b3242600c2 --- /dev/null +++ b/chef/spec/unit/cookbook/metadata_spec.rb @@ -0,0 +1,444 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) +require 'chef/cookbook/metadata' + +describe Chef::Cookbook::Metadata do + before(:each) do + @cookbook = Chef::Cookbook.new('test_cookbook') + @meta = Chef::Cookbook::Metadata.new(@cookbook) + end + + describe "initialize" do + it "should return a Chef::Cookbook::Metadata object" do + @meta.should be_a_kind_of(Chef::Cookbook::Metadata) + end + + it "should allow a cookbook as the first argument" do + lambda { Chef::Cookbook::Metadata.new(@cookbook) }.should_not raise_error + end + + it "should allow an maintainer name for the second argument" do + lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown') }.should_not raise_error + end + + it "should set the maintainer name from the second argument" do + md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown') + md.maintainer.should == 'Bobo T. Clown' + end + + it "should allow an maintainer email for the third argument" do + lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo@clown.co') }.should_not raise_error + end + + it "should set the maintainer email from the third argument" do + md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo@clown.co') + md.maintainer_email.should == 'bobo@clown.co' + end + + it "should allow a license for the fourth argument" do + lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo@clown.co', 'Clown License v1') }.should_not raise_error + end + + it "should set the license from the fourth argument" do + md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo@clown.co', 'Clown License v1') + md.license.should == 'Clown License v1' + end + end + + describe "cookbook" do + it "should return the cookbook we were initialized with" do + @meta.cookbook.should eql(@cookbook) + end + end + + describe "name" do + it "should return the name of the cookbook" do + @meta.name.should eql(@cookbook.name) + end + end + + describe "platforms" do + it "should return the current platform hash" do + @meta.platforms.should be_a_kind_of(Hash) + end + end + + describe "adding a supported platform" do + it "should support adding a supported platform with a single expression" do + @meta.supports("ubuntu", ">= 8.04") + @meta.platforms["ubuntu"].should == [ '>= 8.04' ] + end + + it "should support adding a supported platform with multiple expressions" do + @meta.supports("ubuntu", ">= 8.04", "= 9.04") + @meta.platforms["ubuntu"].should == [ '>= 8.04', "= 9.04" ] + end + end + + describe "meta-data attributes" do + params = { + :maintainer => "Adam Jacob", + :maintainer_email => "adam@opscode.com", + :license => "Apache v2.0", + :description => "Foobar!", + :long_description => "Much Longer\nSeriously", + :version => "0.6" + } + params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value| + describe field do + it "should be set-able via #{field}" do + @meta.send(field, field_value).should eql(field_value) + end + it "should be get-able via #{field}" do + @meta.send(field, field_value) + @meta.send(field).should eql(field_value) + end + end + end + end + + describe "dependency specification" do + dep_types = { + :depends => [ :dependencies, "foo::bar", ">> 0.2" ], + :recommends => [ :recommendations, "foo::bar", ">> 0.2" ], + :suggests => [ :suggestions, "foo::bar", ">> 0.2" ], + :conflicts => [ :conflicting, "foo::bar", ">> 0.2" ], + :provides => [ :providing, "foo::bar", ">> 0.2" ], + :replaces => [ :replacing, "foo::bar", ">> 0.2" ], + } + dep_types.sort { |a,b| a.to_s <=> b.to_s }.each do |dep, dep_args| + check_with = dep_args.shift + describe dep do + it "should be set-able via #{dep}" do + @meta.send(dep, *dep_args).should == [dep_args[1]] + end + it "should be get-able via #{check_with}" do + @meta.send(dep, *dep_args) + @meta.send(check_with).should == { dep_args[0] => [dep_args[1]] } + end + end + end + end + + describe "cookbook attributes" do + it "should allow you set an attributes metadata" do + attrs = { + "display_name" => "MySQL Databases", + "multiple_values" => true, + "type" => 'string', + "required" => false, + "recipes" => [ "mysql::server", "mysql::master" ], + "default" => [ ] + } + @meta.attribute("/db/mysql/databases", attrs).should == attrs + end + + it "should not accept anything but a string for display_name" do + lambda { + @meta.attribute("db/mysql/databases", :display_name => "foo") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :display_name => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should not accept anything but a string for the description" do + lambda { + @meta.attribute("db/mysql/databases", :description => "foo") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :description => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should let multiple_values be true or false" do + lambda { + @meta.attribute("db/mysql/databases", :multiple_values => true) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :multiple_values => false) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :multiple_values => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should set multiple_values to false by default" do + @meta.attribute("db/mysql/databases", {}) + @meta.attributes["db/mysql/databases"][:multiple_values].should == false + end + + it "should let type be string, array or hash" do + lambda { + @meta.attribute("db/mysql/databases", :type => "string") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :type => "array") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :type => "hash") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :type => Array.new) + }.should raise_error(ArgumentError) + end + + it "should let required be true or false" do + lambda { + @meta.attribute("db/mysql/databases", :required => true) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :required => false) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :required => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should set required to false by default" do + @meta.attribute("db/mysql/databases", {}) + @meta.attributes["db/mysql/databases"][:required].should == false + end + + it "should make sure recipes is an array" do + lambda { + @meta.attribute("db/mysql/databases", :recipes => []) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :required => Hash.new) + }.should raise_error(ArgumentError) + end + + it "should set recipes to an empty array by default" do + @meta.attribute("db/mysql/databases", {}) + @meta.attributes["db/mysql/databases"][:recipes].should == [] + end + + it "should allow the default value to be a string, array, or hash" do + lambda { + @meta.attribute("db/mysql/databases", :default => []) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :default => {}) + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :default => "alice in chains") + }.should_not raise_error(ArgumentError) + lambda { + @meta.attribute("db/mysql/databases", :required => :not_gonna_do_it) + }.should raise_error(ArgumentError) + end + + end + + describe "checking version expression" do + it "should accept >> 8.04" do + @meta._check_version_expression(">> 8.04").should == [ ">>", "8.04" ] + end + + it "should accept >= 8.04" do + @meta._check_version_expression(">= 8.04").should == [ ">=", "8.04" ] + end + + it "should accept = 8.04" do + @meta._check_version_expression("= 8.04").should == [ "=", "8.04" ] + end + + it "should accept <= 8.04" do + @meta._check_version_expression("<= 8.04").should == [ "<=", "8.04" ] + end + + it "should accept << 8.04" do + @meta._check_version_expression("<< 8.04").should == [ "<<", "8.04" ] + end + + it "should raise an exception on an invalid version expression" do + lambda { + @meta._check_version_expression("tried to << love you") + }.should raise_error(ArgumentError) + end + end + + describe "check for a valid version" do + it "should think 8.00 is << 8.04" do + @meta._check_valid_version("8.00", "<< 8.04").should == true + end + + it "should think 9.04 is not << 8.04" do + @meta._check_valid_version("9.04", "<< 8.04").should == false + end + + it "should think 8.00 is <= 8.04" do + @meta._check_valid_version("8.00", "<= 8.04").should == true + end + + it "should think 8.04 is <= 8.04" do + @meta._check_valid_version("8.04", "<= 8.04").should == true + end + + it "should think 9.04 is not <= 8.04" do + @meta._check_valid_version("9.04", "<= 8.04").should == false + end + + it "should think 8.00 is not = 8.04" do + @meta._check_valid_version("8.00", "= 8.04").should == false + end + + it "should think 8.04 is = 8.04" do + @meta._check_valid_version("8.04", "= 8.04").should == true + end + + it "should think 8.00 is not >= 8.04" do + @meta._check_valid_version("8.00", ">= 8.04").should == false + end + + it "should think 9.04 is >= 8.04" do + @meta._check_valid_version("9.04", ">= 8.04").should == true + end + + it "should think 8.04 is >= 8.04" do + @meta._check_valid_version("8.04", ">= 8.04").should == true + end + + it "should think 8.00 is not >> 8.04" do + @meta._check_valid_version("8.00", ">> 8.04").should == false + end + + it "should think 8.04 is not >> 8.04" do + @meta._check_valid_version("8.04", ">> 8.04").should == false + end + + it "should think 9.04 is >> 8.04" do + @meta._check_valid_version("9.04", ">> 8.04").should == true + end + end + + describe "recipes" do + before(:each) do + @cookbook.recipe_files = [ "default.rb", "enlighten.rb" ] + @meta = Chef::Cookbook::Metadata.new(@cookbook) + end + + it "should have the names of the recipes" do + @meta.recipes["test_cookbook"].should == "" + @meta.recipes["test_cookbook::enlighten"].should == "" + end + + it "should let you set the description for a recipe" do + @meta.recipe "test_cookbook", "It, um... tests stuff?" + @meta.recipes["test_cookbook"].should == "It, um... tests stuff?" + end + + it "should automatically provide each recipe" do + @meta.providing.has_key?("test_cookbook").should == true + @meta.providing.has_key?("test_cookbook::enlighten").should == true + end + + end + + describe "json" do + before(:each) do + @cookbook.recipe_files = [ "default.rb", "enlighten.rb" ] + @meta = Chef::Cookbook::Metadata.new(@cookbook) + @meta.version "1.0" + @meta.maintainer "Bobo T. Clown" + @meta.maintainer_email "bobo@example.com" + @meta.long_description "I have a long arm!" + @meta.supports :ubuntu, ">> 8.04" + @meta.depends "bobo", "= 1.0" + @meta.depends "bobotclown", "= 1.1" + @meta.recommends "snark", "<< 3.0" + @meta.suggests "kindness", ">> 2.0", "<< 4.0" + @meta.conflicts "hatred" + @meta.provides "foo(:bar, :baz)" + @meta.replaces "snarkitron" + @meta.recipe "test_cookbook::enlighten", "is your buddy" + @meta.attribute "bizspark/has_login", + :display_name => "You have nothing" + end + + describe "serialize" do + before(:each) do + @serial = JSON.parse(@meta.to_json) + end + + it "should serialize to a json hash" do + JSON.parse(@meta.to_json).should be_a_kind_of(Hash) + end + + %w{ + name + description + long_description + maintainer + maintainer_email + license + platforms + dependencies + suggestions + recommendations + conflicting + providing + replacing + attributes + recipes + }.each do |t| + it "should include '#{t}'" do + @serial[t].should == @meta.send(t.to_sym) + end + end + end + + describe "deserialize" do + before(:each) do + @deserial = Chef::Cookbook::Metadata.from_json(@meta.to_json) + end + + it "should deserialize to a Chef::Cookbook::Metadata object" do + @deserial.should be_a_kind_of(Chef::Cookbook::Metadata) + end + + %w{ + name + description + long_description + maintainer + maintainer_email + license + platforms + dependencies + suggestions + recommendations + conflicting + providing + replacing + attributes + recipes + }.each do |t| + it "should match '#{t}'" do + @deserial.send(t.to_sym).should == @meta.send(t.to_sym) + end + end + + end + + end + +end diff --git a/chef/spec/unit/platform_spec.rb b/chef/spec/unit/platform_spec.rb index 299ecd327b..f7f7e6801a 100644 --- a/chef/spec/unit/platform_spec.rb +++ b/chef/spec/unit/platform_spec.rb @@ -140,8 +140,12 @@ describe Chef::Platform do :provider => "masterful" ) Chef::Platform.platforms[:default][:file].should eql("masterful") + + Chef::Platform.platforms = { :neurosis => {} } + Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful") + Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful") end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/provider/group/groupadd_spec.rb b/chef/spec/unit/provider/group/groupadd_spec.rb index ccd150391f..6e8b11a663 100644 --- a/chef/spec/unit/provider/group/groupadd_spec.rb +++ b/chef/spec/unit/provider/group/groupadd_spec.rb @@ -129,7 +129,8 @@ describe Chef::Provider::Group::Groupadd, "modify_group_members" do @new_resource = mock("Chef::Resource::Group", :null_object => true, :group_name => "aj", - :members => [ "all", "your", "base" ] + :members => [ "all", "your", "base" ], + :append => false ) @new_resource.stub!(:to_s).and_return("group[aj]") @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource) @@ -158,9 +159,18 @@ describe Chef::Provider::Group::Groupadd, "modify_group_members" do end it "should run gpasswd with the members joined by ',' followed by the target group" do - @provider.should_receive(:run_command).with({:command => "gpasswd -M all,your,base aj"}) - @provider.modify_group_members + @provider.should_receive(:run_command).with({:command => "gpasswd -M all,your,base aj"}) + @provider.modify_group_members end + + it "should run gpasswd individually for each user when the append option is set" do + @new_resource.stub!(:append).and_return(true) + @provider.should_receive(:run_command).with({:command => "gpasswd -a all aj"}) + @provider.should_receive(:run_command).with({:command => "gpasswd -a your aj"}) + @provider.should_receive(:run_command).with({:command => "gpasswd -a base aj"}) + @provider.modify_group_members + end + end end diff --git a/chef/spec/unit/provider/group_spec.rb b/chef/spec/unit/provider/group_spec.rb index 5d2643b0ee..7b1f7a9524 100644 --- a/chef/spec/unit/provider/group_spec.rb +++ b/chef/spec/unit/provider/group_spec.rb @@ -109,7 +109,8 @@ describe Chef::Provider::Group, "compare_group" do :null_object => true, :group_name => "aj", :gid => 50, - :members => [ "root", "aj"] + :members => [ "root", "aj"], + :append => false ) @current_resource = mock("Chef::Resource::Group", :null_object => true, @@ -128,9 +129,22 @@ describe Chef::Provider::Group, "compare_group" do end end - it "should return false if no change is required" do + it "should return false if gid and members are equal" do + @provider.compare_group.should be_false + end + + it "should return false if append is true and the group member(s) already exists" do + @current_resource.members << "extra_user" + @new_resource.stub!(:append).and_return(true) @provider.compare_group.should be_false end + + it "should return true if append is true and the group member(s) do not already exist" do + @new_resource.members << "extra_user" + @new_resource.stub!(:append).and_return(true) + @provider.compare_group.should be_true + end + end describe Chef::Provider::Group, "action_create" do diff --git a/chef/spec/unit/provider/package/macports_spec.rb b/chef/spec/unit/provider/package/macports_spec.rb new file mode 100644 index 0000000000..ca004116ac --- /dev/null +++ b/chef/spec/unit/provider/package/macports_spec.rb @@ -0,0 +1,180 @@ +# +# Author:: David Balatero (<dbalatero@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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) + +describe Chef::Provider::Package::Macports do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "zsh", + :package_name => "zsh", + :version => nil + ) + @current_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "zsh", + :package_name => "zsh", + :version => nil + ) + + @provider = Chef::Provider::Package::Macports.new(@node, @new_resource) + Chef::Resource::Package.stub!(:new).and_return(@current_resource) + + @status = mock("Status", :exitstatus => 0) + @stdin = mock("STDIN", :null_object => true) + @stdout = mock("STDOUT", :null_object => true) + @stderr = mock("STDERR", :null_object => true) + @pid = mock("PID", :null_object => true) + end + + describe "load_current_resource" do + it "should create a current resource with the name of the new_resource" do + @provider.should_receive(:current_installed_version).and_return(nil) + @provider.should_receive(:macports_candidate_version).and_return("4.2.7") + + @provider.load_current_resource + @provider.current_resource.name.should == "zsh" + end + + it "should create a current resource with the version if the package is installed" do + @provider.should_receive(:macports_candidate_version).and_return("4.2.7") + @provider.should_receive(:current_installed_version).and_return("4.2.7") + + @provider.load_current_resource + @provider.candidate_version.should == "4.2.7" + end + + it "should create a current resource with a nil version if the package is not installed" do + @provider.should_receive(:current_installed_version).and_return(nil) + @provider.should_receive(:macports_candidate_version).and_return("4.2.7") + @provider.load_current_resource + @provider.current_resource.version.should be_nil + end + + it "should set a candidate version if one exists" do + @provider.should_receive(:current_installed_version).and_return(nil) + @provider.should_receive(:macports_candidate_version).and_return("4.2.7") + @provider.load_current_resource + @provider.candidate_version.should == "4.2.7" + end + end + + describe "current_installed_version" do + it "should return the current version if the package is installed" do + @stdout.should_receive(:read).and_return(<<EOF +The following ports are currently installed: + openssl @0.9.8k_0 (active) +EOF + ) + + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.current_installed_version.should == "0.9.8k_0" + end + + it "should return nil if a package is not currently installed" do + @stdout.should_receive(:read).and_return(" \n") + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.current_installed_version.should be_nil + end + end + + describe "macports_candidate_version" do + it "should return the latest available version of a given package" do + @stdout.should_receive(:read).and_return("version: 4.2.7\n") + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.macports_candidate_version.should == "4.2.7" + end + + it "should return nil if there is no version for a given package" do + @stdout.should_receive(:read).and_return("Error: port fadsfadsfads not found\n") + @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.macports_candidate_version.should be_nil + end + end + + describe "install_package" do + it "should run the port install command with the correct version" do + @current_resource.should_receive(:version).and_return("4.1.6") + @provider.current_resource = @current_resource + @provider.should_receive(:run_command).with(:command => "port install zsh @4.2.7") + + @provider.install_package("zsh", "4.2.7") + end + + it "should not do anything if a package already exists with the same version" do + @current_resource.should_receive(:version).and_return("4.2.7") + @provider.current_resource = @current_resource + @provider.should_not_receive(:run_command) + + @provider.install_package("zsh", "4.2.7") + end + end + + describe "purge_package" do + it "should run the port uninstall command with the correct version" do + @provider.should_receive(:run_command).with(:command => "port uninstall zsh @4.2.7") + @provider.purge_package("zsh", "4.2.7") + end + + it "should purge the currently active version if no explicit version is passed in" do + @provider.should_receive(:run_command).with(:command => "port uninstall zsh") + @provider.purge_package("zsh", nil) + end + end + + describe "remove_package" do + it "should run the port deactivate command with the correct version" do + @provider.should_receive(:run_command).with(:command => "port deactivate zsh @4.2.7") + @provider.remove_package("zsh", "4.2.7") + end + + it "should remove the currently active version if no explicit version is passed in" do + @provider.should_receive(:run_command).with(:command => "port deactivate zsh") + @provider.remove_package("zsh", nil) + end + end + + describe "upgrade_package" do + it "should run the port upgrade command with the correct version" do + @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6") + @provider.current_resource = @current_resource + + @provider.should_receive(:run_command).with(:command => "port upgrade zsh @4.2.7") + + @provider.upgrade_package("zsh", "4.2.7") + end + + it "should not run the port upgrade command if the version is already installed" do + @current_resource.should_receive(:version).at_least(:once).and_return("4.2.7") + @provider.current_resource = @current_resource + @provider.should_not_receive(:run_command) + + @provider.upgrade_package("zsh", "4.2.7") + end + + it "should call install_package if the package isn't currently installed" do + @current_resource.should_receive(:version).at_least(:once).and_return(nil) + @provider.current_resource = @current_resource + @provider.should_receive(:install_package).and_return(true) + + @provider.upgrade_package("zsh", "4.2.7") + end + end +end diff --git a/chef/spec/unit/provider/package/portage_spec.rb b/chef/spec/unit/provider/package/portage_spec.rb new file mode 100644 index 0000000000..f4aa4f10bb --- /dev/null +++ b/chef/spec/unit/provider/package/portage_spec.rb @@ -0,0 +1,158 @@ +# +# Author:: Caleb Tennis (<caleb.tennis@gmail.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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) + +describe Chef::Provider::Package::Portage, "load_current_resource" do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + + @new_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "dev-util/git", + :version => nil, + :package_name => "dev-util/git", + :updated => nil + ) + @current_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "dev-util/git", + :version => nil, + :package_name => nil, + :updated => nil + ) + + @provider = Chef::Provider::Package::Portage.new(@node, @new_resource) + Chef::Resource::Package.stub!(:new).and_return(@current_resource) + + ::File.stub!(:exists?).and_return(true) + end + + it "should create a current resource with the name of new_resource" do + Chef::Resource::Package.should_receive(:new).and_return(@current_resource) + @provider.load_current_resource + end + + it "should set the current resource package name to the new resource package name" do + @current_resource.should_receive(:package_name).with(@new_resource.package_name) + @provider.load_current_resource + end + + it "should return a current resource with a nil version if the package is not found" do + ::Dir.stub!(:entries).and_return("git-1.0.0") + @current_resource.should_receive(:version).with("1.0.0") + @provider.load_current_resource + end + + it "should return a current resource with the correct version if the package is found" do + ::Dir.stub!(:entries).and_return("notgit-1.0.0") + @current_resource.should_receive(:version).with(nil) + @provider.load_current_resource + end + +end + +describe Chef::Provider::Package::Portage, "candidate_version" do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "dev-util/git", + :version => nil, + :package_name => "dev-util/git", + :updated => nil + ) + @provider = Chef::Provider::Package::Portage.new(@node, @new_resource) + end + + it "should return the candidate_version variable if already setup" do + @provider.candidate_version = "1.0.0" + @provider.should_not_receive(:popen4) + @provider.candidate_version + end + + it "should lookup the candidate_version if the variable is not already set" do + @status = mock("Status", :exitstatus => 0) + @provider.stub!(:popen4).and_return(@status) + @provider.should_receive(:popen4) + @provider.candidate_version + end + + it "should throw and exception if the exitstatus is not 0" do + @status = mock("Status", :exitstatus => 1) + @provider.stub!(:popen4).and_return(@status) + lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package) + end + +end + +describe Chef::Provider::Package::Portage, "install_package" do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "dev-util/git", + :version => nil, + :package_name => "dev-util/git", + :updated => nil + ) + @provider = Chef::Provider::Package::Portage.new(@node, @new_resource) + end + + it "should install a normally versioned package using portage" do + @provider.should_receive(:run_command).with({ + :command => "emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0" + }) + @provider.install_package("dev-util/git", "1.0.0") + end + + it "should install a tilde versioned package using portage" do + @provider.should_receive(:run_command).with({ + :command => "emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0" + }) + @provider.install_package("dev-util/git", "~1.0.0") + end +end + +describe Chef::Provider::Package::Portage, "remove_package" do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Package", + :null_object => true, + :name => "dev-util/git", + :version => nil, + :package_name => "dev-util/git", + :updated => nil + ) + @provider = Chef::Provider::Package::Portage.new(@node, @new_resource) + end + + it "should un-emerge the package with no version specified" do + @provider.should_receive(:run_command).with({ + :command => "emerge --unmerge --color n --nospinner --quiet dev-util/git" + }) + @provider.remove_package("dev-util/git", nil) + end + + it "should un-emerge the package with a version specified" do + @provider.should_receive(:run_command).with({ + :command => "emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0" + }) + @provider.remove_package("dev-util/git", "1.0.0") + end +end + diff --git a/chef/spec/unit/provider/package/rpm_spec.rb b/chef/spec/unit/provider/package/rpm_spec.rb index dede03a664..8b999f8751 100644 --- a/chef/spec/unit/provider/package/rpm_spec.rb +++ b/chef/spec/unit/provider/package/rpm_spec.rb @@ -143,7 +143,7 @@ describe Chef::Provider::Package::Rpm, "remove" do it "should run rpm -e to remove the package" do @provider.should_receive(:run_command).with({ - :command => "rpm -e emacs" + :command => "rpm -e emacs-21.4-20.el5" }) @provider.remove_package("emacs", "21.4-20.el5") end diff --git a/chef/spec/unit/provider/package_spec.rb b/chef/spec/unit/provider/package_spec.rb index d9a89d607d..d60beb364f 100644 --- a/chef/spec/unit/provider/package_spec.rb +++ b/chef/spec/unit/provider/package_spec.rb @@ -111,6 +111,37 @@ describe Chef::Provider::Package, "action_install" do @provider.should_not_receive(:install_package) @provider.action_install end + + it "should call the candidate_version accessor if the package is not currently installed" do + @provider.should_receive(:candidate_version).and_return(true) + @provider.action_install + end + + it "should not call the candidate_version accessor if the package is already installed and no version is specified" do + @current_resource.stub!(:version).and_return("1.0") + @provider.should_not_receive(:candidate_version) + @provider.action_install + end + + it "should not call the candidate_version accessor if the package is already installed at the version specified" do + @current_resource.stub!(:version).and_return("1.0") + @new_resource.stub!(:version).and_return("1.0") + @provider.should_not_receive(:candidate_version) + @provider.action_install + end + + it "should not call the candidate_version accessor if the package is not installed new package's version is specified" do + @new_resource.stub!(:version).and_return("1.0") + @provider.should_not_receive(:candidate_version) + @provider.action_install + end + + it "should not call the candidate_version accessor if the package at the version specified is a different version than installed" do + @new_resource.stub!(:version).and_return("1.0") + @current_resource.stub!(:version).and_return("0.99") + @provider.should_not_receive(:candidate_version) + @provider.action_install + end it "should set the resource to updated if it installs the package" do @new_resource.should_recieve(:updated=).with(true) diff --git a/chef/spec/unit/provider/service/redhat_spec.rb b/chef/spec/unit/provider/service/redhat_spec.rb index 11a808be98..e69e19229c 100644 --- a/chef/spec/unit/provider/service/redhat_spec.rb +++ b/chef/spec/unit/provider/service/redhat_spec.rb @@ -98,8 +98,8 @@ describe Chef::Provider::Service::Redhat, "enable_service" do Chef::Resource::Service.stub!(:new).and_return(@current_resource) end - it "should call chkconfig --add 'service_name'" do - @provider.should_receive(:run_command).with({:command => "/sbin/chkconfig --add #{@new_resource.service_name}"}) + it "should call chkconfig to add 'service_name'" do + @provider.should_receive(:run_command).with({:command => "/sbin/chkconfig #{@new_resource.service_name} on"}) @provider.enable_service() end end @@ -117,8 +117,8 @@ describe Chef::Provider::Service::Redhat, "disable_service" do Chef::Resource::Service.stub!(:new).and_return(@current_resource) end - it "should call chkconfig --del 'service_name'" do - @provider.should_receive(:run_command).with({:command => "/sbin/chkconfig --del #{@new_resource.service_name}"}) + it "should call chkconfig to del 'service_name'" do + @provider.should_receive(:run_command).with({:command => "/sbin/chkconfig #{@new_resource.service_name} off"}) @provider.disable_service() end end diff --git a/chef/spec/unit/resource/group_spec.rb b/chef/spec/unit/resource/group_spec.rb index 585fcfc4d0..d110f5a45f 100644 --- a/chef/spec/unit/resource/group_spec.rb +++ b/chef/spec/unit/resource/group_spec.rb @@ -103,4 +103,24 @@ describe Chef::Resource::Group, "members" do it "should not allow a hash" do lambda { @resource.send(:members, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError) end -end
\ No newline at end of file +end + +describe Chef::Resource::Group, "append" do + before(:each) do + @resource = Chef::Resource::Group.new("admin") + end + + it "should default to false" do + @resource.append.should eql(false) + end + + it "should allow a boolean" do + @resource.append true + @resource.append.should eql(true) + end + + it "should not allow a hash" do + lambda { @resource.send(:gid, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError) + end + +end diff --git a/chef/spec/unit/resource/macports_package_spec.rb b/chef/spec/unit/resource/macports_package_spec.rb new file mode 100644 index 0000000000..c2dabb089f --- /dev/null +++ b/chef/spec/unit/resource/macports_package_spec.rb @@ -0,0 +1,37 @@ +# +# Author:: David Balatero (<dbalatero@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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::MacportsPackage, "initialize" do + before(:each) do + @resource = Chef::Resource::MacportsPackage.new("foo") + end + + it "should return a Chef::Resource::MacportsPackage" do + @resource.should be_a_kind_of(Chef::Resource::MacportsPackage) + end + + it "should set the resource_name to :macports_package" do + @resource.resource_name.should eql(:macports_package) + end + + it "should set the provider to Chef::Provider::Package::Macports" do + @resource.provider.should eql(Chef::Provider::Package::Macports) + end +end diff --git a/features/cookbooks/metadata.feature b/features/cookbooks/metadata.feature new file mode 100644 index 0000000000..7e1c26c9f0 --- /dev/null +++ b/features/cookbooks/metadata.feature @@ -0,0 +1,20 @@ +Feature: Cookbook Metadata + In order to understand cookbooks without evaluating them + As an Administrator + I want to automatically generate metadata about cookbooks + + Scenario: Generate metadata for all cookbooks + Given a local cookbook repository + When I run the rake task to generate cookbook metadata + Then the run should exit '0' + And 'stdout' should have 'Generating metadata for metadata' + And 'stdout' should have 'Generating metadata for execute_commands' + And a file named 'cookbooks_dir/cookbooks/metadata/metadata.json' should exist + + Scenario: Generate metadata for a specific cookbook + Given a local cookbook repository + When I run the rake task to generate cookbook metadata for 'metadata' + Then the run should exit '0' + And 'stdout' should have 'Generating metadata for metadata' + And a file named 'cookbooks_dir/cookbooks/metadata/metadata.json' should exist + diff --git a/features/data/Rakefile b/features/data/Rakefile index d0a3d16913..27e98cfd98 100644 --- a/features/data/Rakefile +++ b/features/data/Rakefile @@ -18,164 +18,18 @@ # limitations under the License. # -require File.join(File.dirname(__FILE__), 'config', 'rake') +require 'rubygems' +require 'json' -require 'tempfile' +# Make sure you have loaded constants first +require File.join(File.dirname(__FILE__), 'config', 'rake') +# And choosen a VCS if File.directory?(File.join(TOPDIR, ".svn")) $vcs = :svn elsif File.directory?(File.join(TOPDIR, ".git")) $vcs = :git end -desc "Update your repository from source control" -task :update do - puts "** Updating your repository" - - case $vcs - when :svn - sh %{svn up} - when :git - pull = false - pull = true if File.join(TOPDIR, ".git", "remotes", "origin") - IO.foreach(File.join(TOPDIR, ".git", "config")) do |line| - pull = true if line =~ /\[remote "origin"\]/ - end - if pull - sh %{git pull} - else - puts "* Skipping git pull, no origin specified" - end - else - puts "* No SCM configured, skipping update" - end -end - -desc "Test your cookbooks for syntax errors" -task :test do - puts "** Testing your cookbooks for syntax errors" +require 'chef/tasks/chef_repo.rake' - recipes = ["*cookbooks"].map { |folder| - Dir[File.join(TOPDIR, folder, "**", "*.rb")] - }.flatten - - recipes.each do |recipe| - print "Testing recipe #{recipe}: " - sh %{ruby -c #{recipe}} do |ok, res| - if ! ok - raise "Syntax error in #{recipe}" - end - end - end -end - -desc "Install the latest copy of the repository on this Chef Server" -task :install => [ :update, :test ] do - puts "** Installing your cookbooks" - directories = [ - COOKBOOK_PATH, - SITE_COOKBOOK_PATH, - CHEF_CONFIG_PATH - ] - puts "* Creating Directories" - directories.each do |dir| - sh "sudo mkdir -p #{dir}" - sh "sudo chown root #{dir}" - end - puts "* Installing new Cookbooks" - sh "sudo rsync -rlP --delete --exclude '.svn' cookbooks/ #{COOKBOOK_PATH}" - puts "* Installing new Site Cookbooks" - sh "sudo rsync -rlP --delete --exclude '.svn' cookbooks/ #{COOKBOOK_PATH}" - puts "* Installing new Chef Server Config" - sh "sudo cp config/server.rb #{CHEF_SERVER_CONFIG}" - puts "* Installing new Chef Client Config" - sh "sudo cp config/client.rb #{CHEF_CLIENT_CONFIG}" -end - -desc "By default, run rake test" -task :default => [ :test ] - -desc "Create a new cookbook (with COOKBOOK=name)" -task :new_cookbook do - create_cookbook(File.join(TOPDIR, "cookbooks")) -end - -def create_cookbook(dir) - raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"] - puts "** Creating cookbook #{ENV["COOKBOOK"]}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}" - sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}" - unless File.exists?(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb")) - open(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb"), "w") do |file| - file.puts <<-EOH -# -# Cookbook Name:: #{ENV["COOKBOOK"]} -# Recipe:: default -# -# Copyright #{Time.now.year}, #{COMPANY_NAME} -# -EOH - case NEW_COOKBOOK_LICENSE - when :apachev2 - file.puts <<-EOH -# 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. -# -EOH - when :none - file.puts <<-EOH -# All rights reserved - Do Not Redistribute -# -EOH - end - end - end -end - -desc "Create a new self-signed SSL certificate for FQDN=foo.example.com" -task :ssl_cert do - $expect_verbose = true - fqdn = ENV["FQDN"] - fqdn =~ /^(.+?)\.(.+)$/ - hostname = $1 - domain = $2 - raise "Must provide FQDN!" unless fqdn && hostname && domain - puts "** Creating self signed SSL Certificate for #{fqdn}" - sh("(cd #{CADIR} && openssl genrsa 2048 > #{fqdn}.key)") - sh("(cd #{CADIR} && chmod 644 #{fqdn}.key)") - puts "* Generating Self Signed Certificate Request" - tf = Tempfile.new("#{fqdn}.ssl-conf") - ssl_config = <<EOH -[ req ] -distinguished_name = req_distinguished_name -prompt = no - -[ req_distinguished_name ] -C = #{SSL_COUNTRY_NAME} -ST = #{SSL_STATE_NAME} -L = #{SSL_LOCALITY_NAME} -O = #{COMPANY_NAME} -OU = #{SSL_ORGANIZATIONAL_UNIT_NAME} -CN = #{fqdn} -emailAddress = #{SSL_EMAIL_ADDRESS} -EOH - tf.puts(ssl_config) - tf.close - sh("(cd #{CADIR} && openssl req -config '#{tf.path}' -new -x509 -nodes -sha1 -days 3650 -key #{fqdn}.key > #{fqdn}.crt)") - sh("(cd #{CADIR} && openssl x509 -noout -fingerprint -text < #{fqdn}.crt > #{fqdn}.info)") - sh("(cd #{CADIR} && cat #{fqdn}.crt #{fqdn}.key > #{fqdn}.pem)") - sh("(cd #{CADIR} && chmod 644 #{fqdn}.pem)") -end diff --git a/features/data/cookbooks/delayed_notifications/recipes/notify_different_resources_for_different_actions.rb b/features/data/cookbooks/delayed_notifications/recipes/notify_different_resources_for_different_actions.rb new file mode 100644 index 0000000000..3180bbe4c2 --- /dev/null +++ b/features/data/cookbooks/delayed_notifications/recipes/notify_different_resources_for_different_actions.rb @@ -0,0 +1,31 @@ +# +# Cookbook Name:: delayed_notifications +# Recipe:: notify_different_resources_for_different_actions +# +# Copyright 2009, Opscode +# +# 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. +# + +file "#{node[:tmpdir]}/notified_file_2.txt" do + action :nothing +end + +file "#{node[:tmpdir]}/notified_file_3.txt" do + action :nothing +end + +execute "echo foo" do + notifies([{resources("file[#{node[:tmpdir]}/notified_file_2.txt]") => [:create, :delayed]}, + {resources("file[#{node[:tmpdir]}/notified_file_3.txt]") => [:create, :delayed]}]) +end diff --git a/features/data/cookbooks/execute_commands/metadata.rb b/features/data/cookbooks/execute_commands/metadata.rb new file mode 100644 index 0000000000..4b0c820de6 --- /dev/null +++ b/features/data/cookbooks/execute_commands/metadata.rb @@ -0,0 +1,14 @@ + +version "1.0" +maintainer "Bobo T. Clown" +maintainer_email "bobo@example.com" +long_description "I have a long arm!" +supports :ubuntu, ">> 8.04" +depends "bobo", "= 1.0" +depends "bobotclown", "= 1.1" +recommends "snark", "<< 3.0" +suggests "kindness", ">> 2.0", "<< 4.0" +conflicts "hatred" +provides "foo(:bar, :baz)" +replaces "snarkitron" + diff --git a/features/data/cookbooks/metadata/metadata.rb b/features/data/cookbooks/metadata/metadata.rb new file mode 100644 index 0000000000..4b0c820de6 --- /dev/null +++ b/features/data/cookbooks/metadata/metadata.rb @@ -0,0 +1,14 @@ + +version "1.0" +maintainer "Bobo T. Clown" +maintainer_email "bobo@example.com" +long_description "I have a long arm!" +supports :ubuntu, ">> 8.04" +depends "bobo", "= 1.0" +depends "bobotclown", "= 1.1" +recommends "snark", "<< 3.0" +suggests "kindness", ">> 2.0", "<< 4.0" +conflicts "hatred" +provides "foo(:bar, :baz)" +replaces "snarkitron" + diff --git a/features/data/cookbooks/metadata/recipes/default.rb b/features/data/cookbooks/metadata/recipes/default.rb new file mode 100644 index 0000000000..541b504f0b --- /dev/null +++ b/features/data/cookbooks/metadata/recipes/default.rb @@ -0,0 +1,18 @@ +# +# Cookbook Name:: metadata +# Recipe:: default +# +# Copyright 2009, Opscode +# +# 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. +# diff --git a/features/data/cookbooks/packages/recipes/default.rb b/features/data/cookbooks/packages/recipes/default.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/features/data/cookbooks/packages/recipes/default.rb diff --git a/features/data/cookbooks/packages/recipes/macports_install_bad_package.rb b/features/data/cookbooks/packages/recipes/macports_install_bad_package.rb new file mode 100644 index 0000000000..40ac646761 --- /dev/null +++ b/features/data/cookbooks/packages/recipes/macports_install_bad_package.rb @@ -0,0 +1,3 @@ +package "fdsafdsa" do + action :install +end diff --git a/features/data/cookbooks/packages/recipes/macports_install_yydecode.rb b/features/data/cookbooks/packages/recipes/macports_install_yydecode.rb new file mode 100644 index 0000000000..7c2475db37 --- /dev/null +++ b/features/data/cookbooks/packages/recipes/macports_install_yydecode.rb @@ -0,0 +1,3 @@ +package "yydecode" do + action :install +end diff --git a/features/data/cookbooks/packages/recipes/macports_purge_yydecode.rb b/features/data/cookbooks/packages/recipes/macports_purge_yydecode.rb new file mode 100644 index 0000000000..e7ad245dfb --- /dev/null +++ b/features/data/cookbooks/packages/recipes/macports_purge_yydecode.rb @@ -0,0 +1,3 @@ +package "yydecode" do + action :purge +end diff --git a/features/data/cookbooks/packages/recipes/macports_remove_yydecode.rb b/features/data/cookbooks/packages/recipes/macports_remove_yydecode.rb new file mode 100644 index 0000000000..da1acca7ec --- /dev/null +++ b/features/data/cookbooks/packages/recipes/macports_remove_yydecode.rb @@ -0,0 +1,3 @@ +package "yydecode" do + action :remove +end diff --git a/features/data/cookbooks/packages/recipes/macports_upgrade_yydecode.rb b/features/data/cookbooks/packages/recipes/macports_upgrade_yydecode.rb new file mode 100644 index 0000000000..3d008abdec --- /dev/null +++ b/features/data/cookbooks/packages/recipes/macports_upgrade_yydecode.rb @@ -0,0 +1,7 @@ +package "yydecode" do + action :remove +end + +package "yydecode" do + action :upgrade +end diff --git a/features/language/delayed_notifications.feature b/features/language/delayed_notifications.feature index de86f4a240..bc874f0f5e 100644 --- a/features/language/delayed_notifications.feature +++ b/features/language/delayed_notifications.feature @@ -17,5 +17,11 @@ Feature: Delayed Notifications Then the run should exit '0' And a file named 'notified_file.txt' should contain 'bob dylan' only '1' time - + Scenario: Notify different resources for different actions + Given a validated node + And it includes the recipe 'delayed_notifications::notify_different_resources_for_different_actions' + When I run the chef-client + Then the run should exit '0' + And a file named 'notified_file_2.txt' should exist + And a file named 'notified_file_3.txt' should exist diff --git a/features/provider/package/macports.feature b/features/provider/package/macports.feature new file mode 100644 index 0000000000..111fb00e0a --- /dev/null +++ b/features/provider/package/macports.feature @@ -0,0 +1,18 @@ +Feature: Macports integration + In order to easily manage my OS X machines + As a Developer + I want to manage packages installed on OS X machines + + Scenario Outline: OS X package management + Given that I have the MacPorts package system installed + When I run chef-solo with the '<recipe>' recipe + Then the run should exit '<exitcode>' + And there <should> be a binary on the path called '<binary>' + + Examples: + | recipe | binary | should | exitcode | + | packages::macports_install_yydecode | yydecode | should | 0 | + | packages::macports_remove_yydecode | yydecode | should not | 0 | + | packages::macports_upgrade_yydecode | yydecode | should | 0 | + | packages::macports_purge_yydecode | yydecode | should not | 0 | + | packages::macports_install_bad_package | fdsafdsa | should not | 1 | diff --git a/features/steps/cookbooks.rb b/features/steps/cookbooks.rb new file mode 100644 index 0000000000..53f42100ab --- /dev/null +++ b/features/steps/cookbooks.rb @@ -0,0 +1,53 @@ +# +# 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. +# + +Given /^a local cookbook repository$/ do + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir')) + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir', 'cookbooks')) + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir', 'config')) + system("cp #{@datadir}/Rakefile #{@tmpdir}/cookbooks_dir") + system("cp -r #{@datadir}/config/* #{@tmpdir}/cookbooks_dir/config") + system("cp -r #{@datadir}/cookbooks/* #{@tmpdir}/cookbooks_dir/cookbooks") + @cleanup_dirs = "#{@tmpdir}/cookbooks_dir" +end + +Given /^a local cookbook named '(.+)'$/ do |cb| + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir')) + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir', 'cookbooks')) + Dir.mkdir(File.join(@tmpdir, 'cookbooks_dir', 'config')) + system("cp #{@datadir}/Rakefile #{@tmpdir}/cookbooks_dir") + system("cp -r #{@datadir}/config/* #{@tmpdir}/cookbooks_dir/config") + system("cp -r #{@datadir}/cookbooks/#{cb} #{@tmpdir}/cookbooks_dir/cookbooks") + @cleanup_dirs = "#{@tmpdir}/cookbooks_dir" +end + +When /^I run the rake task to generate cookbook metadata for '(.+)'$/ do |cb| + @cookbook = cb + When('I run the rake task to generate cookbook metadata') +end + +When /^I run the rake task to generate cookbook metadata$/ do + to_run = "rake metadata" + to_run += " COOKBOOK=#{@cookbook}" if @cookbook + Dir.chdir(File.join(@tmpdir, 'cookbooks_dir')) do + @status = Chef::Mixin::Command.popen4(to_run) do |p, i, o, e| + @stdout = o.gets(nil) + @stderr = o.gets(nil) + end + end +end diff --git a/features/steps/nodes.rb b/features/steps/node_steps.rb index 36cfb76914..033cea4a12 100644 --- a/features/steps/nodes.rb +++ b/features/steps/node_steps.rb @@ -20,23 +20,23 @@ # Given ### Given /^a validated node$/ do - @client.validation_token = Chef::Config[:validation_token] = 'ceelo' - @client.build_node - @client.node.recipes = "integration_setup" - @client.register - @client.authenticate + client.validation_token = Chef::Config[:validation_token] = 'ceelo' + client.register + client.authenticate + client.build_node + client.node.recipes = "integration_setup" end Given /^it includes the recipe '(.+)'$/ do |recipe| - @recipe = recipe - @client.node.recipes << recipe - @client.save_node + self.recipe = recipe + client.node.recipes << recipe + client.save_node end ### # When ### When /^the node is converged$/ do - @client.run + client.run end diff --git a/features/steps/packages.rb b/features/steps/packages.rb new file mode 100644 index 0000000000..178a171c0a --- /dev/null +++ b/features/steps/packages.rb @@ -0,0 +1,20 @@ +Given /^that I have the (.+) package system installed$/ do |package_system| + unless package_system_available?(package_system) + pending "This Cucumber feature will not execute, as it is missing the #{package_system} packaging system." + end +end + +Then /^there should be a binary on the path called '(.+)'$/ do |binary_name| + binary_name.strip! + result = `which #{binary_name}` + result.should_not =~ /not found/ +end + +Then /^there should not be a binary on the path called '(.+)'$/ do |binary_name| + binary_name.strip! + result = `which #{binary_name}`.strip + + unless result.empty? + result.should =~ /not found/ + end +end diff --git a/features/steps/run_client.rb b/features/steps/run_client.rb index 62249daa7d..62db06523a 100644 --- a/features/steps/run_client.rb +++ b/features/steps/run_client.rb @@ -98,8 +98,10 @@ end # Then ### Then /^the run should exit '(.+)'$/ do |exit_code| - puts @status.inspect - puts @status.exitstatus + if ENV['LOG_LEVEL'] == 'debug' + puts @status.inspect + puts @status.exitstatus + end begin @status.exitstatus.should eql(exit_code.to_i) rescue diff --git a/features/steps/run_solo.rb b/features/steps/run_solo.rb new file mode 100644 index 0000000000..f31749f524 --- /dev/null +++ b/features/steps/run_solo.rb @@ -0,0 +1,36 @@ +# This is kind of a crazy-ass setup, but it works. +When /^I run chef-solo with the '(.+)' recipe$/ do |recipe_name| + # Set up the JSON file with the recipe we want to run. + dna_file = "/tmp/chef-solo-features-dna.json" + File.open(dna_file, "w") do |fp| + fp.write("{ \"recipes\": [\"#{recipe_name}\"] }") + end + + # Set up the cache dir. + cache_dir = "/tmp/chef-solo-cache-features" + result = `rm -fr #{cache_dir}; mkdir #{cache_dir}; chmod 777 #{cache_dir}` + + # Cookbook dir + cookbook_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'data', 'cookbooks')) + result = `ln -sf #{cookbook_dir} #{cache_dir}/cookbooks` + + # Config file + config_file = "/tmp/chef-solo-config-features.rb" + File.open(config_file, "w") do |fp| + fp.write("file_cache_path \"#{cache_dir}\"\n") + end + + binary_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'chef', 'bin', 'chef-solo')) + command = "#{binary_path} -c #{config_file} -j #{dna_file}" + + # Run it + puts "Running solo: #{command}" if ENV['LOG_LEVEL'] == 'debug' + + status = Chef::Mixin::Command.popen4(command) do |p, i, o, e| + @stdout = o.gets(nil) + @stderr = o.gets(nil) + end + @status = status + + print_output if ENV['LOG_LEVEL'] == 'debug' +end diff --git a/features/support/env.rb b/features/support/env.rb index 978ce818bf..3b4a2caaf9 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -30,11 +30,12 @@ Chef::Config.from_file(File.join(File.dirname(__FILE__), '..', 'data', 'config', Ohai::Config[:log_level] = :error class ChefWorld - attr_accessor :client, :tmpdir + attr_accessor :client, :tmpdir, :recipe def initialize @client = Chef::Client.new @tmpdir = File.join(Dir.tmpdir, "chef_integration") + @datadir = File.join(File.dirname(__FILE__), "..", "data") @cleanup_files = Array.new @cleanup_dirs = Array.new @recipe = nil diff --git a/features/support/packages.rb b/features/support/packages.rb new file mode 100644 index 0000000000..90272e543c --- /dev/null +++ b/features/support/packages.rb @@ -0,0 +1,12 @@ +# Provides a method to quickly lookup whether we have +# a given packaging system installed. +def package_system_available?(name) + case name + when 'MacPorts' + uname = `uname` + port = `which port` + (uname =~ /Darwin/ and !port.match(/not found/)) + else + false + end +end |