diff options
Diffstat (limited to 'chef')
214 files changed, 13968 insertions, 0 deletions
diff --git a/chef/History.txt b/chef/History.txt new file mode 100644 index 0000000000..02d903461e --- /dev/null +++ b/chef/History.txt @@ -0,0 +1,17 @@ +=== 1.0.0 / 2008-03-05 + +* 1 major enhancement + * Birthday! + +Sun Apr 27 20:30:43 PDT 2008 + * Compiled first full recipes, and actually passed them to a provider. :) + +Mon Jun 9 01:57:58 PDT 2008 + * Compiled first full recipes over a network, with registration and authorization. + +Thu Jul 10 19:59:46 PDT 2008 + * Search support functional! + +Sat Aug 16 17:46:34 PDT 2008 + * Compiled a template! + * Remote files and directories
\ No newline at end of file diff --git a/chef/LICENSE b/chef/LICENSE new file mode 100644 index 0000000000..11069edd79 --- /dev/null +++ b/chef/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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/chef/Manifest.txt b/chef/Manifest.txt new file mode 100644 index 0000000000..79d961a738 --- /dev/null +++ b/chef/Manifest.txt @@ -0,0 +1,115 @@ +History.txt +LICENSE +Manifest.txt +README.txt +Rakefile +bin/chef-client +bin/chef-indexer +bin/chef-server +bin/chef-solo +config/chef-server.rb +examples/config.rb +examples/config/chef-solo.rb +examples/config/cookbooks/fakefile/attributes/first.rb +examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt +examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt +examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt +examples/config/cookbooks/fakefile/files/default/the_park.txt +examples/config/cookbooks/fakefile/recipes/default.rb +examples/config/cookbooks/fakefile/templates/default/monkey.erb +examples/config/cookbooks/tempfile/attributes/second.rb +examples/config/cookbooks/tempfile/recipes/default.rb +examples/config/nodes/adam.rb +examples/config/nodes/default.rb +examples/config/nodes/latte.rb +examples/log/stompserver.pid +examples/mrepo/Rakefile +examples/node.rb +examples/node.yml +examples/sample_definition.rb +examples/sample_recipe.rb +examples/search_index/_19.cfs +examples/search_index/_1k.cfs +examples/search_index/_1k_0.del +examples/search_index/_1l.cfs +examples/search_index/_c.cfs +examples/search_index/_c_0.del +examples/search_index/_n.cfs +examples/search_index/_y.cfs +examples/search_index/segments +examples/search_index/segments_1v +examples/search_syntax.rb +examples/user_index.pl +examples/user_index.rb +lib/chef.rb +lib/chef/client.rb +lib/chef/compile.rb +lib/chef/config.rb +lib/chef/cookbook.rb +lib/chef/cookbook_loader.rb +lib/chef/couchdb.rb +lib/chef/exceptions.rb +lib/chef/file_store.rb +lib/chef/log.rb +lib/chef/log/formatter.rb +lib/chef/mixin/check_helper.rb +lib/chef/mixin/checksum.rb +lib/chef/mixin/command.rb +lib/chef/mixin/from_file.rb +lib/chef/mixin/params_validate.rb +lib/chef/mixin/template.rb +lib/chef/node.rb +lib/chef/openid_registration.rb +lib/chef/platform.rb +lib/chef/provider.rb +lib/chef/provider/apt.rb +lib/chef/provider/directory.rb +lib/chef/provider/execute.rb +lib/chef/provider/file.rb +lib/chef/provider/link.rb +lib/chef/provider/package.rb +lib/chef/provider/remote_directory.rb +lib/chef/provider/remote_file.rb +lib/chef/provider/script.rb +lib/chef/provider/sysctl.rb +lib/chef/provider/template.rb +lib/chef/queue.rb +lib/chef/recipe.rb +lib/chef/resource.rb +lib/chef/resource/bash.rb +lib/chef/resource/csh.rb +lib/chef/resource/directory.rb +lib/chef/resource/execute.rb +lib/chef/resource/file.rb +lib/chef/resource/link.rb +lib/chef/resource/package.rb +lib/chef/resource/perl.rb +lib/chef/resource/python.rb +lib/chef/resource/remote_directory.rb +lib/chef/resource/remote_file.rb +lib/chef/resource/ruby.rb +lib/chef/resource/script.rb +lib/chef/resource/sysctl.rb +lib/chef/resource/template.rb +lib/chef/resource_collection.rb +lib/chef/resource_definition.rb +lib/chef/rest.rb +lib/chef/runner.rb +lib/chef/search.rb +lib/chef/search_index.rb +lib/chef_server/controllers/application.rb +lib/chef_server/controllers/cookbook_files.rb +lib/chef_server/controllers/cookbook_templates.rb +lib/chef_server/controllers/cookbooks.rb +lib/chef_server/controllers/exceptions.rb +lib/chef_server/controllers/nodes.rb +lib/chef_server/controllers/openid_consumer.rb +lib/chef_server/controllers/openid_register.rb +lib/chef_server/controllers/openid_server.rb +lib/chef_server/controllers/search.rb +lib/chef_server/controllers/search_entries.rb +lib/chef_server/helpers/global_helpers.rb +lib/chef_server/helpers/nodes_helper.rb +lib/chef_server/helpers/openid_server_helpers.rb +lib/chef_server/init.rb + diff --git a/chef/NOTICE b/chef/NOTICE new file mode 100644 index 0000000000..e2385f99a2 --- /dev/null +++ b/chef/NOTICE @@ -0,0 +1,11 @@ +Chef NOTICE +=========== + +Developed at HJK Solutions (http://www.hjksolutions.com). + +Contributors and Copyright holders: + + * Copyright 2008, Adam Jacob <adam@hjksolutions.com> + * Copyright 2008, Arjuna Christensen <aj@hjksolutions.com> + * Copyright 2008, Ezra Zygmuntowicz <ezra@engineyard.com> +
\ No newline at end of file diff --git a/chef/README.txt b/chef/README.txt new file mode 100644 index 0000000000..70758161ae --- /dev/null +++ b/chef/README.txt @@ -0,0 +1,78 @@ += chef + +* http://oss.hjksolutions.com/chef + +== DESCRIPTION: + +Chef is a configuration management tool inspired by Puppet. + +I'm in ur netwerk, cookin up yer servers. :) + +== FEATURES/PROBLEMS: + + +== SYNOPSIS: + + +== REQUIREMENTS: + +RubyGems: + +* stomp +* stompserver +* ultraviolet +* +* facter +* ferret +* merb-core +* haml +* ruby-openid +* json + +External Servers: + +* stompserver (for easy stomp mq testing) +* CouchDB + +== INSTALL: + +Install all of the above. To fire up a develpment environment, do the following: + + * Start CouchDB with 'couchdb' + * Start stompserver with 'stompserver' + * Start chef-indexer with: + + ./bin/chef-indexer -l debug -c ./config/chef-server.rb + + * Start chef-server on port 4000 with: + + ./bin/chef-server + + * Start chef-server on port 4001 with: + + ./bin/chef-server -p 4001 + + * Test run chef with: + + sudo ./bin/chef-client -l debug -c ./examples/config/chef-solo.rb + +== LICENSE: + +Chef - A configuration management system + +Author:: Adam Jacob (<adam@hjksolutions.com>) +Copyright:: Copyright (c) 2008 HJK Solutions, LLC +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. + diff --git a/chef/Rakefile b/chef/Rakefile new file mode 100644 index 0000000000..13738158e4 --- /dev/null +++ b/chef/Rakefile @@ -0,0 +1,48 @@ +# -*- ruby -*- +require 'rubygems' +require 'rake/gempackagetask' +require './lib/chef.rb' +require './tasks/rspec.rb' + +GEM = "chef" +VERSION = "0.0.1" +AUTHOR = "Adam Jacob" +EMAIL = "adam@hjksolutions.com" +HOMEPAGE = "http://hjksolutions.com" +SUMMARY = "A configuration management system." + +spec = Gem::Specification.new do |s| + s.name = GEM + s.version = VERSION + s.platform = Gem::Platform::RUBY + s.has_rdoc = true + s.extra_rdoc_files = ["README.txt", "LICENSE", 'NOTICE'] + s.summary = SUMMARY + s.description = s.summary + s.author = AUTHOR + s.email = EMAIL + s.homepage = HOMEPAGE + + # Uncomment this to add a dependency + s.add_dependency "facter" + s.add_dependency "ruby-openid" + s.add_dependency "json" + s.add_dependency "erubis" + s.add_dependency "extlib" + + s.bindir = "bin" + s.executables = %w( chef-client chef-solo ) + + s.require_path = 'lib' + s.files = %w(LICENSE README.txt Rakefile) + Dir.glob("{lib,specs,config,examples}/**/*") +end + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.gem_spec = spec +end + +task :install => [:package] do + sh %{sudo gem install pkg/#{GEM}-#{VERSION}} +end + +# vim: syntax=Ruby diff --git a/chef/bin/chef-client b/chef/bin/chef-client new file mode 100755 index 0000000000..a80020b0aa --- /dev/null +++ b/chef/bin/chef-client @@ -0,0 +1,65 @@ +#!/usr/bin/env ruby +# +# ./chef-client - Build a meal with chef +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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. + + +$: << File.join(File.dirname(__FILE__), "..", "lib") + +require 'optparse' +require 'chef' +require 'rubygems' +require 'facter' + +config = { + :config_file => "/etc/chef/client.rb", + :log_level => :info, + :noop => false +} +opts = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)" + opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c| + config[:config_file] = c + end + opts.on("-n", "--noop", "Print what you would do, but don't actually do it.") do + config[:noop] = true + end + opts.on_tail("-l LEVEL", "--loglevel LEVEL", "Set the log level (debug, info, warn, error, fatal)") do |l| + config[:log_level] = l.to_sym + end + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end +end +opts.parse!(ARGV) + +unless File.exists?(config[:config_file]) && File.readable?(config[:config_file]) + puts "I cannot find or read the config file: #{config[:config_file]}" + puts opts + exit +end + +# Load our config file +Chef::Config.from_file(config[:config_file]) +if config[:log_level] + Chef::Log.level(config[:log_level].to_sym) +end + +c = Chef::Client.new +c.run diff --git a/chef/bin/chef-solo b/chef/bin/chef-solo new file mode 100755 index 0000000000..ecc06afb79 --- /dev/null +++ b/chef/bin/chef-solo @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby +# +# ./chef-solo - Build a meal with chef, sans-server! +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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. + +$: << File.join(File.dirname(__FILE__), "..", "lib") + +require 'optparse' +require 'chef' +require 'rubygems' +require 'facter' + +config = { + :config_file => "/etc/chef/config.rb", + :log_level => :info, + :noop => false +} +opts = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)" + opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c| + config[:config_file] = c + end + opts.on("-n", "--noop", "Print what you would do, but don't actually do it.") do + config[:noop] = true + end + opts.on_tail("-l LEVEL", "--loglevel LEVEL", "Set the log level (debug, info, warn, error, fatal)") do |l| + config[:log_level] = l.to_sym + end + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end +end +opts.parse!(ARGV) + +unless File.exists?(config[:config_file]) && File.readable?(config[:config_file]) + puts "I cannot find or read the config file: #{config[:config_file]}" + puts opts + exit +end + +# Load our config file +Chef::Config.from_file(config[:config_file]) +if config[:log_level] + Chef::Log.level(config[:log_level].to_sym) +end + +# Find out our own hostname. +node_name = Facter["fqdn"].value +node_name ||= Facter["hostname"].value + +# Grab a Chef::Compile object +compile = Chef::Compile.new() + +# Load our Node, and then add all the Facter facts as attributes +compile.load_node(node_name) +Facter.each do |field, value| + compile.node[field.to_sym] = value +end +compile.load_definitions +compile.load_recipes +cr = Chef::Runner.new(compile.node, compile.collection) +cr.converge diff --git a/chef/config/server.rb b/chef/config/server.rb new file mode 100644 index 0000000000..74c12df403 --- /dev/null +++ b/chef/config/server.rb @@ -0,0 +1,17 @@ +# +# Example Chef Server Config + +log_level :debug + +cookbook_path File.join(File.dirname(__FILE__), "..", "examples", "config", "cookbooks") +node_path File.join(File.dirname(__FILE__), "..", "examples", "config", "nodes") +file_store_path File.join(File.dirname(__FILE__), "..", "examples", "store") +openid_store_path File.join(File.dirname(__FILE__), "..", "examples", "openid-db") +openid_cstore_path File.join(File.dirname(__FILE__), "..", "examples", "openid-cstore") +merb_log_path File.join(File.dirname(__FILE__), "..", "examples", "logs") +search_index_path File.join(File.dirname(__FILE__), "..", "examples", "search_index") + + +# openid_providers [ "localhost:4001", "openid.hjksolutions.com" ] + +Chef::Log::Formatter.show_time = false diff --git a/chef/docs/design/HighLevel.graffle b/chef/docs/design/HighLevel.graffle Binary files differnew file mode 100644 index 0000000000..e3a5aec187 --- /dev/null +++ b/chef/docs/design/HighLevel.graffle diff --git a/chef/docs/recipe.rb b/chef/docs/recipe.rb new file mode 100644 index 0000000000..b132ff42ea --- /dev/null +++ b/chef/docs/recipe.rb @@ -0,0 +1,97 @@ +#namespace :openldap do +# recipe :auth do |n| + +include_recipe 'openldap::client' +include_recipe 'openssh' +include_recipe 'nscd' + +file "/etc/nsswitch.conf" { + insure = "present" + owner = "root" + group = "root" + mode = 0644 +} + +file "/etc/ldap.conf" { + insure = "present" + owner = "root" + group = "root" + mode = 0644 + requires = resources(:file => "/etc/nsswitch.conf") +} + +file "/etc/ldap.conf" do + insure = "present" + owner = "root" + group = "root" + mode = 0644 + requires = resources() +end + +remote_file "nsswitch.conf" { + path "/etc/nsswitch.conf" + source "nsswitch.conf" + module "openldap" + mode 0644 + owner "root" + group "root" + requires :file => "nsswitch-ldap-file", :exec => [ "one", "two" ] + notifies :service => "nscd", :exec => [ "nscd-clear-passwd", "nscd-clear-group" ] + provider 'File::Rsync' +} + +remote_file "nsswitch.conf" { + path = "/etc/nsswitch.conf" + source = "nsswitch.conf" + module = "openldap" + mode = 0644 + owner = "root" + group = "root" + requires = resources :file => "nsswitch-ldap-file", + :exec => [ "one", "two" ] + notifies = resources :service => "nscd", + :exec => [ "nscd-clear-passwd", "nscd-clear-group" ] + provider = 'File::Rsync' +} + +service "nscd" do |s| + s.insure = "running" +end + +case node[:lsbdistid] +when "CentOS" + template_file "ldap.conf" do |f| + f.path = "/etc/ldap.conf" + f.content = "openldap/ldap.conf.erb" + f.mode = 644 + f.owner = "root" + f.group = "root" + f.alias = "nsswitch-ldap-file" + f.notify = resource(:exec => [ "nscd-clear-passwd", "nscd-clear-group"] ) + f.require = resource(:package => "nss_ldap") + end + package "nss_ldap" do |p| + p.insure = "latest" + end +end + +# end +#end + +definition "rails_app" do |n, args| + check_arguments(args, { + :port_number => 8000, + :mongrel_servers => 2, + :rails_environment => "production", + :rails_path => nil, + :rails_user => nil, + :rails_group => nil, + :canonical_hostname => false, + :template => 'rails/rails.conf.erb' + } + ) + file "sites-#{@name}" do |f| + + end +end + diff --git a/chef/examples/config.rb b/chef/examples/config.rb new file mode 100644 index 0000000000..171554c416 --- /dev/null +++ b/chef/examples/config.rb @@ -0,0 +1,20 @@ +# +# Example config +# + +nodes_from([ + Chef::Node::YAML => { + :search_path => [ "/etc/chef/nodes" ] + }, + Chef::Node::PuppetExternalNode => { + :command => "" + }, + :rest => { + :search_url => "http://localhost:3000/nodes/#{node_name}" + }, + :iclassify => { + :search_url => "http://localhost:3000/nodes/#{node_name}" + } +]) + + diff --git a/chef/examples/config/chef-solo.rb b/chef/examples/config/chef-solo.rb new file mode 100644 index 0000000000..2a85253b37 --- /dev/null +++ b/chef/examples/config/chef-solo.rb @@ -0,0 +1,11 @@ +# +# Example Chef Solo Config + +cookbook_path File.join(File.dirname(__FILE__), "cookbooks") +node_path File.join(File.dirname(__FILE__), "nodes") +search_index_path File.join(File.dirname(__FILE__), "..", "search_index") +log_level :info +file_store_path "/tmp/chef" +file_cache_path "/tmp/chef/cache" + +Chef::Log::Formatter.show_time = false diff --git a/chef/examples/config/cookbooks/fakefile/attributes/first.rb b/chef/examples/config/cookbooks/fakefile/attributes/first.rb new file mode 100644 index 0000000000..927dafe967 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/attributes/first.rb @@ -0,0 +1,2 @@ +Chef::Log.debug("You are the first of fakefile's attributes") +friends("you said we wuz visiting em")
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/fakefile/definitions/test.rb b/chef/examples/config/cookbooks/fakefile/definitions/test.rb new file mode 100644 index 0000000000..ce32293943 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/definitions/test.rb @@ -0,0 +1,13 @@ +define :monkey, :eats => "bananna" do + file "/tmp/monkeynews-#{params[:name]}" do + owner "root" + mode 0644 + action :create + end + + file "/tmp/monkeynews-#{params[:name]}-second-#{params[:eats]}" do + owner "root" + mode 0644 + notifies :touch, resources(:file => "/tmp/monkeynews-#{params[:name]}"), :immediately + end +end
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt new file mode 100644 index 0000000000..9664cb3e15 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt @@ -0,0 +1 @@ +Classic rock is awesome. diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt new file mode 100644 index 0000000000..68a3bf17e4 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt @@ -0,0 +1 @@ +appleseed?
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt new file mode 100644 index 0000000000..d4d8511934 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt @@ -0,0 +1 @@ +is a very nice kitty
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt b/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt new file mode 100644 index 0000000000..c71308a6ca --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt @@ -0,0 +1,3 @@ +Is very pretty on a Saturday at 7:50 in August. Hottest day of the year so far in Seattle. + +something
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/fakefile/recipes/default.rb b/chef/examples/config/cookbooks/fakefile/recipes/default.rb new file mode 100644 index 0000000000..12ba3e69d9 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/recipes/default.rb @@ -0,0 +1,151 @@ +execute "write-foolio" do + command <<-EOH + echo 'monkeypants #{node[:ipaddress]} #{node[:friends]}' > /tmp/foolio + EOH + user "daemon" +end + +script "monkeylikesit" do + code %q{ +print "Woot!\n"; +open(FILE, ">", "/tmp/monkeylikesit") or die "Cannot open monkeylikesit"; +print FILE "You have some interesting hobbies #{node[:ipaddress]}"; +close(FILE); +} + interpreter "perl" +end + +perl "foobar" do + code %q{ +print "Woot!\n"; + } +end + +unless @node[:operatingsystem] == "Darwin" + package "emacs" + + package "emacs" do + action :remove + end + + package "emacs" do + version "22.1-0ubuntu10" + action :install + end + + package "emacs" do + action :upgrade + end + + package "emacs" do + action :purge + end +end + +package "ruby-djbdns" do + action [ :install, :remove, :upgrade, :purge ] + provider Chef::Provider::Package::Rubygems +end + +gem_package "ruby-djbdns" do + action [ :install, :remove, :upgrade, :purge ] + provider Chef::Provider::Package::Rubygems +end + +file "/tmp/foo" do + owner "adam" + mode 0644 + action :create + notifies :delete, resources(:file => "/tmp/glen"), :delayed +end + +remote_file "/tmp/the_park.txt" do + owner "adam" + mode 0644 + source "the_park.txt" + action :create +end + +remote_directory "/tmp/remote_test" do + owner "adam" + mode 0755 + source "remote_test" + files_owner "root" + files_group(node[:operatingsystem] == "Debian" ? "root" : "wheel") + files_mode 0644 + files_backup false +end + +template "/tmp/foo-template" do + owner "adam" + mode 0644 + source "monkey.erb" + variables({ + :one => 'two', + :el_che => 'rhymefest', + :white => { + :stripes => "are the best", + :at => "the sleazy rock thing", + } + }) +end + +link "/tmp/foo" do + link_type :symbolic + target_file "/tmp/xmen" +end + +# 0.upto(1000) do |n| +# file "/tmp/somefile#{n}" do +# owner "adam" +# mode 0644 +# action :create +# end +# end + +directory "/tmp/home" do + owner "root" + mode 0755 + action :create +end + +search(:user, "*") do |u| + directory "/tmp/home/#{u['name']}" do + if u['name'] == "nobody" && @node[:operatingsystem] == "Darwin" + owner "root" + else + owner "#{u['name']}" + end + mode 0755 + action :create + end +end + +monkey "snoopy" do + eats "vegetables" +end + +monkey "snack" + +# user "katie" do +# uid 9999 +# gid 100 +# home "/tmp/home/katie" +# shell "/bin/bash" +# comment "Katie Bethell" +# action :create +# end +# +# user "katie" do +# gid 101 +# action :modify +# end +# +# user "katie" do +# shell "/home/katie" +# action :manage +# end +# +# user "katie" do +# action [ :lock, :unlock, :remove ] +# end diff --git a/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb b/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb new file mode 100644 index 0000000000..29c2639381 --- /dev/null +++ b/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb @@ -0,0 +1,5 @@ +This is a <%= @one %> with <%= @el_che %> + +It was rendered on a system with the ipaddress of <%= @node[:ipaddress] %> + +But it's all about the conquest, with <%= @node[:fqdn ] %> diff --git a/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb b/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb new file mode 100644 index 0000000000..d1b7bd6a23 --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb @@ -0,0 +1 @@ +rubygems("are awesome") diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem Binary files differnew file mode 100644 index 0000000000..d3eafbca10 --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem Binary files differnew file mode 100644 index 0000000000..e9bc3c2276 --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem Binary files differnew file mode 100644 index 0000000000..b11cbe00cc --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem Binary files differnew file mode 100644 index 0000000000..545f850a71 --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem diff --git a/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb b/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb new file mode 100644 index 0000000000..6b60456094 --- /dev/null +++ b/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb @@ -0,0 +1,8 @@ +remote_directory "/srv/gems" do + owner "www-data" + mode 0755 + source "packages" + files_owner "www-data" + files_group "www-data" + files_mode 0644 +end diff --git a/chef/examples/config/cookbooks/servicetest/recipes/default.rb b/chef/examples/config/cookbooks/servicetest/recipes/default.rb new file mode 100644 index 0000000000..812b48e19d --- /dev/null +++ b/chef/examples/config/cookbooks/servicetest/recipes/default.rb @@ -0,0 +1,12 @@ +service "puppet-client" do + service_name "puppet" + pattern "puppetd" + action :enable +end + +file "/tmp/foo" do + owner "aj" + mode 0644 + action :create + notifies :start, resources(:service => "puppet-client"), :immediate +end diff --git a/chef/examples/config/cookbooks/tempfile/attributes/second.rb b/chef/examples/config/cookbooks/tempfile/attributes/second.rb new file mode 100644 index 0000000000..df791b8429 --- /dev/null +++ b/chef/examples/config/cookbooks/tempfile/attributes/second.rb @@ -0,0 +1 @@ +monkey "poots"
\ No newline at end of file diff --git a/chef/examples/config/cookbooks/tempfile/recipes/default.rb b/chef/examples/config/cookbooks/tempfile/recipes/default.rb new file mode 100644 index 0000000000..b4fa86ac32 --- /dev/null +++ b/chef/examples/config/cookbooks/tempfile/recipes/default.rb @@ -0,0 +1,24 @@ +file "/tmp/glen" do + owner "adam" + mode 0755 + action "create" +end + +file "/tmp/metallica" do + action [ :create, :touch, :delete ] +end + +directory "/tmp/marginal" do + owner "adam" + mode 0755 + action :create +end + +remote_directory "/tmp/rubygems" do + owner "adam" + mode 0755 + source "packages" + files_owner "adam" + files_group "adam" + files_mode 0755 +end diff --git a/chef/examples/config/nodes/adam.rb b/chef/examples/config/nodes/adam.rb new file mode 100644 index 0000000000..c3e8e4bc16 --- /dev/null +++ b/chef/examples/config/nodes/adam.rb @@ -0,0 +1,14 @@ +## +# Nodes should have a unique name +## +name "adam" + +## +# Nodes can set arbitrary arguments +## +owner "Adam Jacob" + +## +# Nodes should have recipes +## +recipes "tempfile", "fakefile" diff --git a/chef/examples/config/nodes/default.rb b/chef/examples/config/nodes/default.rb new file mode 100644 index 0000000000..d365690c60 --- /dev/null +++ b/chef/examples/config/nodes/default.rb @@ -0,0 +1,9 @@ +## +# Nodes can set arbitrary arguments +## +owner "Adam Jacob" + +## +# Nodes should have recipes +## +recipes "tempfile", "fakefile" diff --git a/chef/examples/config/nodes/junglist.gen.nz.rb b/chef/examples/config/nodes/junglist.gen.nz.rb new file mode 100644 index 0000000000..70ce7661b5 --- /dev/null +++ b/chef/examples/config/nodes/junglist.gen.nz.rb @@ -0,0 +1,14 @@ +## +# Nodes should have a unique name +## +name "junglist.gen.nz" + +## +# Nodes can set arbitrary arguments +## +owner "AJ Christensen" + +## +# Nodes should have recipes +## +recipes "servicetest" diff --git a/chef/examples/config/nodes/latte.rb b/chef/examples/config/nodes/latte.rb new file mode 100644 index 0000000000..8987738151 --- /dev/null +++ b/chef/examples/config/nodes/latte.rb @@ -0,0 +1,14 @@ +## +# Nodes should have a unique name +## +name "latte.local" + +## +# Nodes can set arbitrary arguments +## +owner "Adam Jacob" + +## +# Nodes should have recipes +## +recipes "tempfile", "fakefile" diff --git a/chef/examples/mrepo/Rakefile b/chef/examples/mrepo/Rakefile new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/chef/examples/mrepo/Rakefile diff --git a/chef/examples/node.rb b/chef/examples/node.rb new file mode 100644 index 0000000000..ef1e6a73fa --- /dev/null +++ b/chef/examples/node.rb @@ -0,0 +1,17 @@ +## +# Nodes should have a unique name +## +name "ops1prod" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" +that "is a cool ass trick" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" + diff --git a/chef/examples/node.yml b/chef/examples/node.yml new file mode 100644 index 0000000000..d61a14875d --- /dev/null +++ b/chef/examples/node.yml @@ -0,0 +1,134 @@ +--- +parameters: + innodb_buffer_pool_size: 150M + kernel: Linux + serialnumber: 0123456789 + processorcount: "2" + operatingsystemrelease: 2.6.18-53.1.4.el5 + tinydns_ipaddress: 208.113.71.77 + rubysitedir: /usr/lib/ruby/site_ruby/1.8 + lsbrelease: ":core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch" + hardwaremodel: x86_64 + uniqueid: 007f0100 + ipaddress: 208.113.71.76 + swapfree: 2.00 GB + puppet_class: + - operations-master + - base + - iptables + - runit + - puppet-client + - sudo + - zsh + - subversion-client + - ruby + - man + - openssh + - build-essential + - rsync-client + - munin-node + - resolver + - iclassify-agent + - openldap-auth + - openldap-client + - nscd + - perl + - nagios-nrpe + - ntp-client + - ntp + - postfix + - emacs + - centos + - puppet-server + - mysql-server + - djbdns-server + - djbdns-base + - djbdns-public-cache + - openldap-server + - iclassify-server + - apache2 + - apache2-module-status + - apache2-module-alias + - apache2-module-auth_basic + - apache2-module-authn_file + - apache2-module-authz_default + - apache2-module-authz_groupfile + - apache2-module-authz_host + - apache2-module-authz_user + - apache2-module-autoindex + - apache2-module-dir + - apache2-module-env + - apache2-module-mime + - apache2-module-negotiation + - apache2-module-setenvif + - apache2-module-log_config + - perlbal + - mongrel-runit + - rails-twoohtwo + - mysql-client + - java + - wildcard-cert + - apache2-module-ldap + - gems-server + - operations-monitoring + - munin-moonin + - apache2-module-fcgid + - apache2-module-ssl + - apache2-module-authnz_ldap + - nagios-server + - subversion-server + - apache2-module-dav + - apache2-module-dav_svn + - trac + - apache2-module-proxy + - apache2-module-proxy_http + - apache2-module-rewrite + - apache2-module-deflate + - apache2-module-headers + puppet_env: + - prod + - prod + memorysize: "1.96" + fqdn: ops1prod.sfo.zoosk.com + kernelrelease: 2.6.18-53.1.4.el5 + ps: ps -ef + type: Node + mongrel_servers: "16" + hardwareisa: x86_64 + apache_listen_ports: + - "80" + - "443" + domain: sfo.zoosk.com + lsbdistdescription: CentOS release 5 (Final) + id: Node:1 + tinydns_internal_ipaddress: 127.0.01 + axfr_ipaddress: 208.113.71.77 + macaddress_eth0: 00:30:48:96:3D:72 + lsbdistrelease: "5" + memoryfree: 638.67 MB + manufacturer: Supermicro + sshrsakey: AAAAB3NzaC1yc2EAAAABIwAAAQEAzZBzuD14E6KJT3YKULXM9jEOIiC/efkn8+rK4yePLwDSNypfClZt8+ThUGlGQVZF0Z1kMa1hLfZD+Puhe9Vp4hMkTBmlvATiWEZduUvkZ7Fh56mnstjmwYoxkvP8FG/ItQtfNJY3UgmtdeeoVYw566P5E9HKKWFUQp5IpZBUpsttVuY/b7ro/ray1lY/bWJ7ykYVwcGWVjsM+W6N0je+mouLcDN3QJAMzMwVuf3MzagKcawvM6qPDJ2Lv6lxRiVn1HZwv+YoPS7+S0/wDl4tZo9+mDb1jdI++af27Q8hKUTjjyjlWUcHG9eDWYjMZPYbY5Qv6FWG6/fEwZ0zu+sVhQ== + lsbdistcodename: Final + slapd_type: master + macaddress_eth1: 00:30:48:96:3D:73 + productname: PDSMi + mysql_server_id: "1" + rubyversion: 1.8.5 + hostname: ops1prod + puppetversion: 0.24.1 + public_dns_cache_ipaddress: 192.168.1.110 + processor0: Intel(R) Xeon(R) CPU 3050 @ 2.13GHz + architecture: x86_64 + facterversion: 1.3.8 + swapsize: 2.00 GB + operatingsystem: CentOS + macaddress: 00:30:48:96:3D:72 + processor1: Intel(R) Xeon(R) CPU 3050 @ 2.13GHz + ec2: "false" + ipaddress_eth0: 208.113.71.76 + mongrel_port_number: "5000" + lsbdistid: CentOS + sshdsakey: AAAAB3NzaC1kc3MAAACBANC70lacMCW6+5QtqOAD7Z/GQ7ChywuX332sbZ9tk6PE6HpCy9PoEjs4MZSrYnqeXxXq2XTRwO4e9agf+6z2gzzOZP3QZj2qoVl7WgTVvvgn1c/KbKcO7/wZXKOv2qg6/vzgrgd0wYUp+1Of04uVdbxRshR2QMY7x67bKw3vvDCbAAAAFQCsW+LA/AA2JxmOCRlZC/DAZn/l4wAAAIEAi9Z1DnYg88KV4BQWGCSAdDkJ0O5maUC12GZPQUDKY+AQU+ilS9QBo3Su0Zo9rrlygNrqyVryIbH7fktz81zj410kskktT6JvUsIR5Ep6C/GTZD8+372AsVQQ+LE+Ot3PXPBntVq2uRRum9wRKul2EZ8+uOhh3gZV2l4WdEg/07sAAACANm3CUM/+grYwDg0Uv6dNgcwsQsXHxfSUbPHGVdiEuf7WjQhNgVQffEwGHOS1xvfv2nvbGFnTnTDuIOo45vhBUmbv9s7NuRhn++M8ZwL+pLAq7BbFCeP4y0WvKOTJdupiLrgVH3KGRFP0djpOagueNQr9zcscTJR3eagImkb8aPs= + ipaddress_eth1: 192.168.1.110 +classes: +- operations-master diff --git a/chef/examples/sample_definition.rb b/chef/examples/sample_definition.rb new file mode 100644 index 0000000000..f536369290 --- /dev/null +++ b/chef/examples/sample_definition.rb @@ -0,0 +1,82 @@ +web_server "monchichi" do + one "something" + two "something else" +end + +runit_service "bobo" do + directory "monkey" + downif "/bin/false is true" + templatedir "something" +end + +define :runit_service, :directory => "/etc/sv", :downif => "/bin/false", :templatedir => nil do + require_recipe "runit" + + validate( + params, + { + :directory => { :required => true }, + :downif => { :required => true }, + :templatedir => { :required => false }, + } + ) + + file "#{param[:directory]}-#{param[:name]}" do + path "#{param[:directory]}/#{param[:name]}" + insure "directory" + owner "root" + group "root" + mode 0755 + end + + file "#{param[:directory]}/#{param[:name]}/log" do + insure "directory" + owner "root" + group "root" + mode 0755 + end + + file "#{param[:directory]}/#{param[:name]}/log/main" do + insure "directory" + owner "root" + group "root" + mode 0755 + end + + symlink "/etc/init.d/#{param[:name]}" do + sv_dir = case node[:lsbdistid] + when 'CentOS': "/usr/local/bin/sv" + else: "/usr/bin/sv" + end + source_file = sv_dir + end + + symlink "/var/service/#{param[:name]}" do + source_file "#{param[:directory]}/#{param[:name]}" + end + + service "#{param[:name]}" do + supports :status => true, :restart => true + end + + template_file "#{param[:directory]}/#{param[:name]}/log/run" do + content "#{param[:templatedir]}/log-run.erb" + owner "root" + group "root" + mode 755 + notifies resource("service[#{param[:name]}]") + end + + template_file "#{param[:directory]}/#{param[:name]}/run" do + content "#{param[:templatedir]}/run.erb" + owner root + group root + mode 755 + notifies resource("service[#{param[:name]}]") + end + + exec "#{param[:name]}-down" do + command "/etc/init.d/#{param[:name]} down" + only_if "#{downif}" + end +end diff --git a/chef/examples/sample_recipe.rb b/chef/examples/sample_recipe.rb new file mode 100644 index 0000000000..6abb31bfcd --- /dev/null +++ b/chef/examples/sample_recipe.rb @@ -0,0 +1,77 @@ +require_recipe "openldap" +require_recipe "openldap::client" +require_recipe "openldap::server" +require_recipe "resolver" +require_recipe "base" + +exec "restart-apache" do + path "/usr/bin:/usr/local/bin" + command "/etc/init.d/apache2 restart" + action :nothing +end + +service "apache2" do + insure "running" + has_restart true +end + +file "/etc/nsswitch.conf" do + owner "root" + group "root" + mode 0644 + notifies :restart, resources("service[openldap]"), :immediately +end + +service "apache2" do + action "enabled" + subscribes :restart, resources("/etc/nsswitch.conf"), :immediately +end + +file "/etc/ldap.conf" do + owner "root" + group "root" + mode 0644 +end + +file "/srv/monkey" do + insure "present" + owner "root" + group "root" + mode 0644 +end + +file "/srv/owl" do + insure "present" + owner "root" + group "root" + mode 0644 +end + +file "/srv/zen" do + insure "absent" +end + +# +# file "/srv/monkey" do |f| +# f.insure = "present" +# f.owner = "adam" +# f.group = "adam" +# f.mode = 0644 +# f.before = resources(:file => "/etc/nsswitch.conf") +# end +# +# file "/etc/ldap-nss.conf" do |f| +# f.insure = "present" +# f.owner = "root" +# f.group = "root" +# f.mode = 0644 +# f.notifies = :refresh, resources(:file => "/etc/ldap.conf") +# end +# +# file "/etc/coffee.conf" do |f| +# f.insure = "present" +# f.owner = "root" +# f.group = "root" +# f.mode = 0644 +# f.subscribes = :polio, resources(:file => "/etc/nsswitch.conf") +# end
\ No newline at end of file diff --git a/chef/examples/search_index/segments b/chef/examples/search_index/segments Binary files differnew file mode 100644 index 0000000000..01d633b27e --- /dev/null +++ b/chef/examples/search_index/segments diff --git a/chef/examples/search_index/segments_0 b/chef/examples/search_index/segments_0 Binary files differnew file mode 100644 index 0000000000..b642403384 --- /dev/null +++ b/chef/examples/search_index/segments_0 diff --git a/chef/examples/search_syntax.rb b/chef/examples/search_syntax.rb new file mode 100644 index 0000000000..4be57b7802 --- /dev/null +++ b/chef/examples/search_syntax.rb @@ -0,0 +1,10 @@ +search(:users, "allowed:#{node[:hostname]} or allowed:#{node[:tags]}") do |u| + user "#{u['username']}" do + uid "#{u['uid']}" + gid "#{u['gid']}" + username "#{u['username']}" + homedir "#{u['homedir']}" + action :create + end +end + diff --git a/chef/examples/user_index.pl b/chef/examples/user_index.pl new file mode 100755 index 0000000000..e78a8125f4 --- /dev/null +++ b/chef/examples/user_index.pl @@ -0,0 +1,115 @@ +#!/usr/bin/perl +# +# M00se on the L00se + +package Chef::Rest; + +use strict; +use warnings; + +use LWP::UserAgent; +use URI; +use Params::Validate qw(:all); +use JSON::Syck; + +sub new { + my $self = shift; + my %p = validate(@_, + { + content_type => { type => SCALAR }, + }, + ); + my $ref = { + 'ua' => LWP::UserAgent->new, + 'content_type' => $p{'content_type'}, + }; + bless $ref, $self; +} + +sub load { + my $self = shift; + my $data = shift; + return JSON::Syck::Load($data); +} + +sub get { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + params => { type => ARRAYREF, optional => 1 }, + }, + ); + + my $url = URI->new($p{'url'}); + if (defined($p{'params'})) { + $url->query_form($p{'params'}); + } + my $req = HTTP::Request->new('GET' => $url); + $req->content_type($self->{'content_type'}); + return $self->ua->request($req); +} + +sub delete { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + }, + ); + my $req = HTTP::Request->new('DELETE' => $p{'url'}); + $req->content_type($self->{'content_type'}); + return $self->ua->request($req); +} + +sub put { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + data => 1, + }, + ); + my $data = JSON::Syck::Dump($p{'data'}); + my $req = HTTP::Request->new('PUT' => $p{'url'}); + $req->content_type($self->{'content_type'}); + $req->content_length(do { use bytes; length($data) }); + $req->content($data); + return $self->ua->request($req); +} + +sub post { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + data => { required => 1 }, + }, + ); + my $data = JSON::Syck::Dump($p{'data'}); + my $req = HTTP::Request->new('POST' => $p{'url'}); + $req->content_type($self->{'content_type'}); + $req->content_length(do { use bytes; length($data) }); + $req->content($data); + return $self->{ua}->request($req); +} + +my $rest = Chef::Rest->new(content_type => 'application/json'); + +while (my @passwd = getpwent) { + print "Ensuring we have $passwd[0]\n"; + $rest->post( + url => 'http://localhost:4000/search/user/entries', + data => { + id => $passwd[0], + name => $passwd[0], + uid => $passwd[2], + gid => $passwd[3], + gecos => $passwd[6], + dir => $passwd[7], + shell => $passwd[8], + change => '', + expire => $passwd[9], + } + ) +} diff --git a/chef/examples/user_index.rb b/chef/examples/user_index.rb new file mode 100755 index 0000000000..485cff81b8 --- /dev/null +++ b/chef/examples/user_index.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# +# Create a users index, based on /etc/passwd + +require 'etc' +require File.join(File.dirname(__FILE__), "..", "lib", "chef") + +Chef::Config[:log_level] = :info +r = Chef::REST.new("http://localhost:4000") + +users = Array.new +Etc.passwd do |passwd| + Chef::Log.info("Ensuring we have #{passwd.name}") + r.post_rest("search/user/entries", + { + :id => passwd.name, + :name => passwd.name, + :uid => passwd.uid, + :gid => passwd.gid, + :gecos => passwd.gecos, + :dir => passwd.dir, + :shell => passwd.shell, + :change => passwd.change, + :expire => passwd.expire + } + ) +end diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb new file mode 100644 index 0000000000..ac7f43adef --- /dev/null +++ b/chef/lib/chef.rb @@ -0,0 +1,29 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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' + +Dir[ + File.join( + File.dirname(__FILE__), + 'chef/**/*.rb' + )].sort.each { |lib| require lib unless lib =~ /server/ } + +class Chef + VERSION = '0.0.1' +end diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb new file mode 100644 index 0000000000..fd7e263ce0 --- /dev/null +++ b/chef/lib/chef/client.rb @@ -0,0 +1,277 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require File.join(File.dirname(__FILE__), "mixin", "generate_url") +require File.join(File.dirname(__FILE__), "mixin", "checksum") + +require 'rubygems' +require 'facter' + +class Chef + class Client + + include Chef::Mixin::GenerateURL + include Chef::Mixin::Checksum + + attr_accessor :node, :registration, :safe_name + + # Creates a new Chef::Client. + def initialize() + @node = nil + @safe_name = nil + @registration = nil + @rest = Chef::REST.new(Chef::Config[:registration_url]) + end + + # Do a full run for this Chef::Client. Calls: + # + # * build_node - Get the last known state, merge with local changes + # * register - Make sure we have an openid + # * authenticate - Authenticate with our openid + # * sync_definitions - Populate the local cache with all the definitions + # * sync_recipes - Populate the local cache with all the recipes + # * do_attribute_files - Populate the local cache with all attributes, and execute them + # * save_node - Store the new node configuration + # * converge - Bring this system up to date, based on the local cache + # * save_node - Store the node again, in case convergence altered future state + # + # === Returns + # true:: Always returns true. + def run + build_node + register + authenticate + sync_definitions + sync_recipes + do_attribute_files + save_node + converge + save_node + true + 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 Facter. + # + # === Parameters + # node_name<String>:: The name of the node to build - defaults to nil + # + # === Returns + # node:: Returns the created node object, also stored in @node + def build_node(node_name=nil) + node_name ||= Facter["fqdn"].value ? Facter["fqdn"].value : Facter["hostname"].value + @safe_name = node_name.gsub(/\./, '_') + Chef::Log.debug("Building node object for #{@safe_name}") + begin + @node = @rest.get_rest("nodes/#{@safe_name}") + rescue Net::HTTPServerException => e + unless e.message =~ /^404/ + raise e + end + end + unless @node + @node ||= Chef::Node.new + @node.name(node_name) + end + Facter.each do |field, value| + @node[field] = value + end + @node + end + + # If this node has been registered before, this method will fetch the current registration + # data. + # + # If it has not, we register it by calling create_registration. + # + # === Returns + # true:: Always returns true + def register + Chef::Log.debug("Registering #{@safe_name} for an openid") + @registration = nil + begin + @registration = @rest.get_rest("registrations/#{@safe_name}") + rescue Net::HTTPServerException => e + unless e.message =~ /^404/ + raise e + end + end + + if @registration + reg = Chef::FileStore.load("registration", @safe_name) + @secret = reg["secret"] + else + create_registration + end + true + end + + # Generates a random secret, stores it in the Chef::Filestore with the "registration" key, + # and posts our nodes registration information to the server. + # + # === Returns + # true:: Always returns true + def create_registration + @secret = random_password(500) + Chef::FileStore.store("registration", @safe_name, { "secret" => @secret }) + @rest.post_rest("registrations", { :id => @safe_name, :password => @secret }) + true + end + + # Authenticates the node via OpenID. + # + # === Returns + # true:: Always returns true + def authenticate + 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}", + "submit" => "Verify" + }) + @rest.post_rest( + "#{Chef::Config[:openid_url]}#{response["action"]}", + { "password" => @secret } + ) + end + + # Update the file caches for a given cache segment. Takes a segment name + # and a hash that matches one of the cookbooks/_attribute_files style + # remote file listings. + # + # === Parameters + # segment<String>:: The cache segment to update + # remote_list<Hash>:: A cookbooks/_attribute_files style remote file listing + def update_file_cache(segment, remote_list) + # We need the list of known good attribute files, so we can delete any that are + # just laying about. + file_canonical = Hash.new + + remote_list.each do |rf| + cache_file = File.join("cookbooks", rf['cookbook'], segment, rf['name']) + file_canonical[cache_file] = true + + current_checksum = nil + if Chef::FileCache.has_key?(cache_file) + current_checksum = checksum(Chef::FileCache.load(cache_file, false)) + end + + rf_url = generate_cookbook_url( + rf['name'], + rf['cookbook'], + segment, + @node, + current_checksum ? { 'checksum' => current_checksum } : nil + ) + Chef::Log.debug(rf_url) + + changed = true + begin + raw_file = @rest.get_rest(rf_url, true) + rescue Net::HTTPRetriableError => e + if e.response.kind_of?(Net::HTTPNotModified) + changed = false + Chef::Log.debug("Cache file #{cache_file} is unchanged") + else + raise e + end + end + + if changed + Chef::Log.info("Storing updated #{cache_file} in the cache.") + Chef::FileCache.move_to(raw_file.path, cache_file) + end + end + + Chef::FileCache.list.each do |cache_file| + if cache_file.match("cookbooks/.+?/#{segment}") + unless file_canonical[cache_file] + Chef::Log.info("Removing #{cache_file} from the cache; it is no longer on the server.") + Chef::FileCache.delete(cache_file) + end + end + end + + end + + # Gets all the attribute files included in all the cookbooks available on the server, + # and executes them. + # + # === Returns + # true:: Always returns true + def do_attribute_files + Chef::Log.debug("Synchronizing attributes") + update_file_cache("attributes", @rest.get_rest('cookbooks/_attribute_files')) + Chef::FileCache.list.each do |cache_file| + if cache_file.match("cookbooks/.+?/attributes") + Chef::Log.debug("Executing #{cache_file}") + @node.from_file(Chef::FileCache.load(cache_file, false)) + end + end + true + end + + def sync_definitions + Chef::Log.debug("Synchronizing definitions") + update_file_cache("definitions", @rest.get_rest('cookbooks/_definition_files')) + end + + def sync_recipes + Chef::Log.debug("Synchronizing attributes") + update_file_cache("recipes", @rest.get_rest('cookbooks/_recipe_files')) + end + + # Updates the current node configuration on the server. + # + # === Returns + # true:: Always returns true + def save_node + Chef::Log.debug("Saving the current state of node #{@safe_name}") + @node = @rest.put_rest("nodes/#{@safe_name}", @node) + true + end + + # Compiles the full list of recipes for the server, and passes it to an instance of + # Chef::Runner.converge. + # + # === Returns + # true:: Always returns true + def converge + Chef::Log.debug("Compiling recipes for node #{@safe_name}") + Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") + compile = Chef::Compile.new() + compile.node = @node + compile.load_definitions + compile.load_recipes + + Chef::Log.debug("Executing recipes for node #{@safe_name}") + cr = Chef::Runner.new(@node, compile.collection) + cr.converge + true + end + + protected + # Generates a random password of "len" length. + def random_password(len) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + newpass = "" + 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } + newpass + end + + end +end diff --git a/chef/lib/chef/compile.rb b/chef/lib/chef/compile.rb new file mode 100644 index 0000000000..d5f38368ed --- /dev/null +++ b/chef/lib/chef/compile.rb @@ -0,0 +1,73 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Compile + + attr_accessor :node, :cookbook_loader, :collection, :definitions + + # Creates a new Chef::Compile object. This object gets used by the Chef Server to generate + # a fully compiled recipe list for a node. + def initialize() + @node = nil + @cookbook_loader = Chef::CookbookLoader.new + @collection = Chef::ResourceCollection.new + @definitions = Hash.new + end + + # Looks up the node via the "name" argument, first from CouchDB, then by calling + # Chef::Node.find_file(name) + # + # The first step in compiling the catalog. Results available via the node accessor. + def load_node(name) + Chef::Log.debug("Loading Chef Node #{name} from CouchDB") + @node = Chef::Node.load(name) + Chef::Log.debug("Loading Recipe for Chef Node #{name}") + @node.find_file(name) + @node + end + + # Load all the definitions, from every cookbook, so they are available when we process + # the recipes. + # + # Results available via the definitions accessor. + def load_definitions() + @cookbook_loader.each do |cookbook| + hash = cookbook.load_definitions + @definitions.merge!(hash) + end + end + + # Load all the recipes specified in the node data (loaded via load_node, above.) + # + # The results are available via the collection accessor (which returns a Chef::ResourceCollection + # object) + def load_recipes + @node.recipes.each do |recipe| + rmatch = recipe.match(/(.+?)::(.+)/) + if rmatch + cookbook = @cookbook_loader[rmatch[1]] + cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) + else + cookbook = @cookbook_loader[recipe] + cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) + end + end + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb new file mode 100644 index 0000000000..573cf9dfb7 --- /dev/null +++ b/chef/lib/chef/config.rb @@ -0,0 +1,98 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "check_helper") +require File.join(File.dirname(__FILE__), "mixin", "from_file") + +# Chef::Config[:variable] +# @config = Chef::Config.new() +# +# Chef::ConfigFast << Chef::Config +# +# Chef::Config.from_file(foo) +# Chef::Resource.from_file (NoMethodError) +# Chef::Config[:cookbook_path] +# Chef::Config.cookbook_path +# Chef::Config.cookbook_path "one", "two" + +class Chef + class Config + include Chef::Mixin::CheckHelper + + @configuration = { + :cookbook_path => [ "/etc/chef/site-cookbook", "/etc/chef/cookbook" ], + :node_path => "/etc/chef/node", + :file_store_path => "/var/chef/store", + :search_index_path => "/var/chef/search_index", + :log_level => :info, + :log_location => STDOUT, + :merb_log_path => "/var/log/chef/merb.log", + :openid_providers => nil, + :ssl_verify_mode => :verify_none, + :rest_timeout => 60, + :couchdb_url => "http://localhost:5984", + :registration_url => "http://localhost:4000", + :openid_url => "http://localhost:4001", + :template_url => "http://localhost:4000", + :remotefile_url => "http://localhost:4000", + :search_url => "http://localhost:4000", + :couchdb_database => "chef", + :openid_store_path => "/var/chef/openid/db", + :openid_cstore_path => "/var/chef/openid/cstore", + :file_cache_path => "/var/chef/cache", + :executable_path => ENV['PATH'] ? ENV['PATH'].split(File::PATH_SEPARATOR) : [] + } + + class << self + include Chef::Mixin::FromFile + + def configure(&block) + yield @configuration + end + + def [](config_option) + if @configuration.has_key?(config_option.to_sym) + @configuration[config_option.to_sym] + else + raise ArgumentError, "Cannot find configuration option #{config_option.to_s}" + end + end + + def []=(config_option, value) + @configuration[config_option.to_sym] = value + end + + def has_key?(key) + @configuration.has_key?(key.to_sym) + end + + def method_missing(method_symbol, *args) + if @configuration.has_key?(method_symbol) + if args.length == 1 + @configuration[method_symbol] = args[0] + elsif args.length > 1 + @configuration[method_symbol] = args + end + return @configuration[method_symbol] + else + raise ArgumentError, "Cannot find configuration option #{method_symbol.to_s}" + end + end + + end # class << self + end +end
\ No newline at end of file diff --git a/chef/lib/chef/cookbook.rb b/chef/lib/chef/cookbook.rb new file mode 100644 index 0000000000..46fc956d83 --- /dev/null +++ b/chef/lib/chef/cookbook.rb @@ -0,0 +1,111 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Cookbook + + attr_accessor :attribute_files, :definition_files, :template_files, :remote_files, :name + attr_reader :recipe_files + + def initialize(name) + @name = name + @attribute_files = Array.new + @definition_files = Array.new + @template_files = Array.new + @remote_files = Array.new + @recipe_files = Array.new + @recipe_names = Hash.new + @loaded_attributes = false + end + + def load_attributes(node) + unless node.kind_of?(Chef::Node) + raise ArgumentError, "You must pass a Chef::Node to load_attributes!" + end + @attribute_files.each do |file| + node.from_file(file) + end + @loaded_atributes = true + node + end + + def load_definitions + results = Hash.new + @definition_files.each do |file| + Chef::Log.debug("Loading cookbook #{name}'s definitions from #{file}") + resourcedef = Chef::ResourceDefinition.new + resourcedef.from_file(file) + results[resourcedef.name] = resourcedef + end + results + end + + def recipe_files=(*args) + @recipe_files = args.flatten + @recipe_files.each_index do |i| + file = @recipe_files[i] + case file + when /(.+\/)(.+).rb$/ + @recipe_names[$2] = i + when /(.+).rb$/ + @recipe_names[$1] = i + else + @recipe_names[file] = i + end + end + @recipe_files + end + + def recipe?(name) + lookup_name = name + if name =~ /(.+)::(.+)/ + cookbook_name = $1 + lookup_name = $2 + return false unless cookbook_name == @name + end + @recipe_names.has_key?(lookup_name) + end + + def recipes + results = Array.new + @recipe_names.each_key do |rname| + results << "#{@name}::#{rname}" + end + results + end + + def load_recipe(name, node, collection=nil, definitions=nil, cookbook_loader=nil) + cookbook_name = @name + recipe_name = nil + nmatch = name.match(/^(.+?)::(.+)$/) + recipe_name = nmatch ? nmatch[2] : name + + unless @recipe_names.has_key?(recipe_name) + raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{@name}" + end + Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{cookbook_name}") if Chef::Log.debug? + unless @loaded_attributes + load_attributes(node) + end + recipe = Chef::Recipe.new(cookbook_name, recipe_name, node, + collection, definitions, cookbook_loader) + recipe.from_file(@recipe_files[@recipe_names[recipe_name]]) + recipe + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb new file mode 100644 index 0000000000..6f6c8d8010 --- /dev/null +++ b/chef/lib/chef/cookbook_loader.rb @@ -0,0 +1,147 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 CookbookLoader + + attr_accessor :cookbook + + include Enumerable + + def initialize() + @cookbook = Hash.new + load_cookbooks + end + + def load_cookbooks + cookbook_settings = Hash.new + Chef::Config.cookbook_path.each do |cb_path| + Dir[File.join(cb_path, "*")].each do |cookbook| + next unless File.directory?(cookbook) + cookbook_name = File.basename(cookbook).to_sym + unless cookbook_settings.has_key?(cookbook_name) + cookbook_settings[cookbook_name] = { + :ignore_regexes => Array.new, + :attribute_files => Array.new, + :definition_files => Array.new, + :recipe_files => Array.new, + :template_files => Array.new, + :remote_files => Array.new, + } + end + ignore_regexes = load_ignore_file(File.join(cookbook, "ignore")) + cookbook_settings[cookbook_name][:ignore_regexes].concat(ignore_regexes) + load_files_unless_basename( + File.join(cookbook, "attributes", "*.rb"), + cookbook_settings[cookbook_name][:attribute_files], + cookbook_settings[cookbook_name][:ignore_regexes] + ) + load_files_unless_basename( + File.join(cookbook, "definitions", "*.rb"), + cookbook_settings[cookbook_name][:definition_files], + cookbook_settings[cookbook_name][:ignore_regexes] + ) + load_files_unless_basename( + File.join(cookbook, "recipes", "*.rb"), + cookbook_settings[cookbook_name][:recipe_files], + cookbook_settings[cookbook_name][:ignore_regexes] + ) + load_cascading_files( + File.join(cookbook, "templates", "**", "*.erb"), + File.join(cookbook, "templates"), + cookbook_settings[cookbook_name][:template_files], + cookbook_settings[cookbook_name][:ignore_regexes] + ) + load_cascading_files( + File.join(cookbook, "files", "**", "*"), + File.join(cookbook, "files"), + cookbook_settings[cookbook_name][:remote_files], + cookbook_settings[cookbook_name][:ignore_regexes] + ) + end + end + cookbook_settings.each_key do |cookbook| + @cookbook[cookbook] = Chef::Cookbook.new(cookbook) + @cookbook[cookbook].attribute_files = cookbook_settings[cookbook][:attribute_files] + @cookbook[cookbook].definition_files = cookbook_settings[cookbook][:definition_files] + @cookbook[cookbook].recipe_files = cookbook_settings[cookbook][:recipe_files] + @cookbook[cookbook].template_files = cookbook_settings[cookbook][:template_files] + @cookbook[cookbook].remote_files = cookbook_settings[cookbook][:remote_files] + end + end + + def [](cookbook) + if @cookbook.has_key?(cookbook.to_sym) + @cookbook[cookbook.to_sym] + else + raise ArgumentError, "Cannot find a cookbook named #{cookbook.to_s}" + end + end + + def each + @cookbook.each_value do |cobject| + yield cobject + end + end + + private + + def load_ignore_file(ignore_file) + results = Array.new + if File.exists?(ignore_file) && File.readable?(ignore_file) + IO.foreach(ignore_file) do |line| + next if line =~ /^#/ + next if line =~ /^\w*$/ + line.chomp! + results << Regexp.new(line) + end + end + results + end + + def load_cascading_files(file_glob, base_path, result_array, ignore_regexes) + Dir[file_glob].each do |file| + next if skip_file(file, ignore_regexes) + file =~ /^#{base_path}\/(.+)$/ + singlecopy = $1 + unless result_array.detect { |f| f =~ /#{singlecopy}$/ } + result_array << file + end + end + end + + def load_files_unless_basename(file_glob, result_array, ignore_regexes) + Dir[file_glob].each do |file| + next if skip_file(file, ignore_regexes) + file_basename = File.basename(file) + # If we've seen a file with this basename before, skip it. + unless result_array.detect { |f| File.basename(f) == file_basename } + result_array << file + end + end + end + + def skip_file(file, ignore_regexes) + skip = false + ignore_regexes.each do |exp| + skip = true if exp.match(file) + end + skip + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/couchdb.rb b/chef/lib/chef/couchdb.rb new file mode 100644 index 0000000000..22a651ca48 --- /dev/null +++ b/chef/lib/chef/couchdb.rb @@ -0,0 +1,149 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require 'digest/sha2' +require 'json' + +class Chef + class CouchDB + include Chef::Mixin::ParamsValidate + + def initialize(url=nil) + url ||= Chef::Config[:couchdb_url] + @rest = Chef::REST.new(url) + end + + def create_db + @database_list = @rest.get_rest("_all_dbs") + unless @database_list.detect { |db| db == Chef::Config[:couchdb_database] } + response = @rest.put_rest(Chef::Config[:couchdb_database], Hash.new) + end + Chef::Config[:couchdb_database] + end + + def create_design_document(name, data) + to_update = true + begin + old_doc = @rest.get_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}") + if data["version"] != old_doc["version"] + data["_rev"] = old_doc["_rev"] + Chef::Log.debug("Updating #{name} views") + else + to_update = false + end + rescue + Chef::Log.debug("Creating #{name} views for the first time") + end + if to_update + @rest.put_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}", data) + end + true + end + + def store(obj_type, name, object) + validate( + { + :obj_type => obj_type, + :name => name, + :object => object, + }, + { + :object => { :respond_to => :to_json }, + } + ) + @rest.put_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}", object) + end + + def load(obj_type, name) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}") + end + + def delete(obj_type, name, rev=nil) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + unless rev + last_obj = @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}") + if last_obj.respond_to?(:couchdb_rev) + rev = last_obj.couchdb_rev + else + rev = last_obj['_rev'] + end + end + @rest.delete_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}?rev=#{rev}") + end + + def list(view, inflate=false) + validate( + { + :view => view, + }, + { + :view => { :kind_of => String } + } + ) + if inflate + @rest.get_rest("#{Chef::Config[:couchdb_database]}/_view/#{view}/all") + else + @rest.get_rest("#{Chef::Config[:couchdb_database]}/_view/#{view}/all_id") + end + end + + def has_key?(obj_type, name) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + begin + @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}") + true + rescue + false + end + end + + private + def safe_name(name) + name.gsub(/\./, "_") + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb new file mode 100644 index 0000000000..fa037a7f6c --- /dev/null +++ b/chef/lib/chef/exceptions.rb @@ -0,0 +1,30 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Exception + class Exec < RuntimeError; end + class FileNotFound < RuntimeError; end + class Package < RuntimeError; end + class Service < RuntimeError; end + class SearchIndex < RuntimeError; end + class Override < RuntimeError; end + class UnsupportedAction < RuntimeError; end + class MissingLibrary < RuntimeError; end + class User < RuntimeError; end + end +end diff --git a/chef/lib/chef/file_cache.rb b/chef/lib/chef/file_cache.rb new file mode 100644 index 0000000000..ea9e17fbb7 --- /dev/null +++ b/chef/lib/chef/file_cache.rb @@ -0,0 +1,203 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require File.join(File.dirname(__FILE__), "mixin", "create_path") +require 'json' + +class Chef + class FileCache + class << self + include Chef::Mixin::ParamsValidate + include Chef::Mixin::CreatePath + + # Write a file to the File Cache. + # + # === Parameters + # path<String>:: The path to the file you want to put in the cache - should + # be relative to Chef::Config[:file_cache_path] + # contents<String>:: A string with the contents you want written to the file + # + # === Returns + # true + def store(path, contents) + validate( + { + :path => path, + :contents => contents + }, + { + :path => { :kind_of => String }, + :contents => { :kind_of => String }, + } + ) + + file_path_array = File.split(path) + file_name = file_path_array.pop + cache_path = create_cache_path(File.join(file_path_array)) + io = File.open(File.join(cache_path, file_name), "w") + io.print(contents) + io.close + true + end + + # Move a file in to the cache. Useful with the REST raw file output. + # + # === Parameteres + # file<String>:: The path to the file you want in the cache + # path<String>:: The relative name you want the new file to use + def move_to(file, path) + validate( + { + :file => file, + :path => path + }, + { + :file => { :kind_of => String }, + :path => { :kind_of => String }, + } + ) + + file_path_array = File.split(path) + file_name = file_path_array.pop + if File.exists?(file) && File.writable?(file) + File.rename( + file, + File.join(create_cache_path(File.join(file_path_array), true), file_name) + ) + else + raise RuntimeError, "Cannot move #{file} to #{path}!" + end + end + + # Read a file from the File Cache + # + # === Parameters + # path<String>:: The path to the file you want to load - should + # be relative to Chef::Config[:file_cache_path] + # read<True/False>:: Whether to return the file contents, or the path. + # Defaults to true. + # + # === Returns + # String:: A string with the file contents. + # + # === Raises + # Chef::Exception::FileNotFound:: If it cannot find the file in the cache + def load(path, read=true) + validate( + { + :path => path + }, + { + :path => { :kind_of => String } + } + ) + cache_path = create_cache_path(path, false) + raise Chef::Exception::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exists?(cache_path) + if read + File.read(cache_path) + else + cache_path + end + end + + # Delete a file from the File Cache + # + # === Parameters + # path<String>:: The path to the file you want to delete - should + # be relative to Chef::Config[:file_cache_path] + # + # === Returns + # true + def delete(path) + validate( + { + :path => path + }, + { + :path => { :kind_of => String }, + } + ) + cache_path = create_cache_path(path, false) + if File.exists?(cache_path) + File.unlink(cache_path) + end + true + end + + # List all the files in the Cache + # + # === Returns + # Array:: An array of files in the cache, suitable for use with load, delete and store + def list() + keys = Array.new + Dir[File.join(Chef::Config[:file_cache_path], '**', '*')].each do |f| + if File.file?(f) + path = f.match("^#{Chef::Config[:file_cache_path]}\/(.+)")[1] + keys << path + end + end + keys + end + + # Whether or not this file exists in the Cache + # + # === Parameters + # path:: The path to the file you want to check - is relative + # to Chef::Config[:file_cache_path] + # + # === Returns + # True:: If the file exists + # False:: If it does not + def has_key?(path) + validate( + { + :path => path + }, + { + :path => { :kind_of => String }, + } + ) + full_path = create_cache_path(path, false) + if File.exists?(full_path) + true + else + false + end + end + + # Create a full path to a given file in the cache. By default, + # also creates the path if it does not exist. + # + # === Parameters + # path:: The path to create, relative to Chef::Config[:file_cache_path] + # create_if_missing:: True by default - whether to create the path if it does not exist + # + # === Returns + # String:: The fully expanded path + def create_cache_path(path, create_if_missing=true) + cache_dir = File.expand_path(File.join(Chef::Config[:file_cache_path], path)) + if create_if_missing + create_path(cache_dir) + else + cache_dir + end + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/file_store.rb b/chef/lib/chef/file_store.rb new file mode 100644 index 0000000000..6fe13e5ad6 --- /dev/null +++ b/chef/lib/chef/file_store.rb @@ -0,0 +1,135 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require File.join(File.dirname(__FILE__), "mixin", "create_path") +require 'digest/sha2' +require 'json' + +class Chef + class FileStore + class << self + include Chef::Mixin::ParamsValidate + include Chef::Mixin::CreatePath + + def store(obj_type, name, object) + validate( + { + :obj_type => obj_type, + :name => name, + :object => object, + }, + { + :object => { :respond_to => :to_json }, + } + ) + + store_path = create_store_path(obj_type, name) + io = File.open(store_path, "w") + io.puts object.to_json + io.close + end + + def load(obj_type, name) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + store_path = create_store_path(obj_type, name) + raise "Cannot find #{store_path} for #{obj_type} #{name}!" unless File.exists?(store_path) + object = JSON.parse(IO.read(store_path)) + end + + def delete(obj_type, name) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + store_path = create_store_path(obj_type, name) + if File.exists?(store_path) + File.unlink(store_path) + end + end + + def list(obj_type, inflate=false) + validate( + { + :obj_type => obj_type, + }, + { + :obj_type => { :kind_of => String } + } + ) + keys = Array.new + Dir[File.join(Chef::Config[:file_store_path], obj_type, '**', '*')].each do |f| + if File.file?(f) + if inflate + keys << load(obj_type, File.basename(f)) + else + keys << File.basename(f) + end + end + end + keys + end + + def has_key?(obj_type, name) + validate( + { + :obj_type => obj_type, + :name => name, + }, + { + :obj_type => { :kind_of => String }, + :name => { :kind_of => String }, + } + ) + Dir[File.join(Chef::Config[:file_store_path], obj_type, '**', '*')].each do |f| + if File.file?(f) + return true if File.basename(f) == name + end + end + return false + end + + def create_store_path(obj_type, key) + shadigest = Digest::SHA2.hexdigest("#{obj_type}#{key}") + + file_path = [ + Chef::Config[:file_store_path], + obj_type, + shadigest[0,1], + shadigest[1,3] + ] + File.join(create_path(file_path), key) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/log.rb b/chef/lib/chef/log.rb new file mode 100644 index 0000000000..ff98cc9dff --- /dev/null +++ b/chef/lib/chef/log.rb @@ -0,0 +1,86 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'logger' + +class Chef + class Log + + @logger = nil + + class << self + attr_reader :logger #:nodoc + + # Use Chef::Logger.init when you want to set up the logger manually. Arguments to this method + # get passed directly to Logger.new, so check out the documentation for the standard Logger class + # to understand what to do here. + # + # If this method is called with no arguments, it will log to STDOUT at the :info level. + # + # It also configures the Logger instance it creates to use the custom Chef::Log::Formatter class. + def init(*opts) + if opts.length == 0 + @logger = Logger.new(STDOUT) + else + @logger = Logger.new(*opts) + end + @logger.formatter = Chef::Log::Formatter.new() + level(Chef::Config.log_level) + end + + # Sets the level for the Logger object by symbol. Valid arguments are: + # + # :debug + # :info + # :warn + # :error + # :fatal + # + # Throws an ArgumentError if you feed it a bogus log level. + def level(loglevel) + init() unless @logger + case loglevel + when :debug + @logger.level = Logger::DEBUG + when :info + @logger.level = Logger::INFO + when :warn + @logger.level = Logger::WARN + when :error + @logger.level = Logger::ERROR + when :fatal + @logger.level = Logger::FATAL + else + raise ArgumentError, "Log level must be one of :debug, :info, :warn, :error, or :fatal" + end + end + + # Passes any other method calls on directly to the underlying Logger object created with init. If + # this method gets hit before a call to Chef::Logger.init has been made, it will call + # Chef::Logger.init() with no arguments. + def method_missing(method_symbol, *args) + init() unless @logger + if args.length > 0 + @logger.send(method_symbol, *args) + else + @logger.send(method_symbol) + end + end + + end # class << self + end +end
\ No newline at end of file diff --git a/chef/lib/chef/log/formatter.rb b/chef/lib/chef/log/formatter.rb new file mode 100644 index 0000000000..9f758178f5 --- /dev/null +++ b/chef/lib/chef/log/formatter.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'logger' +require 'time' + +class Chef + class Log + class Formatter < Logger::Formatter + @@show_time = true + + def self.show_time=(show=false) + @@show_time = show + end + + # Prints a log message as '[time] severity: message' if Chef::Log::Formatter.show_time == true. + # Otherwise, doesn't print the time. + def call(severity, time, progname, msg) + if @@show_time + sprintf("[%s] %s: %s\n", time.rfc2822(), severity, msg2str(msg)) + else + sprintf("%s: %s\n", severity, msg2str(msg)) + end + end + + # Converts some argument to a Logger.severity() call to a string. Regular strings pass through like + # normal, Exceptions get formatted as "message (class)\nbacktrace", and other random stuff gets + # put through "object.inspect" + def msg2str(msg) + case msg + when ::String + msg + when ::Exception + "#{ msg.message } (#{ msg.class })\n" << + (msg.backtrace || []).join("\n") + else + msg.inspect + end + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/mixin/check_helper.rb b/chef/lib/chef/mixin/check_helper.rb new file mode 100644 index 0000000000..959b57cbb7 --- /dev/null +++ b/chef/lib/chef/mixin/check_helper.rb @@ -0,0 +1,31 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + module Mixin + module CheckHelper + def set_if_args(thing, arguments) + raise ArgumentError, "Must call set_if_args with a block!" unless Kernel.block_given? + if arguments != nil + yield(arguments) + else + thing + end + end + end + end +end diff --git a/chef/lib/chef/mixin/checksum.rb b/chef/lib/chef/mixin/checksum.rb new file mode 100644 index 0000000000..5a7eed4165 --- /dev/null +++ b/chef/lib/chef/mixin/checksum.rb @@ -0,0 +1,36 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'digest/md5' + +class Chef + module Mixin + module Checksum + + def checksum(file) + digest = Digest::MD5.new + fh = ::File.open(file) + fh.each do |line| + digest.update(line) + end + digest.hexdigest + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/mixin/command.rb b/chef/lib/chef/mixin/command.rb new file mode 100644 index 0000000000..08344148a5 --- /dev/null +++ b/chef/lib/chef/mixin/command.rb @@ -0,0 +1,206 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'tmpdir' +require 'fcntl' +require 'etc' + +class Chef + module Mixin + module Command + + def run_command(args={}) + if args.has_key?(:creates) + if File.exists?(args[:creates]) + Chef::Log.debug("Skipping #{args[:command_string]} - creates #{args[:creates]} exists.") + return false + end + end + + if args.has_key?(:onlyif) + status = popen4(args[:onlyif]) { |p, i, o, e| } + if status.exitstatus != 0 + Chef::Log.debug("Skipping #{args[:command_string]} - onlyif #{args[:onlyif]} returned #{status.exitstatus}") + return false + end + end + + if args.has_key?(:not_if) + status = popen4(args[:not_if]) { |p, i, o, e| } + if status.exitstatus == 0 + Chef::Log.debug("Skipping #{args[:command_string]} - unless #{args[:not_if]} returned #{status.exitstatus}") + return false + end + end + + exec_processing_block = lambda do |pid, stdin, stdout, stderr| + stdin.close + + stdout_string = stdout.gets(nil) + if stdout_string + Chef::Log.debug("---- Begin #{args[:command_string]} STDOUT ----") + Chef::Log.debug(stdout_string.strip) + Chef::Log.debug("---- End #{args[:command_string]} STDOUT ----") + end + stderr_string = stderr.gets(nil) + if stderr_string + Chef::Log.debug("---- Begin #{args[:command_string]} STDERR ----") + Chef::Log.debug(stderr_string.strip) + Chef::Log.debug("---- End #{args[:command_string]} STDERR ----") + end + end + + args[:cwd] ||= Dir.tmpdir + unless File.directory?(args[:cwd]) + raise Chef::Exception::Exec, "#{args[:cwd]} does not exist or is not a directory" + end + + status = nil + Dir.chdir(args[:cwd]) do + if args[:timeout] + begin + Timeout.timeout(args[:timeout]) do + status = popen4(args[:command], args, &exec_processing_block) + end + rescue Exception => e + Chef::Log.error("#{args[:command_string]} exceeded timeout #{args[:timeout]}") + raise(e) + end + else + status = popen4(args[:command], args, &exec_processing_block) + end + + args[:returns] ||= 0 + if status.exitstatus != args[:returns] + raise Chef::Exception::Exec, "#{args[:command_string]} returned #{status.exitstatus}, expected #{args[:returns]}" + else + Chef::Log.debug("Ran #{args[:command_string]} (#{args[:command]}) returned #{status.exitstatus}") + end + end + status + end + + module_function :run_command + + # This is taken directly from Ara T Howard's Open4 library, and then + # modified to suit the needs of Chef. Any bugs here are most likely + # my own, and not Ara's. + # + # The original appears in external/open4.rb in it's unmodified form. + # + # Thanks, Ara. + def popen4(cmd, args={}, &b) + + args[:user] ||= nil + unless args[:user].kind_of?(Integer) + args[:user] = Etc.getpwnam(args[:user]).uid if args[:user] + end + args[:group] ||= nil + unless args[:group].kind_of?(Integer) + args[:group] = Etc.getgrnam(args[:group]).gid if args[:group] + end + args[:environment] ||= nil + + pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe + + verbose = $VERBOSE + begin + $VERBOSE = nil + ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + cid = fork { + pw.last.close + STDIN.reopen pw.first + pw.first.close + + pr.first.close + STDOUT.reopen pr.last + pr.last.close + + pe.first.close + STDERR.reopen pe.last + pe.last.close + + STDOUT.sync = STDERR.sync = true + + if args[:user] + Process.euid = args[:user] + Process.uid = args[:user] + end + + if args[:group] + Process.egid = args[:group] + Process.gid = args[:group] + end + + if args[:environment] + args[:environment].each do |key,value| + ENV[key] = value + end + end + + begin + if cmd.kind_of?(Array) + exec(*cmd) + else + exec(cmd) + end + raise 'forty-two' + rescue Exception => e + Marshal.dump(e, ps.last) + ps.last.flush + end + ps.last.close unless (ps.last.closed?) + exit! + } + ensure + $VERBOSE = verbose + end + + [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close} + + begin + e = Marshal.load ps.first + raise(Exception === e ? e : "unknown failure!") + rescue EOFError # If we get an EOF error, then the exec was successful + 42 + ensure + ps.first.close + end + + pw.last.sync = true + + pi = [pw.last, pr.first, pe.first] + + if b + begin + b[cid, *pi] + Process.waitpid2(cid).last + ensure + pi.each{|fd| fd.close unless fd.closed?} + end + else + [cid, pw.last, pr.first, pe.first] + end + end + + module_function :popen4 + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/mixin/create_path.rb b/chef/lib/chef/mixin/create_path.rb new file mode 100644 index 0000000000..dc9200eaf5 --- /dev/null +++ b/chef/lib/chef/mixin/create_path.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + module Mixin + module CreatePath + + # Creates a given path, including all directories that lead up to it. + # Like mkdir_p, but without the leaking. + # + # === Parameters + # file_path<String, Array>:: A string that represents the path to create, + # or an Array with the path-parts. + # + # === Returns + # The created file_path. + def create_path(file_path) + unless file_path.kind_of?(String) || file_path.kind_of?(Array) + raise ArgumentError, "file_path must be a string or an array!" + end + + if file_path.kind_of?(String) + file_path = File.expand_path(file_path).split(File::SEPARATOR) + file_path.shift if file_path[0] = '' + unless file_path[0].match("^#{File::SEPARATOR}") + file_path[0] = "#{File::SEPARATOR}#{file_path[0]}" + end + end + + file_path.each_index do |i| + create_path = File.join(file_path[0, i + 1]) + unless File.directory?(create_path) + Chef::Log.debug("Creating directory #{create_path}") + Dir.mkdir(create_path) + end + end + File.expand_path(File.join(file_path)) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/mixin/from_file.rb b/chef/lib/chef/mixin/from_file.rb new file mode 100644 index 0000000000..05f7fe8e8b --- /dev/null +++ b/chef/lib/chef/mixin/from_file.rb @@ -0,0 +1,36 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + module Mixin + module FromFile + + # Loads a given ruby file, and runs instance_eval against it in the context of the current + # object. + # + # Raises an IOError if the file cannot be found, or is not readable. + def from_file(filename) + if File.exists?(filename) && File.readable?(filename) + self.instance_eval(IO.read(filename), filename, 1) + else + raise IOError, "Cannot open or read #{filename}!" + end + end + end + end +end diff --git a/chef/lib/chef/mixin/generate_url.rb b/chef/lib/chef/mixin/generate_url.rb new file mode 100644 index 0000000000..3eb8c4f70d --- /dev/null +++ b/chef/lib/chef/mixin/generate_url.rb @@ -0,0 +1,46 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + module Mixin + module GenerateURL + + def generate_cookbook_url(url, cookbook, type, node, args=nil) + new_url = nil + if url =~ /^http/ + new_url = url + else + new_url = "cookbooks/#{cookbook}/#{type}?" + new_url += "id=#{url}" + platform, version = Chef::Platform.find_platform_and_version(node) + if type == "files" || type == "templates" + new_url += "&platform=#{platform}&version=#{version}&fqdn=#{node[:fqdn]}" + end + if args + args.each do |key, value| + new_url += "&#{key}=#{value}" + end + end + end + + return new_url + end + + end + end +end diff --git a/chef/lib/chef/mixin/params_validate.rb b/chef/lib/chef/mixin/params_validate.rb new file mode 100644 index 0000000000..4d8ddb74c3 --- /dev/null +++ b/chef/lib/chef/mixin/params_validate.rb @@ -0,0 +1,197 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Chef + module Mixin + module ParamsValidate + + # Takes a hash of options, along with a map to validate them. Returns the original + # options hash, plus any changes that might have been made (through things like setting + # default values in the validation map) + # + # For example: + # + # validate({ :one => "neat" }, { :one => { :kind_of => String }}) + # + # Would raise an exception if the value of :one above is not a kind_of? string. Valid + # map options are: + # + # :default:: Sets the default value for this parameter. + # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. + # The key will be inserted into the error message if the Proc does not return true: + # "Option #{key}'s value #{value} #{message}!" + # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure + # that the value is one of those types. + # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of + # method names. + # :required:: Raise an exception if this parameter is missing. Valid values are true or false, + # by default, options are not required. + # :regex:: Match the value of the paramater against a regular expression. + # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any + # of the values. + def validate(opts, map) + #-- + # validate works by taking the keys in the validation map, assuming it's a hash, and + # looking for _pv_:symbol as methods. Assuming it find them, it calls the right + # one. + #++ + raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) + raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) + + map.each do |key, validation| + unless key.kind_of?(Symbol) || key.kind_of?(String) + raise ArgumentError, "Validation map keys must be symbols or strings!" + end + case validation + when true + _pv_required(opts, key) + when false + true + when Hash + validation.each do |check, carg| + check_method = "_pv_#{check.to_s}" + if self.respond_to?(check_method, true) + self.send(check_method, opts, key, carg) + else + raise ArgumentError, "Validation map has unknown check: #{check}" + end + end + end + end + opts + end + + def set_or_return(symbol, arg, validation) + iv_symbol = "@#{symbol.to_s}".to_sym + map = { + symbol => validation + } + if arg == nil + self.instance_variable_get(iv_symbol) + else + validate({ symbol => arg }, { symbol => validation }) + self.instance_variable_set(iv_symbol, arg) + end + end + + private + + # Return the value of a parameter, or nil if it doesn't exist. + def _pv_opts_lookup(opts, key) + if opts.has_key?(key.to_s) + opts[key.to_s] + elsif opts.has_key?(key.to_sym) + opts[key.to_sym] + else + nil + end + end + + # Raise an exception if the parameter is not found. + def _pv_required(opts, key, is_required=true) + if is_required + if opts.has_key?(key.to_s) || opts.has_key?(key.to_sym) + true + else + raise ArgumentError, "Required argument #{key} is missing!" + end + end + end + + def _pv_equal_to(opts, key, to_be) + value = _pv_opts_lookup(opts, key) + if value != nil + passes = false + [ to_be ].flatten.each do |tb| + if value == tb + passes = true + end + end + unless passes + raise ArgumentError, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}." + end + end + end + + # Raise an exception if the parameter is not a kind_of?(to_be) + def _pv_kind_of(opts, key, to_be) + value = _pv_opts_lookup(opts, key) + if value != nil + passes = false + [ to_be ].flatten.each do |tb| + if value.kind_of?(tb) + passes = true + end + end + unless passes + raise ArgumentError, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}." + end + end + end + + # Raise an exception if the parameter does not respond to a given set of methods. + def _pv_respond_to(opts, key, method_name_list) + value = _pv_opts_lookup(opts, key) + if value != nil + [ method_name_list ].flatten.each do |method_name| + unless value.respond_to?(method_name) + raise ArgumentError, "Option #{key} must have a #{method_name} method!" + end + end + end + end + + # Assign a default value to a parameter. + def _pv_default(opts, key, default_value) + value = _pv_opts_lookup(opts, key) + if value == nil + opts[key] = default_value + end + end + + # Check a parameter against a regular expression. + def _pv_regex(opts, key, regex) + value = _pv_opts_lookup(opts, key) + passes = false + [ regex ].flatten.each do |r| + if value != nil + if r.match(value.to_s) + passes = true + end + end + end + unless passes + raise ArgumentError, "Option #{key}'s value #{value} does not match regular expression #{regex.to_s}" + end + end + + # Check a parameter against a hash of proc's. + def _pv_callbacks(opts, key, callbacks) + raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash) + value = _pv_opts_lookup(opts, key) + if value != nil + callbacks.each do |message, zeproc| + if zeproc.call(value) != true + raise ArgumentError, "Option #{key}'s value #{value} #{message}!" + end + end + end + end + end + end +end + diff --git a/chef/lib/chef/mixin/template.rb b/chef/lib/chef/mixin/template.rb new file mode 100644 index 0000000000..f3980922a1 --- /dev/null +++ b/chef/lib/chef/mixin/template.rb @@ -0,0 +1,39 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'tempfile' +require 'erubis' + +class Chef + module Mixin + module Template + + # Render a template with Erubis. Takes a template as a string, and a + # context hash. + def render_template(template, context) + eruby = Erubis::Eruby.new(template) + output = eruby.evaluate(context) + final_tempfile = Tempfile.new("chef-rendered-template") + final_tempfile.print(output) + final_tempfile.close + final_tempfile + end + + end + end +end diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb new file mode 100644 index 0000000000..e1c8ef2d9a --- /dev/null +++ b/chef/lib/chef/node.rb @@ -0,0 +1,260 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "check_helper") +require File.join(File.dirname(__FILE__), "mixin", "params_validate") +require File.join(File.dirname(__FILE__), "mixin", "from_file") + +require 'extlib' +require 'rubygems' +require 'json' + +class Chef + class Node + + attr_accessor :attribute, :recipe_list, :couchdb_rev + + include Chef::Mixin::CheckHelper + include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate + + DESIGN_DOCUMENT = { + "version" => 3, + "language" => "javascript", + "views" => { + "all" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "node") { + emit(doc.name, doc); + } + } + EOJS + }, + "all_id" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "node") { + emit(doc.name, doc.name); + } + } + EOJS + }, + }, + } + + # Create a new Chef::Node object. + def initialize() + @name = nil + @attribute = Mash.new + @recipe_list = Array.new + @couchdb_rev = nil + @couchdb = Chef::CouchDB.new + end + + # Find a recipe for this Chef::Node by fqdn. Will search first for + # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb. + # + # Returns a new Chef::Node object. + # + # Raises an ArgumentError if it cannot find the node. + def find_file(fqdn) + node_file = nil + host_parts = fqdn.split(".") + hostname = host_parts[0] + + if File.exists?(File.join(Chef::Config[:node_path], "#{fqdn}.rb")) + node_file = File.join(Chef::Config[:node_path], "#{fqdn}.rb") + elsif File.exists?(File.join(Chef::Config[:node_path], "#{hostname}.rb")) + node_file = File.join(Chef::Config[:node_path], "#{hostname}.rb") + elsif File.exists?(File.join(Chef::Config[:node_path], "default.rb")) + node_file = File.join(Chef::Config[:node_path], "default.rb") + end + unless node_file + raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!" + end + self.from_file(node_file) + end + + # Set the name of this Node, or return the current name. + def name(arg=nil) + if arg != nil + validate( + { :name => arg }, + { + :name => { + :kind_of => String + } + } + ) + @name = arg + else + @name + end + end + + # Return an attribute of this node. Returns nil if the attribute is not found. + def [](attrib) + if @attribute.has_key?(attrib) + @attribute[attrib] + elsif @attribute.has_key?(attrib.to_s) + @attribute[attrib.to_s] + else + nil + end + end + + # Set an attribute of this node + def []=(attrib, value) + @attribute[attrib] = value + end + + # Yield each key to the block + def each(&block) + @attribute.each_key do |k| + yield(k) + end + end + + # Iterates over each attribute, passing the attribute and value to the block. + def each_attribute(&block) + @attribute.each do |k,v| + yield(k, v) + end + end + + # Return true if this Node has a given attribute, false if not. Takes either a symbol or + # a string. + def attribute?(attrib) + result = false + result = @attribute.has_key?(attrib) + return result if result + return @attribute.has_key?(attrib.to_sym) + end + + # Returns true if this Node expects a given recipe, false if not. + def recipe?(recipe_name) + @recipe_list.detect { |r| r == recipe_name } ? true : false + end + + # Returns an Array of recipes. If you call it with arguments, they will become the new + # list of recipes. + def recipes(*args) + if args.length > 0 + @recipe_list = args.flatten + else + @recipe_list + end + end + + # Set an attribute based on the missing method. If you pass an argument, we'll use that + # to set the attribute values. Otherwise, we'll wind up just returning the attributes + # value. + def method_missing(symbol, *args) + if args.length != 0 + @attribute[symbol] = args.length == 1 ? args[0] : args + else + if @attribute.has_key?(symbol) + @attribute[symbol] + else + raise ArgumentError, "Attribute #{symbol.to_s} is not defined!" + end + end + end + + def to_index + index_hash = { + :index_name => "node", + :id => "node_#{@name}", + :name => @name, + } + @attribute.each do |key, value| + index_hash[key] = value + end + index_hash[:recipe] = @recipe_list if @recipe_list.length > 0 + index_hash + end + + # Serialize this object as a hash + def to_json(*a) + result = { + "name" => @name, + 'json_class' => self.class.name, + "attributes" => @attribute, + "chef_type" => "node", + "recipes" => @recipe_list, + } + result["_rev"] = @couchdb_rev if @couchdb_rev + result.to_json(*a) + end + + # Create a Chef::Node from JSON + def self.json_create(o) + node = new + node.name(o["name"]) + o["attributes"].each do |k,v| + node[k] = v + end + o["recipes"].each do |r| + node.recipes << r + end + node.couchdb_rev = o["_rev"] if o.has_key?("_rev") + node + end + + # List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get + # the full list of all Nodes, fully inflated. + def self.list(inflate=false) + rs = Chef::CouchDB.new.list("nodes", inflate) + if inflate + rs["rows"].collect { |r| r["value"] } + else + rs["rows"].collect { |r| r["key"] } + end + end + + # Load a node by name from CouchDB + def self.load(name) + Chef::CouchDB.new.load("node", name) + end + + # Remove this node from the CouchDB + def destroy + Chef::Queue.send_msg(:queue, :remove, self) + @couchdb.delete("node", @name, @couchdb_rev) + end + + # Save this node to the CouchDB + def save + Chef::Queue.send_msg(:queue, :index, self) + results = @couchdb.store("node", @name, self) + @couchdb_rev = results["rev"] + end + + # Set up our CouchDB design document + def self.create_design_document + Chef::CouchDB.new.create_design_document("nodes", DESIGN_DOCUMENT) + end + + # As a string + def to_s + "node[#{@name}]" + end + + end +end diff --git a/chef/lib/chef/openid_registration.rb b/chef/lib/chef/openid_registration.rb new file mode 100644 index 0000000000..748c865097 --- /dev/null +++ b/chef/lib/chef/openid_registration.rb @@ -0,0 +1,175 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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' + +class Chef + class OpenIDRegistration + + attr_accessor :name, :salt, :validated, :password, :couchdb_rev + + include Chef::Mixin::ParamsValidate + + DESIGN_DOCUMENT = { + "version" => 3, + "language" => "javascript", + "views" => { + "all" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "openid_registration") { + emit(doc.name, doc); + } + } + EOJS + }, + "all_id" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "openid_registration") { + emit(doc.name, doc.name); + } + } + EOJS + }, + "validated" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "openid_registration") { + if (doc.validated == true) { + emit(doc.name, doc); + } + } + } + EOJS + }, + "unvalidated" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "openid_registration") { + if (doc.validated == false) { + emit(doc.name, doc); + } + } + } + EOJS + }, + }, + } + + # Create a new Chef::OpenIDRegistration object. + def initialize() + @name = nil + @salt = nil + @password = nil + @validated = false + @couchdb_rev = nil + @couchdb = Chef::CouchDB.new + end + + def name=(n) + @name = n.gsub(/\./, '_') + end + + # Set the password for this object. + def set_password(password) + @salt = generate_salt + @password = encrypt_password(@salt, password) + end + + # Serialize this object as a hash + def to_json(*a) + attributes = Hash.new + recipes = Array.new + result = { + 'name' => @name, + 'json_class' => self.class.name, + 'salt' => @salt, + 'password' => @password, + 'validated' => @validated, + 'chef_type' => 'openid_registration', + } + result["_rev"] = @couchdb_rev if @couchdb_rev + result.to_json(*a) + end + + # Create a Chef::Node from JSON + def self.json_create(o) + me = new + me.name = o["name"] + me.salt = o["salt"] + me.password = o["password"] + me.validated = o["validated"] + me.couchdb_rev = o["_rev"] if o.has_key?("_rev") + me + end + + # List all the Chef::OpenIDRegistration objects in the CouchDB. If inflate is set to true, you will get + # the full list of all registration objects. Otherwise, you'll just get the IDs + def self.list(inflate=false) + rs = Chef::CouchDB.new.list("registrations", inflate) + if inflate + rs["rows"].collect { |r| r["value"] } + else + rs["rows"].collect { |r| r["key"] } + end + end + + # Load an OpenIDRegistration by name from CouchDB + def self.load(name) + Chef::CouchDB.new.load("openid_registration", name) + end + + # Whether or not there is an OpenID Registration with this key. + def self.has_key?(name) + Chef::CouchDB.new.has_key?("openid_registration", name) + end + + # Remove this node from the CouchDB + def destroy + @couchdb.delete("openid_registration", @name, @couchdb_rev) + end + + # Save this node to the CouchDB + def save + results = @couchdb.store("openid_registration", @name, self) + @couchdb_rev = results["rev"] + end + + # Set up our CouchDB design document + def self.create_design_document + Chef::CouchDB.new.create_design_document("registrations", DESIGN_DOCUMENT) + end + + protected + + def generate_salt + salt = Time.now.to_s + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + 1.upto(30) { |i| salt << chars[rand(chars.size-1)] } + salt + end + + def encrypt_password(salt, password) + Digest::SHA1.hexdigest("--#{salt}--#{password}--") + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb new file mode 100644 index 0000000000..c7a7be1236 --- /dev/null +++ b/chef/lib/chef/platform.rb @@ -0,0 +1,202 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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. +# + +Dir[File.join(File.dirname(__FILE__), 'provider/**/*.rb')].sort.each { |lib| require lib } +require File.join(File.dirname(__FILE__), 'mixin', 'params_validate') + +class Chef + class Platform + + @platforms = { + :mac_os_x => {}, + :ubuntu => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + } + }, + :centos => {}, + :redhat => {}, + :gentoo => { + :default => { + :package => Chef::Provider::Package::Portage + } + }, + :solaris => {}, + :default => { + :file => Chef::Provider::File, + :directory => Chef::Provider::Directory, + :link => Chef::Provider::Link, + :template => Chef::Provider::Template, + :remote_file => Chef::Provider::RemoteFile, + :remote_directory => Chef::Provider::RemoteDirectory, + :sysctl => Chef::Provider::Sysctl, + :execute => Chef::Provider::Execute, + :script => Chef::Provider::Script, + :service => Chef::Provider::Service::Init, + :perl => Chef::Provider::Script, + :python => Chef::Provider::Script, + :ruby => Chef::Provider::Script, + :bash => Chef::Provider::Script, + :csh => Chef::Provider::Script, + :user => Chef::Provider::User::Useradd, + } + } + + class << self + attr_accessor :platforms + + include Chef::Mixin::ParamsValidate + + def find(name, version) + provider_map = @platforms[:default].clone + + name_sym = name + if name.kind_of?(String) + name.downcase! + name.gsub!(/\s/, "_") + name_sym = name.to_sym + end + + if @platforms.has_key?(name_sym) + if @platforms[name_sym].has_key?(version) + Chef::Log.debug("Platform #{name.to_s} version #{version} found") + if @platforms[name_sym].has_key?(:default) + provider_map.merge!(@platforms[name_sym][:default]) + end + provider_map.merge!(@platforms[name_sym][version]) + elsif @platforms[name_sym].has_key?(:default) + provider_map.merge!(@platforms[name_sym][:default]) + end + else + Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") + end + provider_map + end + + def find_provider(platform, version, resource_type) + pmap = Chef::Platform.find(platform, version) + rtkey = resource_type + if resource_type.kind_of?(Chef::Resource) + rtkey = resource_type.resource_name.to_sym + end + if pmap.has_key?(rtkey) + pmap[rtkey] + else + Chef::Log.error("#{rtkey.inspect} #{pmap.inspect}") + raise( + ArgumentError, + "Cannot find a provider for #{resource_type} on #{platform} version #{version}" + ) + end + end + + def find_platform_and_version(node) + platform = nil + version = nil + if node.attribute?("lsbdistid") + platform = node[:lsbdistid] + elsif node.attribute?("macosx_productname") + platform = node[:macosx_productname] + elsif node.attribute?("operatingsystem") + platform = node[:operatingsystem] + end + raise ArgumentError, "Cannot find a platform for #{node}" unless platform + + if node.attribute?("lsbdistrelease") + version = node[:lsbdistrelease] + elsif node.attribute?("macosx_productversion") + version = node[:macosx_productversion] + elsif node.attribute?("operatingsystemversion") + version = node[:operatingsystemversion] + elsif node.attribute?("operatingsystemrelease") + version = node[:operatingsystemrelease] + end + raise ArgumentError, "Cannot find a version for #{node}" unless version + + return platform, version + end + + def find_provider_for_node(node, resource_type) + platform, version = find_platform_and_version(node) + provider = find_provider(platform, version, resource_type) + end + + def set(args) + validate( + args, + { + :platform => { + :kind_of => Symbol, + :required => false, + }, + :version => { + :kind_of => String, + :required => false, + }, + :resource => { + :kind_of => Symbol, + }, + :provider => { + :kind_of => [ String, Symbol, Class ], + } + } + ) + if args.has_key?(:platform) + if args.has_key?(:version) + if @platforms.has_key?(args[:platform]) + if @platforms[args[:platform]].has_key?(args[:version]) + @platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] + else + @platforms[args[:platform]][args[:version]] = { + args[:resource].to_sym => args[:provider] + } + end + else + @platforms[args[:platform]] = { + args[:version] => { + args[:resource].to_sym => args[:provider] + } + } + end + else + if @platforms.has_key?(args[:platform]) + @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + else + @platforms[args[:platform]] = { + :default => { + args[:resource].to_sym => args[:provider] + } + } + end + end + else + if @platforms.has_key?(:default) + @platforms[:default][args[:resource].to_sym] = args[:provider] + else + @platforms[:default] = { + args[:resource].to_sym => args[:provider] + } + end + end + end + + end + + end +end diff --git a/chef/lib/chef/provider.rb b/chef/lib/chef/provider.rb new file mode 100644 index 0000000000..3251693cb3 --- /dev/null +++ b/chef/lib/chef/provider.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Provider + + attr_accessor :node, :new_resource, :current_resource + + def initialize(node, new_resource) + @node = node + @new_resource = new_resource + @current_resource = nil + end + + def load_current_resource + raise Chef::Exception::Override, "You must override load_current_resource in #{self.to_s}" + end + + def action_nothing + Chef::Log.debug("Doing nothing for #{@new_resource.to_s}") + true + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/directory.rb b/chef/lib/chef/provider/directory.rb new file mode 100644 index 0000000000..d042b6a8eb --- /dev/null +++ b/chef/lib/chef/provider/directory.rb @@ -0,0 +1,68 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "file") +require "fileutils" + +class Chef + class Provider + class Directory < Chef::Provider::File + def load_current_resource + @current_resource = Chef::Resource::Directory.new(@new_resource.name) + @current_resource.path(@new_resource.path) + if ::File.exist?(@current_resource.path) && ::File.directory?(@current_resource.path) + cstats = ::File.stat(@current_resource.path) + @current_resource.owner(cstats.uid) + @current_resource.group(cstats.gid) + @current_resource.mode("%o" % (cstats.mode & 007777)) + end + @current_resource + end + + def action_create + unless ::File.exists?(@new_resource.path) + Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") + if @new_resource.recursive == true + ::FileUtils.mkdir_p(@new_resource.path) + else + ::Dir.mkdir(@new_resource.path) + end + @new_resource.updated = true + end + set_owner if @new_resource.owner != nil + set_group if @new_resource.group != nil + set_mode if @new_resource.mode != nil + end + + def action_delete + if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path) + if @new_resource.recursive == true + Chef::Log.info("Deleting #{@new_resource} recursively at #{@new_resource.path}") + FileUtils.rm_rf(@new_resource.path) + else + Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.path}") + ::Dir.delete(@new_resource.path) + end + @new_resource.updated = true + else + raise RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource_path}!" if ::File.exists?(@new_resource.path) + end + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/execute.rb b/chef/lib/chef/provider/execute.rb new file mode 100644 index 0000000000..67a706e4f1 --- /dev/null +++ b/chef/lib/chef/provider/execute.rb @@ -0,0 +1,55 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "mixin", "command") + +class Chef + class Provider + class Execute < Chef::Provider + + include Chef::Mixin::Command + + def load_current_resource + true + end + + def action_run + command_args = { + :command => @new_resource.command, + :command_string => @new_resource.to_s, + } + command_args[:creates] = @new_resource.creates if @new_resource.creates + command_args[:onlyif] = @new_resource.onlyif if @new_resource.onlyif + command_args[:not_if] = @new_resource.not_if if @new_resource.not_if + command_args[:timeout] = @new_resource.timeout if @new_resource.timeout + command_args[:returns] = @new_resource.returns if @new_resource.returns + command_args[:environment] = @new_resource.environment if @new_resource.environment + command_args[:user] = @new_resource.user if @new_resource.user + command_args[:group] = @new_resource.group if @new_resource.group + command_args[:cwd] = @new_resource.cwd if @new_resource.cwd + + status = run_command(command_args) + if status + @new_resource.updated = true + Chef::Log.info("Ran #{@new_resource} successfully") + end + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/file.rb b/chef/lib/chef/provider/file.rb new file mode 100644 index 0000000000..d25e5decca --- /dev/null +++ b/chef/lib/chef/provider/file.rb @@ -0,0 +1,169 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'etc' +require 'fileutils' +require File.join(File.dirname(__FILE__), "..", "mixin", "checksum") +require File.join(File.dirname(__FILE__), "..", "mixin", "generate_url") + +class Chef + class Provider + class File < Chef::Provider + include Chef::Mixin::Checksum + include Chef::Mixin::GenerateURL + + def load_current_resource + @current_resource = Chef::Resource::File.new(@new_resource.name) + @current_resource.path(@new_resource.path) + if ::File.exist?(@current_resource.path) && ::File.readable?(@current_resource.path) + cstats = ::File.stat(@current_resource.path) + @current_resource.owner(cstats.uid) + @current_resource.group(cstats.gid) + @current_resource.mode("%o" % (cstats.mode & 007777)) + @current_resource.checksum(checksum(@current_resource.path)) + end + @current_resource + end + + # Compare the ownership of a file. Returns true if they are the same, false if they are not. + def compare_owner + if @new_resource.owner != nil + case @new_resource.owner + when /^\d+$/, Integer + @set_user_id = @new_resource.owner.to_i + @set_user_id == @current_resource.owner + else + # This raises an ArugmentError if you can't find the user + user_info = Etc.getpwnam(@new_resource.owner) + @set_user_id = user_info.uid + @set_user_id == @current_resource.owner + end + end + end + + # Set the ownership on the file, assuming it is not set correctly already. + def set_owner + unless compare_owner + Chef::Log.info("Setting owner to #{@set_user_id} for #{@new_resource}") + ::File.chown(@set_user_id, nil, @new_resource.path) + @new_resource.updated = true + end + end + + # Compares the group of a file. Returns true if they are the same, false if they are not. + def compare_group + if @new_resource.group != nil + case @new_resource.group + when /^\d+$/, Integer + @set_group_id = @new_resource.group.to_i + @set_group_id == @current_resource.group + else + group_info = Etc.getgrnam(@new_resource.group) + @set_group_id = group_info.gid + @set_group_id == @current_resource.group + end + end + end + + def set_group + unless compare_group + Chef::Log.info("Setting group to #{@set_group_id} for #{@new_resource}") + ::File.chown(nil, @set_group_id, @new_resource.path) + @new_resource.updated = true + end + end + + def compare_mode + if @new_resource.mode != nil + case @new_resource.mode + when /^\d+$/, Integer + real_mode = sprintf("%o" % (@new_resource.mode & 007777)) + real_mode.to_i == @current_resource.mode.to_i + end + end + end + + def set_mode + unless compare_mode && @new_resource.mode != nil + Chef::Log.info("Setting mode to #{sprintf("%o" % (@new_resource.mode & 007777)) + } for #{@new_resource}") + ::File.chmod(@new_resource.mode.to_i, @new_resource.path) + @new_resource.updated = true + end + end + + def action_create + unless ::File.exists?(@new_resource.path) + Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") + ::File.open(@new_resource.path, "w+") { |f| } + @new_resource.updated = true + end + set_owner if @new_resource.owner != nil + set_group if @new_resource.group != nil + set_mode if @new_resource.mode != nil + end + + def action_delete + if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path) + backup + Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.path}") + ::File.delete(@new_resource.path) + @new_resource.updated = true + else + raise "Cannot delete #{@new_resource} at #{@new_resource_path}!" + end + end + + def action_touch + action_create + time = Time.now + Chef::Log.info("Updating #{@new_resource} with new atime/mtime of #{time}") + ::File.utime(time, time, @new_resource.path) + @new_resource.updated = true + end + + def backup(file=nil) + file ||= @new_resource.path + if @new_resource.backup && ::File.exist?(file) + time = Time.now + savetime = time.strftime("%Y%m%d%H%M%S") + backup_filename = "#{@new_resource.path}.chef-#{savetime}" + Chef::Log.info("Backing up #{@new_resource} to #{backup_filename}") + FileUtils.cp(file, backup_filename) + + # Clean up after the number of backups + slice_number = @new_resource.backup - 1 + backup_files = Dir["#{@new_resource.path}.chef-*"].sort { |a,b| b <=> a } + if backup_files.length >= @new_resource.backup + remainder = backup_files.slice(slice_number..-1) + remainder.each do |backup_to_delete| + Chef::Log.info("Removing backup of #{@new_resource} at #{backup_to_delete}") + FileUtils.rm(backup_to_delete) + end + end + + end + end + + def generate_url(url, type, args=nil) + generate_cookbook_url(url, @new_resource.cookbook_name, type, @node, args) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/link.rb b/chef/lib/chef/provider/link.rb new file mode 100644 index 0000000000..843a4ad009 --- /dev/null +++ b/chef/lib/chef/provider/link.rb @@ -0,0 +1,71 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Provider + class Link < Chef::Provider + def load_current_resource + @current_resource = Chef::Resource::Link.new(@new_resource.name) + @current_resource.target_file(@new_resource.target_file) + @current_resource.link_type(@new_resource.link_type) + if @new_resource.link_type == :symbolic + if ::File.exists?(@current_resource.target_file) && ::File.symlink?(@current_resource.target_file) + @current_resource.source_file( + ::File.expand_path(::File.readlink(@current_resource.target_file)) + ) + else + @current_resource.source_file("") + end + elsif @new_resource.link_type == :hard + if ::File.exists?(@current_resource.target_file) && ::File.exists?(@new_resource.source_file) + if ::File.stat(@current_resource.target_file).ino == ::File.stat(@new_resource.source_file).ino + @current_resource.source_file(@new_resource.source_file) + else + @current_resource.source_file("") + end + else + @current_resource.source_file("") + end + end + @current_resource + end + + def action_create + if @current_resource.source_file != @new_resource.source_file + Chef::Log.info("Creating a #{@new_resource.link_type} link from #{@new_resource.source_file} -> #{@new_resource.target_file} for #{@new_resource}") + if @new_resource.link_type == :symbolic + ::File.symlink(@new_resource.source_file, @new_resource.target_file) + elsif @new_resource.link_type == :hard + ::File.link(@new_resource.source_file, @new_resource.target_file) + end + @new_resource.updated = true + end + end + + def action_delete + if ::File.exists?(@new_resource.target_file) && ::File.writable?(@new_resource.target_file) + Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.target_file}") + ::File.delete(@new_resource.target_file) + @new_resource.updated = true + else + raise "Cannot delete #{@new_resource} at #{@new_resource_path}!" + end + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/package.rb b/chef/lib/chef/provider/package.rb new file mode 100644 index 0000000000..a68c2e88f2 --- /dev/null +++ b/chef/lib/chef/provider/package.rb @@ -0,0 +1,105 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "mixin", "command") + +class Chef + class Provider + class Package < Chef::Provider + + include Chef::Mixin::Command + + def initialize(node, new_resource) + super(node, new_resource) + @candidate_version = nil + end + + def action_install + # First, select what version we should be using + install_version = @new_resource.version + install_version ||= @candidate_version + + unless install_version + raise(Chef::Exception::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} successfully") + status = install_package(@new_resource.package_name, install_version) + if status + @new_resource.updated = true + end + end + end + + def action_upgrade + if @current_resource.version != @candidate_version + Chef::Log.info("Upgrading #{@new_resource} version from #{@current_resource.version} to #{@candidate_version} successfully") + status = install_package(@new_resource.package_name, @candidate_version) + if status + @new_resource.updated = true + end + end + end + + def action_remove + if @current_resource.version != nil + Chef::Log.info("Removing #{@new_resource} successfully") + remove_package(@new_resource.package_name, @new_resource.version) + @new_resource.updated = true + end + end + + def action_purge + if @current_resource.version != nil + Chef::Log.info("Purging #{@new_resource} successfully") + purge_package(@new_resource.package_name, @new_resource.version) + @new_resource.updated = true + end + end + + def install_package(name, version) + raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :install" + end + + def upgrade_package(name, version) + raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :upgrade" + end + + def remove_package(name, version) + raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :remove" + end + + def purge_package(name, version) + raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :purge" + end + + end + end +end diff --git a/chef/lib/chef/provider/package/apt.rb b/chef/lib/chef/provider/package/apt.rb new file mode 100644 index 0000000000..a2e9bd68a2 --- /dev/null +++ b/chef/lib/chef/provider/package/apt.rb @@ -0,0 +1,89 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "package") +require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command") + +class Chef + class Provider + class Package + class Apt < Chef::Provider::Package + + def load_current_resource + @current_resource = Chef::Resource::Package.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + + status = popen4("apt-cache policy #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| + stdin.close + stdout.each do |line| + case line + when /^\s{2}Installed: (.+)$/ + installed_version = $1 + if installed_version == '(none)' + @current_resource.version(nil) + else + @current_resource.version(installed_version) + end + when /^\s{2}Candidate: (.+)$/ + @candidate_version = $1 + end + end + end + + unless status.exitstatus == 0 + raise Chef::Exception::Package, "apt-cache failed - #{status.inspect}!" + end + + @current_resource + end + + def install_package(name, version) + run_command( + :command => "apt-get -q -y install #{name}=#{version}", + :environment => { + "DEBIAN_FRONTEND" => "noninteractive" + } + ) + end + + def upgrade_package(name, version) + install_package(name, version) + end + + def remove_package(name, version) + run_command( + :command => "apt-get -q -y remove #{@new_resource.package_name}", + :environment => { + "DEBIAN_FRONTEND" => "noninteractive" + } + ) + end + + def purge_package(name, version) + run_command( + :command => "apt-get -q -y purge #{@new_resource.package_name}", + :environment => { + "DEBIAN_FRONTEND" => "noninteractive" + } + ) + end + + end + end + end +end diff --git a/chef/lib/chef/provider/package/portage.rb b/chef/lib/chef/provider/package/portage.rb new file mode 100644 index 0000000000..8981a386bc --- /dev/null +++ b/chef/lib/chef/provider/package/portage.rb @@ -0,0 +1,93 @@ +# +# Author:: Ezra Zygmuntowicz (<ezra@engineyard.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "package") +require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command") + +class Chef + class Provider + class Package + class Portage < Chef::Provider::Package + + 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}") do |pid, stdin, stdout, stderr| + stdin.close + + 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::Exception::Package, "emerge --search failed - #{status.inspect}!" + end + + @current_resource + end + + + def parse_emerge(package, txt) + available, installed, pkg = nil + txt.each do |line| + if line =~ /\*(.*)/ + pkg = $1.strip.split('/').last + end + if pkg == package + if line =~ /Latest version available: (.*)/ + available = $1 + elsif line =~ /Latest version installed: (.*)/ + installed = $1 + end + end + end + [available, installed] + end + + + def install_package(name, version) + run_command( + :command => "emerge -g --color n --nospinner --quiet =#{name}-#{version}" + ) + end + + def upgrade_package(name, version) + install_package(name, version) + end + + def remove_package(name, version) + run_command( + :command => "emerge --unmerge --color n --nospinner --quiet #{@new_resource.package_name}" + ) + end + + def purge_package(name, version) + remove_package(name, version) + end + + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb new file mode 100644 index 0000000000..69459ffef4 --- /dev/null +++ b/chef/lib/chef/provider/package/rubygems.rb @@ -0,0 +1,116 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "package") +require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command") + +class Chef + class Provider + class Package + class Rubygems < Chef::Provider::Package + + def gem_list_parse(line) + installed_versions = Array.new + if line.match("^#{@new_resource.package_name} \\((.+?)\\)$") + installed_versions = $1.split(/, /) + installed_versions + else + nil + end + end + + def load_current_resource + @current_resource = Chef::Resource::Package.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @current_resource.version(nil) + + # First, we need to look up whether we have the local gem installed or not + status = popen4("gem list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| + stdin.close + stdout.each do |line| + installed_versions = gem_list_parse(line) + next unless installed_versions + # If the version we are asking for is installed, make that our current + # version. Otherwise, go ahead and use the highest one, which + # happens to come first in the array. + if installed_versions.detect { |v| v == @new_resource.version } + Chef::Log.debug("#{@new_resource.package_name} at version #{@new_resource.version}") + @current_resource.version(@new_resource.version) + else + iv = installed_versions.first + Chef::Log.debug("#{@new_resource.package_name} at version #{iv}") + @current_resource.version(iv) + end + end + end + + unless status.exitstatus == 0 + raise Chef::Exception::Package, "gem list --local failed - #{status.inspect}!" + end + + status = popen4("gem list --remote #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| + stdin.close + stdout.each do |line| + installed_versions = gem_list_parse(line) + next unless installed_versions + Chef::Log.debug("I have #{installed_versions.inspect}") + + if installed_versions.length >= 1 + Chef::Log.debug("Setting candidate version") + @candidate_version = installed_versions.first + end + end + end + + unless status.exitstatus == 0 + raise Chef::Exception::Package, "gem list --remote failed - #{status.inspect}!" + end + + @current_resource + end + + def install_package(name, version) + run_command( + :command => "gem install #{name} -q --no-rdoc --no-ri -v #{version}" + ) + end + + def upgrade_package(name, version) + install_package(name, version) + end + + def remove_package(name, version) + if version + run_command( + :command => "gem uninstall #{name} -q -v #{version}" + ) + else + run_command( + :command => "gem uninstall #{name} -q -a" + ) + end + end + + def purge_package(name, version) + remove_package(name, version) + end + + end + end + end +end diff --git a/chef/lib/chef/provider/remote_directory.rb b/chef/lib/chef/provider/remote_directory.rb new file mode 100644 index 0000000000..47d62b6308 --- /dev/null +++ b/chef/lib/chef/provider/remote_directory.rb @@ -0,0 +1,78 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "file") +require 'uri' +require 'tempfile' +require 'net/https' + +class Chef + class Provider + class RemoteDirectory < Chef::Provider::Directory + + def action_create + super + + @remote_file_list = Hash.new + do_recursive + end + + def do_recursive + 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_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_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 +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/remote_file.rb b/chef/lib/chef/provider/remote_file.rb new file mode 100644 index 0000000000..cc0812346e --- /dev/null +++ b/chef/lib/chef/provider/remote_file.rb @@ -0,0 +1,80 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "file") +require 'uri' +require 'tempfile' +require 'net/https' + +class Chef + class Provider + class RemoteFile < Chef::Provider::File + + def action_create + Chef::Log.debug("Checking #{@new_resource} for changes") + do_remote_file(@new_resource.source, @current_resource.path) + end + + def do_remote_file(source, path) + r = Chef::REST.new(Chef::Config[:remotefile_url]) + + current_checksum = nil + current_checksum = self.checksum(path) if ::File.exists?(path) + + url = generate_url( + source, + "files", + { + :checksum => current_checksum + } + ) + + raw_file = nil + begin + raw_file = r.get_rest(url, true) + rescue Net::HTTPRetriableError => e + if e.response.kind_of?(Net::HTTPNotModified) + Chef::Log.debug("File #{path} is unchanged") + return false + else + raise e + end + end + + raw_file_checksum = self.checksum(raw_file.path) + + if ::File.exists?(path) + Chef::Log.debug("#{path} changed from #{current_checksum} to #{raw_file_checksum}") + Chef::Log.info("Updating file for #{@new_resource} at #{path}") + else + Chef::Log.info("Creating file for #{@new_resource} at #{path}") + end + + backup(path) + FileUtils.cp(raw_file.path, path) + @new_resource.updated = true + + set_owner if @new_resource.owner != nil + set_group if @new_resource.group != nil + set_mode if @new_resource.mode != nil + return true + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/script.rb b/chef/lib/chef/provider/script.rb new file mode 100644 index 0000000000..4669361142 --- /dev/null +++ b/chef/lib/chef/provider/script.rb @@ -0,0 +1,35 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'tempfile' + +class Chef + class Provider + class Script < Chef::Provider::Execute + + def action_run + tf = Tempfile.new("chef-script") + tf.puts(@new_resource.code) + tf.close + @new_resource.command("#{@new_resource.interpreter} #{tf.path}") + super + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/service.rb b/chef/lib/chef/provider/service.rb new file mode 100644 index 0000000000..5ac53478e3 --- /dev/null +++ b/chef/lib/chef/provider/service.rb @@ -0,0 +1,86 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "mixin", "command") + +class Chef + class Provider + class Service < Chef::Provider + + include Chef::Mixin::Command + + def initialize(node, new_resource) + super(node, new_resource) + @enabled = nil + end + + def action_enable + if @current_resource.enabled == false + Chef::Log.debug("#{@new_resource}: attempting to enable") + status = enable_service(@new_resource.service_name) + if status + @new_resource.enabled == true + Chef::Log.info("#{@new_resource}: enabled succesfully") + end + else + Chef::Log.debug("#{@new_resource}: not enabling, already enabled") + end + end + + def action_disable + if @current_resource.enabled == true + Chef::Log.debug("#{@new_resource}: attempting to disable") + status = disable_service(@new_resource.service_name) + if status + @new_resource.enabled == false + Chef::Log.info("#{@new_resource}: disabled succesfully") + end + else + Chef::Log.debug("#{@new_resource}: not disabling, already disabled") + end + end + + def action_start + if @current_resource.running == false + Chef::Log.debug("#{@new_resource}: attempting to start") + status = start_service(@new_resource.service_name) + if status + @new_resource.running == true + Chef::Log.info("Started service #{@new_resource} succesfully") + end + else + Chef::Log.debug("#{@new_resource}: not starting, already running") + end + end + + def action_stop + if @current_resource.running == true + Chef::Log.debug("#{@new_resource}: attempting to stop") + status = stop_service(@new_resource.service_name) + if status + @new_resource.running == false + Chef::Log.info("#{@new_resource}: stopped succesfully") + end + else + Chef::Log.debug("#{@new_resource}: not stopping, already stopped") + end + end + + end + end +end diff --git a/chef/lib/chef/provider/service/debian.rb b/chef/lib/chef/provider/service/debian.rb new file mode 100644 index 0000000000..21f166ba6a --- /dev/null +++ b/chef/lib/chef/provider/service/debian.rb @@ -0,0 +1,53 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "service") +require File.join(File.dirname(__FILE__), "init") +require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command") + +class Chef + class Provider + class Service + class Debian < Chef::Provider::Service::Init + def load_current_resource + super + + status = popen4("update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr| + stdin.close + stdout.gets(nil) =~ /etc\/rc[\dS].d\/S|not installed/i ? @current_resource.enabled(true) : @current_resource.enabled(false) + end + + unless status.exitstatus == 0 + raise Chef::Exception::Service, "update-rc.d -n -f #{@current_resource.service_name} failed - #{status.inspect}" + end + + @current_resource + end + + def enable_service(name) + run_command(:command => "update-rc.d #{name} defaults") + end + + def disable_service(name) + run_command(:command => "update-rc.d -f #{name} remove") + end + + end + end + end +end diff --git a/chef/lib/chef/provider/service/init.rb b/chef/lib/chef/provider/service/init.rb new file mode 100644 index 0000000000..0ccfc232d8 --- /dev/null +++ b/chef/lib/chef/provider/service/init.rb @@ -0,0 +1,95 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "service") +require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command") + +class Chef + class Provider + class Service + class Init < Chef::Provider::Service + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + process_running = false + if @new_resource.supports[:status] + run_command(:command => "/etc/init.d/#{@current_resource.service_name} status") == 0 ? process_running = true : process_running = false + elsif @new_resource.status_command + run_command(:command => @new_resource.status_command) == 0 ? process_running = true : process_running = false + else + Chef::Log.debug("#{@new_resource} does not support status and you have not specified a status command, falling back to process table inspection") + if @new_resource.pattern == @new_resource.service_name + Chef::Log.debug("#{@new_resource} defaulting pattern to #{Regex.new(@new_resource.pattern)}") + elsif @node[:ps] == "" + raise Chef::Exception::Service, "#{@new_resource}: Facter could not determine how to call `ps` on your system (#{Facter["ps"].value})" + end + + process_pid = nil + status = popen4(@node[:ps]) do |pid, stdin, stdout, stderr| + stdin.close + r = Regexp.new(@new_resource.pattern) + Chef::Log.debug("#{@new_resource}: attempting to match #{@new_resource.pattern} (#{r}) against process table") + stdout.each_line do |line| + if r.match(line) + process_pid = line.sub(/^\s+/, '').split(/\s+/)[1] + end + end + end + unless status.exitstatus == 0 + raise Chef::Exception::Service, "Command #{@node[:ps]} failed" + else + process_pid ? process_running = true : process_running = false + Chef::Log.debug("#{@new_resource}: #{@node[:ps]} exited succesfully, process_running: #{process_running}") + end + end + @current_resource.running process_running + @current_resource + end + + def start_service(name) + if @new_resource.start_command + run_command(:command => @new_resource.start_command) + else + run_command(:command => "/etc/init.d/#{name} start") + end + end + + def stop_service(name) + if @new_resource.stop_command + run_command(:command => @new_resource.stop_command) + else + run_command(:command => "/etc/init.d/#{name} stop") + end + end + + def restart_service(name) + if @new_resource.supports[:restart] + run_command(:command => "/etc/init.d/#{name} restart") + elsif @new_resource.restart_command + run_command(:command => @new_resource.restart_command) + else + stop_service + start_service + end + end + + end + end + end +end diff --git a/chef/lib/chef/provider/sysctl.rb b/chef/lib/chef/provider/sysctl.rb new file mode 100644 index 0000000000..09b5942f5e --- /dev/null +++ b/chef/lib/chef/provider/sysctl.rb @@ -0,0 +1,38 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "file") +require "fileutils" + +class Chef + class Provider + class Sysctl < Chef::Provider + def load_current_resource + @current_resource = Chef::Resource::Sysctl.new(@new_resource.name) + @current_resource.value(`/sbin/sysctl #{@new_resource.name}`.chomp) + @current_resource + end + + def action_set + if @current_resource.value != @new_resource.value + system("/sbin/sysctl #{@new_resource.name}=#{@new_resource.value}") + end + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/template.rb b/chef/lib/chef/provider/template.rb new file mode 100644 index 0000000000..550a1388bf --- /dev/null +++ b/chef/lib/chef/provider/template.rb @@ -0,0 +1,69 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "file") +require File.join(File.dirname(__FILE__), "..", "mixin", "template") +require 'uri' +require 'tempfile' + +class Chef + class Provider + class Template < Chef::Provider::File + + include Chef::Mixin::Template + + def action_create + r = Chef::REST.new(Chef::Config[:template_url]) + + template_url = generate_url(@new_resource.source, "templates") + raw_template_file = r.get_rest(template_url, true) + + context = @new_resource.variables + context[:node] = @node + template_file = render_template(::File.read(raw_template_file.path), context) + + update = false + + if ::File.exists?(@new_resource.path) + @new_resource.checksum(self.checksum(template_file.path)) + if @new_resource.checksum != @current_resource.checksum + Chef::Log.debug("#{@new_resource} changed from #{@current_resource.checksum} to #{@new_resource.checksum}") + Chef::Log.info("Updating #{@new_resource} at #{@new_resource.path}") + update = true + end + else + Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") + update = true + end + + if update + backup + FileUtils.cp(template_file.path, @new_resource.path) + @new_resource.updated = true + else + Chef::Log.debug("#{@new_resource} is unchanged") + end + + set_owner if @new_resource.owner != nil + set_group if @new_resource.group != nil + set_mode if @new_resource.mode != nil + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/user.rb b/chef/lib/chef/provider/user.rb new file mode 100644 index 0000000000..a3de68d93c --- /dev/null +++ b/chef/lib/chef/provider/user.rb @@ -0,0 +1,172 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "provider") +require File.join(File.dirname(__FILE__), "..", "mixin", "command") +require 'etc' + +class Chef + class Provider + class User < Chef::Provider + + include Chef::Mixin::Command + + def initialize(node, new_resource) + super(node, new_resource) + @user_exists = true + @locked = nil + end + + def load_current_resource + @current_resource = Chef::Resource::User.new(@new_resource.name) + @current_resource.username(@new_resource.username) + + user_info = nil + begin + user_info = Etc.getpwnam(@new_resource.username) + rescue ArgumentError => e + @user_exists = false + Chef::Log.debug("User #{@new_resource.username} does not exist") + end + + if user_info + @current_resource.uid(user_info.uid) + @current_resource.gid(user_info.gid) + @current_resource.comment(user_info.gecos) + @current_resource.home(user_info.dir) + @current_resource.shell(user_info.shell) + + if @new_resource.password + begin + require 'shadow' + rescue Exception => e + Chef::Log.error("You must have ruby-shadow installed for password support!") + raise Chef::Exception::MissingLibrary, "You must have ruby-shadow installed for password support!" + end + shadow_info = Shadow::Passwd.getspnam(@new_resource.username) + @current_resource.password(shadow_info.sp_pwdp) + end + end + + @current_resource + end + + def compare_user + change_required = false + change_required = true if @new_resource.uid != @current_resource.uid + change_required = true if @new_resource.gid != @current_resource.gid + change_required = true if @new_resource.comment != @current_resource.comment + change_required = true if @new_resource.home != @current_resource.home + change_required = true if @new_resource.shell != @current_resource.shell + change_required = true if @new_resource.password != @current_resource.password + change_required + end + + def action_create + case @user_exists + when false + create_user + Chef::Log.info("Created #{@new_resource}") + @new_resource.updated = true + else + if compare_user + manage_user + Chef::Log.info("Altered #{@new_resource}") + @new_resource.updated = true + end + end + end + + def action_remove + if @user_exists + remove_user + @new_resource.updated = true + Chef::Log.info("Removed #{@new_resource}") + end + end + + def action_manage + if @user_exists && compare_user + manage_user + @new_resource.updated = true + Chef::Log.info("Managed #{@new_resource}") + end + end + + def action_modify + if @user_exists && compare_user + manage_user + @new_resource.updated = true + Chef::Log.info("Modified #{@new_resource}") + else + raise Chef::Exception::User, "Cannot modify #{@new_resource} - user does not exist!" + end + end + + def check_lock + status = popen4("passwd -S #{@new_resource.username}") do |pid, stdin, stdout, stderr| + stdin.close + status_line = stdout.gets.split(' ') + case status_line[1] + when /^P/ + @locked = false + when /^N/ + @locked = false + when /^L/ + @locked = true + end + end + + unless status.exitstatus == 0 + raise Chef::Exception::User, "Cannot determine if #{@new_resource} is locked!" + end + + @locked + end + + def action_lock + if @user_exists + if check_lock() == false + lock_user + @new_resource.updated = true + Chef::Log.info("Locked #{@new_resource}") + else + Chef::Log.debug("No need to lock #{@new_resource}") + end + else + raise Chef::Exception::User, "Cannot lock #{@new_resource} - user does not exist!" + end + end + + def action_unlock + if @user_exists + if check_lock() == true + unlock_user + @new_resource.updated = true + Chef::Log.info("Unlocked #{@new_resource}") + else + Chef::Log.debug("No need to unlock #{@new_resource}") + end + else + raise Chef::Exception::User, "Cannot unlock #{@new_resource} - user does not exist!" + end + end + + end + end +end diff --git a/chef/lib/chef/provider/user/useradd.rb b/chef/lib/chef/provider/user/useradd.rb new file mode 100644 index 0000000000..96736a27a8 --- /dev/null +++ b/chef/lib/chef/provider/user/useradd.rb @@ -0,0 +1,88 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", "user") + +class Chef + class Provider + class User + class Useradd < Chef::Provider::User + def create_user + command = "useradd" + command << set_options + run_command(:command => command) + end + + def manage_user + command = "usermod" + command << set_options + run_command(:command => command) + end + + def remove_user + command = "userdel" + command << " -r" if @new_resource.supports[:manage_home] + command << " #{@new_resource.username}" + run_command(:command => command) + end + + def lock_user + run_command(:command => "usermod -L #{@new_resource.username}") + end + + def unlock_user + run_command(:command => "usermod -U #{@new_resource.username}") + end + + def set_options + opts = '' + + field_list = { + 'comment' => "-c", + 'home' => "-d", + 'gid' => "-g", + 'uid' => "-u", + 'shell' => "-s", + 'password' => "-p" + } + field_list.each do |field, option| + field_symbol = field.to_sym + if @current_resource.send(field_symbol) != @new_resource.send(field_symbol) + if @new_resource.send(field_symbol) + Chef::Log.debug("Setting #{@new_resource} #{field} to #{@new_resource.send(field_symbol)}") + opts << " #{option} '#{@new_resource.send(field_symbol)}'" + end + end + end + if @new_resource.supports[:manage_home] + Chef::Log.debug("Managing the home directory for #{@new_resource}") + case @node[:operatingsystem] + when "Fedora","RedHat","CentOS" + opts << " -M" + else + opts << " -m" + end + end + opts << " #{@new_resource.username}" + opts + end + + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/queue.rb b/chef/lib/chef/queue.rb new file mode 100644 index 0000000000..86eb7df257 --- /dev/null +++ b/chef/lib/chef/queue.rb @@ -0,0 +1,107 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") + +class Chef + class Queue + require 'stomp' + + @client = nil + + class << self + include Chef::Mixin::ParamsValidate + + def connect + @client = Stomp::Connection.open( + Chef::Config.has_key?(:queue_user) ? Chef::Config[:queue_user] : "", + Chef::Config.has_key?(:queue_password) ? Chef::Config[:queue_password] : "", + Chef::Config.has_key?(:queue_host) ? Chef::Config[:queue_host] : "localhost", + Chef::Config.has_key?(:queue_port) ? Chef::Config[:queue_port] : 61613, + false + ) + end + + def make_url(type, name) + validate( + { + :queue_type => type.to_sym, + :queue_name => name.to_sym, + }, + { + :queue_type => { + :equal_to => [ :topic, :queue ], + }, + :queue_name => { + :kind_of => [ String, Symbol ], + } + } + ) + queue_url = "/#{type}/chef/#{name}" + end + + def subscribe(type, name) + queue_url = make_url(type, name) + Chef::Log.debug("Subscribing to #{queue_url}") + connect if @client == nil + @client.subscribe(queue_url) + end + + def send_msg(type, name, msg) + validate( + { + :message => msg, + }, + { + :message => { + :respond_to => :to_json + } + } + ) + queue_url = make_url(type, name) + json = msg.to_json + connect if @client == nil + Chef::Log.debug("Sending to #{queue_url}: #{json}") + @client.send(queue_url, json) + end + + def receive_msg + connect if @client == nil + raw_msg = @client.receive() + Chef::Log.debug("Received Message from #{raw_msg.headers["destination"]} containing: #{raw_msg.body}") + msg = JSON.parse(raw_msg.body) + return msg, raw_msg.headers + end + + def poll_msg + connect if @client == nil + raw_msg = @client.poll() + if raw_msg + msg = JSON.parse(raw_msg.body) + else + nil + end + end + + def disconnect + raise ArgumentError, "You must call connect before you can disconnect!" unless @client + @client.disconnect + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb new file mode 100644 index 0000000000..e2e20640f7 --- /dev/null +++ b/chef/lib/chef/recipe.rb @@ -0,0 +1,127 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "from_file") + +class Chef + class Recipe + + include Chef::Mixin::FromFile + + attr_accessor :cookbook_name, :recipe_name, :recipe, :node, :collection, + :definitions, :params, :cookbook_loader + + def initialize(cookbook_name, recipe_name, node, collection=nil, definitions=nil, cookbook_loader=nil) + @cookbook_name = cookbook_name + @recipe_name = recipe_name + @node = node + + if collection + @collection = collection + else + @collection = Chef::ResourceCollection.new() + end + + if definitions + @definitions = definitions + else + @definitions = Hash.new + end + + if cookbook_loader + @cookbook_loader = cookbook_loader + else + @cookbook_loader = Chef::CookbookLoader.new() + end + + @params = Hash.new + end + + def require_recipe(*args) + args.flatten.each do |recipe| + rmatch = recipe.match(/(.+?)::(.+)/) + if rmatch + cookbook = @cookbook_loader[rmatch[1]] + cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader) + else + cookbook = @cookbook_loader[recipe] + cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader) + end + end + end + + def resources(*args) + @collection.resources(*args) + end + + def search(type, query, &block) + Chef::Log.debug("Searching #{type} index with #{query}") + r = Chef::REST.new(Chef::Config[:search_url]) + results = r.get_rest("search/#{type}?q=#{query}") + Chef::Log.debug("Searching #{type} index with #{query} returned #{results.length} entries") + results.each do |sr| + block.call(sr) + end + end + + def method_missing(method_symbol, *args, &block) + resource = nil + # If we have a definition that matches, we want to use that instead. This should + # let you do some really crazy over-riding of "native" types, if you really want + # to. + if @definitions.has_key?(method_symbol) + new_def = @definitions[method_symbol].dup + new_def.instance_eval(&block) if block + new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @cookbook_loader) + new_recipe.params = new_def.params + new_recipe.params[:name] = args[0] + new_recipe.instance_eval(&new_def.recipe) + else + method_name = method_symbol.to_s + # Otherwise, we're rocking the regular resource call route. + rname = nil + mn = method_name.match(/^(.+)_(.+)$/) + if mn + rname = "Chef::Resource::#{mn[1].capitalize}#{mn[2].capitalize}" + else + short_match = method_name.match(/^(.+)$/) + if short_match + rname = "Chef::Resource::#{short_match[1].capitalize}" + end + end + begin + args << @collection + args << @node + resource = eval(rname).new(*args) + resource.cookbook_name = @cookbook_name + resource.recipe_name = @recipe_name + resource.params = @params + resource.instance_eval(&block) if block + rescue Exception => e + if e.kind_of?(NameError) && e.to_s =~ /Chef::Resource/ + raise NameError, "Cannot find #{rname} for #{method_name}\nOriginal: #{e.to_s}" + else + raise e + end + end + @collection << resource + resource + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb new file mode 100644 index 0000000000..75349a7927 --- /dev/null +++ b/chef/lib/chef/resource.rb @@ -0,0 +1,172 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require File.join(File.dirname(__FILE__), "mixin", "check_helper") + +class Chef + class Resource + + include Chef::Mixin::CheckHelper + include Chef::Mixin::ParamsValidate + + attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection, :cookbook_name, :recipe_name, :supports + attr_reader :resource_name, :source_line, :node + + def initialize(name, collection=nil, node=nil) + @name = name + if collection + @collection = collection + else + @collection = Chef::ResourceCollection.new() + end + @node = node ? node : Chef::Node.new + @noop = nil + @before = nil + @actions = Hash.new + @params = Hash.new + @provider = nil + @allowed_actions = [ :nothing ] + @action = :nothing + @updated = false + @supports = {} + @source_line = caller(4).shift.gsub!(/^(.+):(.+):.+$/, '\1 line \2') + @source_line = ::File.expand_path(@source_line) if @source_line + end + + def provider(arg=nil) + set_or_return( + :provider, + arg, + :kind_of => [ Class ] + ) + end + + def action(arg=nil) + if arg + action_list = arg.kind_of?(Array) ? arg : [ arg ] + action_list.each do |action| + validate( + { + :action => action, + }, + { + :object => { :equal_to => @allowed_actions }, + } + ) + end + @action = action_list + else + @action + end + end + + def name(name=nil) + set_if_args(@name, name) do + raise ArgumentError, "name must be a string!" unless name.kind_of?(String) + @name = name + end + end + + def noop(tf=nil) + set_if_args(@noop, tf) do + raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false + @noop = tf + end + 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 + end + true + end + + def resources(*args) + @collection.resources(*args) + end + + def subscribes(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 resource.actions.has_key?(action_sym) + resource.actions[action_sym][timing] << self + else + resource.actions[action_sym] = Hash.new + resource.actions[action_sym][:delayed] = Array.new + resource.actions[action_sym][:immediate] = Array.new + resource.actions[action_sym][timing] << self + end + end + true + end + + def is(*args) + return *args + end + + def to_s + "#{@resource_name}[#{@name}]" + end + + # Serialize this object as a hash + def to_json(*a) + instance_vars = Hash.new + self.instance_variables.each do |iv| + instance_vars[iv] = self.instance_variable_get(iv) unless iv == "@collection" + end + results = { + 'json_class' => self.class.name, + 'instance_vars' => instance_vars + } + results.to_json(*a) + end + + def self.json_create(o) + resource = self.new(o["instance_vars"]["@name"]) + o["instance_vars"].each do |k,v| + resource.instance_variable_set(k.to_sym, v) + end + resource + end + + private + + def check_timing(timing) + unless timing == :delayed || timing == :immediate || timing == :immediately + raise ArgumentError, "Timing must be :delayed or :immediate(ly), you said #{timing}" + end + if timing == :immediately + timing = :immediate + end + timing + end + end +end diff --git a/chef/lib/chef/resource/apt_package.rb b/chef/lib/chef/resource/apt_package.rb new file mode 100644 index 0000000000..58e8b14edc --- /dev/null +++ b/chef/lib/chef/resource/apt_package.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "package") + +class Chef + class Resource + class AptPackage < Chef::Resource::Package + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :apt_package + @provider = Chef::Provider::Package::Apt + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/bash.rb b/chef/lib/chef/resource/bash.rb new file mode 100644 index 0000000000..7af5f9756a --- /dev/null +++ b/chef/lib/chef/resource/bash.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "script") + +class Chef + class Resource + class Bash < Chef::Resource::Script + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :bash + @interpreter = "bash" + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/csh.rb b/chef/lib/chef/resource/csh.rb new file mode 100644 index 0000000000..29de9777b9 --- /dev/null +++ b/chef/lib/chef/resource/csh.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "script") + +class Chef + class Resource + class Csh < Chef::Resource::Script + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :csh + @interpreter = "csh" + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/directory.rb b/chef/lib/chef/resource/directory.rb new file mode 100644 index 0000000000..7e8944f5fc --- /dev/null +++ b/chef/lib/chef/resource/directory.rb @@ -0,0 +1,74 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Directory < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :directory + @path = name + @action = :create + @recursive = false + @allowed_actions.push(:create, :delete) + end + + def recursive(arg=nil) + set_or_return( + :recursive, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + def group(arg=nil) + set_or_return( + :group, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + def mode(arg=nil) + set_or_return( + :mode, + arg, + :regex => /^\d{3,4}$/ + ) + end + + def owner(arg=nil) + set_or_return( + :owner, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + def path(arg=nil) + set_or_return( + :path, + arg, + :kind_of => String + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/execute.rb b/chef/lib/chef/resource/execute.rb new file mode 100644 index 0000000000..232cfe4d64 --- /dev/null +++ b/chef/lib/chef/resource/execute.rb @@ -0,0 +1,133 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Execute < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :execute + @command = name + @backup = 5 + @action = "run" + @creates = nil + @cwd = nil + @environment = nil + @group = nil + @onlyif = nil + @path = nil + @notify_only = false + @returns = 0 + @timeout = nil + @not_if = nil + @user = nil + @allowed_actions.push(:run) + end + + def command(arg=nil) + set_or_return( + :command, + arg, + :kind_of => [ String ] + ) + end + + def creates(arg=nil) + set_or_return( + :creates, + arg, + :kind_of => [ String ] + ) + end + + def cwd(arg=nil) + set_or_return( + :cwd, + arg, + :kind_of => [ String ] + ) + end + + def environment(arg=nil) + set_or_return( + :environment, + arg, + :kind_of => [ Hash ] + ) + end + + def group(arg=nil) + set_or_return( + :group, + arg, + :kind_of => [ String, Integer ] + ) + end + + def onlyif(arg=nil) + set_or_return( + :onlyif, + arg, + :kind_of => [ String ] + ) + end + + def path(arg=nil) + set_or_return( + :path, + arg, + :kind_of => [ Array ] + ) + end + + def returns(arg=nil) + set_or_return( + :returns, + arg, + :kind_of => [ Integer ] + ) + end + + def timeout(arg=nil) + set_or_return( + :timeout, + arg, + :kind_of => [ Integer ] + ) + end + + def not_if(arg=nil) + set_or_return( + :not_if, + arg, + :kind_of => [ String ] + ) + end + + def user(arg=nil) + set_or_return( + :user, + arg, + :kind_of => [ String, Integer ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/file.rb b/chef/lib/chef/resource/file.rb new file mode 100644 index 0000000000..099bd76591 --- /dev/null +++ b/chef/lib/chef/resource/file.rb @@ -0,0 +1,82 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 File < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :file + @path = name + @backup = 5 + @action = "create" + @allowed_actions.push(:create, :delete, :touch) + end + + def backup(arg=nil) + set_or_return( + :backup, + arg, + :kind_of => [ Integer, FalseClass ] + ) + end + + def checksum(arg=nil) + set_or_return( + :checksum, + arg, + :regex => /^[a-zA-Z0-9]{32}$/ + ) + end + + def group(arg=nil) + set_or_return( + :group, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + def mode(arg=nil) + set_or_return( + :mode, + arg, + :regex => /^\d{3,4}$/ + ) + end + + def owner(arg=nil) + set_or_return( + :owner, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + def path(arg=nil) + set_or_return( + :path, + arg, + :kind_of => String + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/gem_package.rb b/chef/lib/chef/resource/gem_package.rb new file mode 100644 index 0000000000..1ec9463337 --- /dev/null +++ b/chef/lib/chef/resource/gem_package.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "package") + +class Chef + class Resource + class GemPackage < Chef::Resource::Package + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :gem_package + @provider = Chef::Provider::Package::Rubygems + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/link.rb b/chef/lib/chef/resource/link.rb new file mode 100644 index 0000000000..46b5793b0f --- /dev/null +++ b/chef/lib/chef/resource/link.rb @@ -0,0 +1,60 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Link < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :link + @source_file = name + @action = :create + @link_type = :symbolic + @target_file = nil + @allowed_actions.push(:create, :delete) + end + + def source_file(arg=nil) + set_or_return( + :source_file, + arg, + :kind_of => String + ) + end + + def target_file(arg=nil) + set_or_return( + :target_file, + arg, + :kind_of => String + ) + end + + def link_type(arg=nil) + real_arg = arg.kind_of?(String) ? arg.to_sym : arg + set_or_return( + :link_type, + real_arg, + :equal_to => [ :symbolic, :hard ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/package.rb b/chef/lib/chef/resource/package.rb new file mode 100644 index 0000000000..f5f1c8418e --- /dev/null +++ b/chef/lib/chef/resource/package.rb @@ -0,0 +1,69 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Package < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :package + @package_name = name + @version = nil + @candidate_version = nil + @response_file = nil + @source = nil + @action = "install" + @allowed_actions.push(:install, :upgrade, :remove, :purge) + end + + def package_name(arg=nil) + set_or_return( + :package_name, + arg, + :kind_of => [ String ] + ) + end + + def version(arg=nil) + set_or_return( + :version, + arg, + :kind_of => [ String ] + ) + end + + def response_file(arg=nil) + set_or_return( + :response_file, + arg, + :kind_of => [ String ] + ) + end + + def source(arg=nil) + set_or_return( + :source, + arg, + :kind_of => [ String ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/perl.rb b/chef/lib/chef/resource/perl.rb new file mode 100644 index 0000000000..a9b891ed5c --- /dev/null +++ b/chef/lib/chef/resource/perl.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "script") + +class Chef + class Resource + class Perl < Chef::Resource::Script + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :perl + @interpreter = "perl" + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/portage_package.rb b/chef/lib/chef/resource/portage_package.rb new file mode 100644 index 0000000000..0e300da7a3 --- /dev/null +++ b/chef/lib/chef/resource/portage_package.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "package") + +class Chef + class Resource + class PortagePackage < Chef::Resource::Package + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :portage_package + @provider = Chef::Provider::Package::Apt + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/python.rb b/chef/lib/chef/resource/python.rb new file mode 100644 index 0000000000..ac2b27c00f --- /dev/null +++ b/chef/lib/chef/resource/python.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "script") + +class Chef + class Resource + class Python < Chef::Resource::Script + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :python + @interpreter = "python" + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/remote_directory.rb b/chef/lib/chef/resource/remote_directory.rb new file mode 100644 index 0000000000..a23b39d42c --- /dev/null +++ b/chef/lib/chef/resource/remote_directory.rb @@ -0,0 +1,79 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 RemoteDirectory < Chef::Resource::Directory + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :remote_directory + @path = name + @delete = false + @action = :create + @recursive = true + @files_backup = 5 + @files_owner = nil + @files_group = nil + @files_mode = 0644 + @allowed_actions.push(:create, :delete) + end + + def source(args=nil) + set_or_return( + :source, + args, + :kind_of => String + ) + end + + def files_backup(arg=nil) + set_or_return( + :files_backup, + arg, + :kind_of => [ Integer, FalseClass ] + ) + end + + def files_group(arg=nil) + set_or_return( + :files_group, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + def files_mode(arg=nil) + set_or_return( + :files_mode, + arg, + :regex => /^\d{3,4}$/ + ) + end + + def files_owner(arg=nil) + set_or_return( + :files_owner, + arg, + :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/remote_file.rb b/chef/lib/chef/resource/remote_file.rb new file mode 100644 index 0000000000..dace234398 --- /dev/null +++ b/chef/lib/chef/resource/remote_file.rb @@ -0,0 +1,41 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 RemoteFile < Chef::Resource::File + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :remote_file + @action = "create" + @source = nil + @variables = Hash.new + end + + def source(args=nil) + set_or_return( + :source, + args, + :kind_of => String + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/ruby.rb b/chef/lib/chef/resource/ruby.rb new file mode 100644 index 0000000000..591f1a658b --- /dev/null +++ b/chef/lib/chef/resource/ruby.rb @@ -0,0 +1,33 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "script") + +class Chef + class Resource + class Ruby < Chef::Resource::Script + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :ruby + @interpreter = "ruby" + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/script.rb b/chef/lib/chef/resource/script.rb new file mode 100644 index 0000000000..d59882ab9c --- /dev/null +++ b/chef/lib/chef/resource/script.rb @@ -0,0 +1,51 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "execute") + +class Chef + class Resource + class Script < Chef::Resource::Execute + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :script + @command = name + @code = nil + @interpreter = nil + end + + def code(arg=nil) + set_or_return( + :code, + arg, + :kind_of => [ String ] + ) + end + + def interpreter(arg=nil) + set_or_return( + :interpreter, + arg, + :kind_of => [ String ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/service.rb b/chef/lib/chef/resource/service.rb new file mode 100644 index 0000000000..1c9a784745 --- /dev/null +++ b/chef/lib/chef/resource/service.rb @@ -0,0 +1,113 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Service < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :service + @service_name = name + @enabled = nil + @running = nil + @pattern = service_name + @start_command = nil + @stop_command = nil + @status_command = nil + @restart_command = nil + @action = "none" + @supports = { :restart => false, :status => false } + @allowed_actions.push(:enable, :disable, :start, :stop) + end + + def service_name(arg=nil) + set_or_return( + :service_name, + arg, + :kind_of => [ String ] + ) + end + + # regex for match against ps -ef when !supports[:has_status] && status == nil + def pattern(arg=nil) + set_or_return( + :pattern, + arg, + :kind_of => [ String ] + ) + end + + # command to call to start service + def start_command(arg=nil) + set_or_return( + :start_command, + arg, + :kind_of => [ String ] + ) + end + + # command to call to stop service + def stop_command(arg=nil) + set_or_return( + :stop_command, + arg, + :kind_of => [ String ] + ) + end + + # command to call to get status of service + def status_command(arg=nil) + set_or_return( + :status_command, + arg, + :kind_of => [ String ] + ) + end + + # command to call to restart service + def restart_command(arg=nil) + set_or_return( + :restart_command, + arg, + :kind_of => [ String ] + ) + end + + # if the service is enabled or not + def enabled(arg=nil) + set_or_return( + :enabled, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + # if the service is running or not + def running(arg=nil) + set_or_return( + :running, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + + end + end +end diff --git a/chef/lib/chef/resource/sysctl.rb b/chef/lib/chef/resource/sysctl.rb new file mode 100644 index 0000000000..203ad72425 --- /dev/null +++ b/chef/lib/chef/resource/sysctl.rb @@ -0,0 +1,42 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Sysctl < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :sysctl + @action = "set" + @name = name + @value = nil + @variables = Hash.new + end + + def value(args=nil) + set_or_return( + :value, + args, + :kind_of => String + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/template.rb b/chef/lib/chef/resource/template.rb new file mode 100644 index 0000000000..5f2512a274 --- /dev/null +++ b/chef/lib/chef/resource/template.rb @@ -0,0 +1,49 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 Template < Chef::Resource::File + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :template + @action = "create" + @source = nil + @variables = Hash.new + end + + def source(file=nil) + set_or_return( + :source, + file, + :kind_of => [ String ] + ) + end + + def variables(args=nil) + set_or_return( + :variables, + args, + :kind_of => [ Hash ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/user.rb b/chef/lib/chef/resource/user.rb new file mode 100644 index 0000000000..1702753408 --- /dev/null +++ b/chef/lib/chef/resource/user.rb @@ -0,0 +1,96 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 User < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :user + @username = name + @comment = nil + @uid = nil + @gid = nil + @home = nil + @shell = nil + @password = nil + @action = :create + @supports = { :manage_home => false } + @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock) + end + + def username(arg=nil) + set_or_return( + :username, + arg, + :kind_of => [ String ] + ) + end + + def comment(arg=nil) + set_or_return( + :comment, + arg, + :kind_of => [ String ] + ) + end + + def uid(arg=nil) + set_or_return( + :uid, + arg, + :kind_of => [ String, Integer ] + ) + end + + def gid(arg=nil) + set_or_return( + :gid, + arg, + :kind_of => [ String, Integer ] + ) + end + + def home(arg=nil) + set_or_return( + :home, + arg, + :kind_of => [ String ] + ) + end + + def shell(arg=nil) + set_or_return( + :shell, + arg, + :kind_of => [ String ] + ) + end + + def password(arg=nil) + set_or_return( + :password, + arg, + :kind_of => [ String ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource_collection.rb b/chef/lib/chef/resource_collection.rb new file mode 100644 index 0000000000..5278b15c72 --- /dev/null +++ b/chef/lib/chef/resource_collection.rb @@ -0,0 +1,174 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 ResourceCollection + include Enumerable + + def initialize + @resources = Array.new + @resources_by_name = Hash.new + end + + def [](index) + @resources[index] + end + + def []=(index, arg) + is_chef_resource(arg) + @resources[index] = arg + @resources_by_name[arg.to_s] = index + end + + def <<(*args) + args.flatten.each do |a| + is_chef_resource(a) + @resources << a + @resources_by_name[a.to_s] = @resources.length - 1 + end + end + + def push(*args) + args.flatten.each do |a| + is_chef_resource(a) + @resources.push(a) + @resources_by_name[a.to_s] = @resources.length - 1 + end + end + + def each + @resources.each do |r| + yield r + end + end + + def each_index + @resources.each_index do |i| + yield i + end + end + + def lookup(resource) + lookup_by = nil + if resource.kind_of?(Chef::Resource) + lookup_by = resource.to_s + elsif resource.kind_of?(String) + lookup_by = resource + else + raise ArgumentError, "Must pass a Chef::Resource or String to lookup" + end + res = @resources_by_name[lookup_by] + unless res + raise ArgumentError, "Cannot find a resource matching #{lookup_by} (did you define it first?)" + end + @resources[res] + end + + # Find existing resources by searching the list of existing resources. Possible + # forms are: + # + # resources(:file => "foobar") + # resources(:file => [ "foobar", "baz" ]) + # resources("file[foobar]", "file[baz]") + # resources("file[foobar,baz]") + # + # Returns the matching resource, or an Array of matching resources. + # + # Raises an ArgumentError if you feed it bad lookup information + # Raises a Runtime Error if it can't find the resources you are looking for. + def resources(*args) + results = Array.new + args.each do |arg| + case arg + when Hash + results << find_resource_by_hash(arg) + when String + results << find_resource_by_string(arg) + else + raise ArgumentError, "resources takes arguments as a hash or strings!" + end + end + flat_results = results.flatten + flat_results.length == 1 ? flat_results[0] : flat_results + end + + # Serialize this object as a hash + def to_json(*a) + instance_vars = Hash.new + self.instance_variables.each do |iv| + instance_vars[iv] = self.instance_variable_get(iv) + end + results = { + 'json_class' => self.class.name, + 'instance_vars' => instance_vars + } + results.to_json(*a) + end + + def self.json_create(o) + collection = self.new() + o["instance_vars"].each do |k,v| + collection.instance_variable_set(k.to_sym, v) + end + collection + end + + private + + def find_resource_by_hash(arg) + results = Array.new + arg.each do |resource_name, name_list| + names = name_list.kind_of?(Array) ? name_list : [ name_list ] + names.each do |name| + res_name = "#{resource_name.to_s}[#{name}]" + results << lookup(res_name) + end + end + return results + end + + def find_resource_by_string(arg) + results = Array.new + case arg + when /^(.+)\[(.+?),(.+)\]$/ + resource_type = $1 + arg =~ /^.+\[(.+)\]$/ + resource_list = $1 + resource_list.split(",").each do |name| + resource_name = "#{resource_type}[#{name}]" + results << lookup(resource_name) + end + when /^(.+)\[(.+)\]$/ + resource_type = $1 + name = $2 + resource_name = "#{resource_type}[#{name}]" + results << lookup(resource_name) + else + raise ArgumentError, "You must have a string like resource_type[name]!" + end + return results + end + + def is_chef_resource(arg) + unless arg.kind_of?(Chef::Resource) + raise ArgumentError, "Members must be Chef::Resource's" + end + true + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource_definition.rb b/chef/lib/chef/resource_definition.rb new file mode 100644 index 0000000000..9a0a9cb09a --- /dev/null +++ b/chef/lib/chef/resource_definition.rb @@ -0,0 +1,67 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "from_file") +require File.join(File.dirname(__FILE__), "mixin", "params_validate") + + +class Chef + class ResourceDefinition + + include Chef::Mixin::FromFile + include Chef::Mixin::ParamsValidate + + attr_accessor :name, :params, :recipe + + def initialize + @name = nil + @params = Hash.new + @recipe = nil + end + + def define(resource_name, prototype_params=nil, &block) + unless resource_name.kind_of?(Symbol) + raise ArgumentError, "You must use a symbol when defining a new resource!" + end + @name = resource_name + if prototype_params + unless prototype_params.kind_of?(Hash) + raise ArgumentError, "You must pass a hash as the prototype parameters for a definition." + end + @params = prototype_params + end + if Kernel.block_given? + @recipe = block + else + raise ArgumentError, "You must pass a block to a definition." + end + true + end + + # When we do the resource definition, we're really just setting new values for + # the paramaters we prototyped at the top. This method missing is as simple as + # it gets. + def method_missing(symbol, *args) + @params[symbol] = args.length == 1 ? args[0] : args + end + + def to_s + "#{name.to_s}" + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb new file mode 100644 index 0000000000..b78df6c35f --- /dev/null +++ b/chef/lib/chef/rest.rb @@ -0,0 +1,150 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require 'net/https' +require 'uri' +require 'json' +require 'tempfile' + +class Chef + class REST + + def initialize(url) + @url = url + @cookies = Hash.new + end + + # Send an HTTP GET request to the path + # + # === Parameters + # path:: The path to GET + # raw:: Whether you want the raw body returned, or JSON inflated. Defaults + # to JSON inflated. + def get_rest(path, raw=false) + run_request(:GET, create_url(path), false, 10, raw) + end + + # Send an HTTP DELETE request to the path + def delete_rest(path) + run_request(:DELETE, create_url(path)) + end + + # Send an HTTP POST request to the path + def post_rest(path, json) + run_request(:POST, create_url(path), json) + end + + # Send an HTTP PUT request to the path + def put_rest(path, json) + run_request(:PUT, create_url(path), json) + end + + def create_url(path) + if path =~ /^(http|https):\/\// + URI.parse(path) + else + URI.parse("#{@url}/#{path}") + end + end + + # Actually run an HTTP request. First argument is the HTTP method, + # which should be one of :GET, :PUT, :POST or :DELETE. Next is the + # URL, then an object to include in the body (which will be converted with + # .to_json) and finally, the limit of HTTP Redirects to follow (10). + # + # Typically, you won't use this method -- instead, you'll use one of + # the helper methods (get_rest, post_rest, etc.) + # + # Will return the body of the response on success. + def run_request(method, url, data=false, limit=10, raw=false) + raise ArgumentError, 'HTTP redirect too deep' if limit == 0 + + http = Net::HTTP.new(url.host, url.port) + if url.scheme == "https" + http.use_ssl = true + if Chef::Config[:ssl_verify_mode] == :verify_none + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + end + http.read_timeout = Chef::Config[:rest_timeout] + headers = Hash.new + unless raw + headers = { + 'Accept' => "application/json", + } + end + if @cookies["#{url.host}:#{url.port}"] + headers['Cookie'] = @cookies["#{url.host}:#{url.port}"] + end + req = nil + case method + when :GET + req_path = "#{url.path}" + req_path << "?#{url.query}" if url.query + req = Net::HTTP::Get.new(req_path, headers) + when :POST + headers["Content-Type"] = 'application/json' if data + req = Net::HTTP::Post.new(url.path, headers) + req.body = data.to_json if data + when :PUT + headers["Content-Type"] = 'application/json' if data + req = Net::HTTP::Put.new(url.path, headers) + req.body = data.to_json if data + when :DELETE + req_path = "#{url.path}" + req_path << "?#{url.query}" if url.query + req = Net::HTTP::Delete.new(req_path, headers) + else + raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method" + end + Chef::Log.debug("Sending HTTP Request via #{req.method} to #{req.path}") + res = http.request(req) + + Chef::Log.debug("HTTP request headers: #{req.to_hash.inspect} ") + + Chef::Log.debug("HTTP response headers: #{res.to_hash.inspect} ") + + if res.kind_of?(Net::HTTPSuccess) + if res['set-cookie'] + @cookies["#{url.host}:#{url.port}"] = res['set-cookie'] + end + if res['content-type'] =~ /json/ + JSON.parse(res.body) + else + if raw + tf = Tempfile.new("chef-rest") + tf.print(res.body) + tf.close + tf + else + res.body + end + end + elsif res.kind_of?(Net::HTTPFound) + if res['set-cookie'] + @cookies["#{url.host}:#{url.port}"] = res['set-cookie'] + end + run_request(:GET, create_url(res['location']), false, limit - 1, raw) + else + res.error! + end + end + + end +end diff --git a/chef/lib/chef/runner.rb b/chef/lib/chef/runner.rb new file mode 100644 index 0000000000..b943ef5ec4 --- /dev/null +++ b/chef/lib/chef/runner.rb @@ -0,0 +1,100 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") + +class Chef + class Runner + + include Chef::Mixin::ParamsValidate + + def initialize(node, collection) + validate( + { + :node => node, + :collection => collection, + }, + { + :node => { + :kind_of => Chef::Node, + }, + :collection => { + :kind_of => Chef::ResourceCollection, + }, + } + ) + @node = node + @collection = collection + end + + def build_provider(resource) + provider_klass = resource.provider + if provider_klass == nil + provider_klass = Chef::Platform.find_provider_for_node(@node, resource) + end + Chef::Log.debug("#{resource} using #{provider_klass.to_s}") + provider = provider_klass.new(@node, resource) + provider.load_current_resource + provider + end + + def converge + start_time = Time.now + Chef::Log.info("Starting Chef Run") + delayed_actions = Array.new + + @collection.each do |resource| + begin + Chef::Log.debug("Processing #{resource}") + action_list = resource.action.kind_of?(Array) ? resource.action : [ resource.action ] + action_list.each do |ra| + provider = build_provider(resource) + provider.send("action_#{ra}") + if resource.updated + resource.actions.each_key do |action| + if resource.actions[action].has_key?(:immediate) + resource.actions[action][:immediate].each do |r| + Chef::Log.info("#{resource} sending #{action} action to #{r} (immediate)") + build_provider(r).send("action_#{action}") + end + end + if resource.actions[action].has_key?(:delayed) + resource.actions[action][:delayed].each do |r| + delayed_actions << lambda { + Chef::Log.info("#{resource} sending #{action} action to #{r} (delayed)") + build_provider(r).send("action_#{action}") + } + end + end + end + end + end + rescue => e + Chef::Log.error("#{resource} (#{resource.source_line}) had an error:") + raise + end + end + + # Run all our :delayed actions + delayed_actions.each { |da| da.call } + end_time = Time.now + Chef::Log.info("Chef Run complete in #{end_time - start_time} seconds") + true + end + end +end diff --git a/chef/lib/chef/search.rb b/chef/lib/chef/search.rb new file mode 100644 index 0000000000..229f738399 --- /dev/null +++ b/chef/lib/chef/search.rb @@ -0,0 +1,74 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require 'ferret' + +class Chef + class Search + + attr_reader :index + + def initialize + @index = Ferret::Index::Index.new(:path => Chef::Config[:search_index_path]) + end + + def search(type, query, &block) + search_query = build_search_query(type, query) + start_time = Time.now + result = Array.new + + if Kernel.block_given? + result = @index.search_each(search_query, :limit => :all) do |id, score| + block.call(build_hash(@index.doc(id))) + end + else + @index.search_each(search_query, :limit => :all) do |id, score| + result << build_hash(@index.doc(id)) + end + end + Chef::Log.debug("Search #{search_query} complete in #{Time.now - start_time} seconds") + result + end + + def list_indexes + indexes = Hash.new + @index.search_each("index_name:*", :limit => :all) do |id, score| + indexes[@index.doc(id)["index_name"]] = true + end + indexes.keys + end + + def has_index?(index) + list_indexes.detect { |i| i == index } + end + + private + def build_search_query(type, query) + "index_name:#{type} AND (#{query})" + end + + def build_hash(doc) + result = Hash.new + doc.fields.each do |f| + result[f] = doc[f] + end + result + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/search_index.rb b/chef/lib/chef/search_index.rb new file mode 100644 index 0000000000..932a451999 --- /dev/null +++ b/chef/lib/chef/search_index.rb @@ -0,0 +1,80 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "mixin", "params_validate") +require 'ferret' + +class Chef + class SearchIndex + + attr_reader :index + + def initialize + @index = Ferret::Index::Index.new( + :path => Chef::Config[:search_index_path], + :key => [ :id ] + ) + end + + def add(new_object) + index_hash = create_index_object(new_object) + Chef::Log.debug("Indexing #{index_hash[:index_name]} with #{index_hash.inspect}") + @index.add_document(index_hash) + end + + def create_index_object(new_object) + index_hash = nil + + if new_object.respond_to?(:to_index) + index_hash = new_object.to_index + elsif new_object.kind_of?(Hash) + index_hash = new_object + else + raise Chef::Exception::SearchIndex, "Cannot transform argument to a Hash!" + end + + unless index_hash.has_key?(:index_name) || index_hash.has_key?("index_name") + raise Chef::Exception::SearchIndex, "Cannot index without an index_name key: #{index_hash.inspect}" + end + + unless index_hash.has_key?(:id) || index_hash.has_key?("id") + raise Chef::Exception::SearchIndex, "Cannot index without an id key: #{index_hash.inspect}" + end + + index_hash.each do |k,v| + unless k.kind_of?(Symbol) + index_hash[k.to_sym] = v + index_hash.delete(k) + end + end + + index_hash + end + + def delete(index_obj) + to_delete = create_index_object(index_obj) + Chef::Log.debug("Removing #{to_delete.inspect} from the #{to_delete[:index_name]} index") + @index.delete(to_delete[:id]) + end + + def commit + @index.commit + end + + end +end diff --git a/chef/log/chef-server.log b/chef/log/chef-server.log new file mode 100644 index 0000000000..eabfde15a0 --- /dev/null +++ b/chef/log/chef-server.log @@ -0,0 +1,9 @@ +Mon, 16 Jun 2008 01:13:56 GMT ~ info ~ Logfile created + ~ Compiling routes... + ~ Using 'share-nothing' cookie sessions (4kb limit per client) + ~ Using Mongrel adapter + ~ Compiling routes... + ~ Using 'share-nothing' cookie sessions (4kb limit per client) + ~ Using Mongrel adapter + ~ Using 'share-nothing' cookie sessions (4kb limit per client) + ~ Using Mongrel adapter diff --git a/chef/log/merb.main.pid b/chef/log/merb.main.pid new file mode 100644 index 0000000000..0eb419a8f1 --- /dev/null +++ b/chef/log/merb.main.pid @@ -0,0 +1 @@ +12210
\ No newline at end of file diff --git a/chef/log/merb_test.log b/chef/log/merb_test.log new file mode 100644 index 0000000000..dae0e5d9dd --- /dev/null +++ b/chef/log/merb_test.log @@ -0,0 +1,7 @@ +Tue, 03 Jun 2008 04:33:52 GMT ~ info ~ Logfile created + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions diff --git a/chef/log/stompserver.pid b/chef/log/stompserver.pid new file mode 100644 index 0000000000..20e502d586 --- /dev/null +++ b/chef/log/stompserver.pid @@ -0,0 +1 @@ +12157
\ No newline at end of file diff --git a/chef/pkg/chef-0.0.1.gem b/chef/pkg/chef-0.0.1.gem Binary files differnew file mode 100644 index 0000000000..c090729e12 --- /dev/null +++ b/chef/pkg/chef-0.0.1.gem diff --git a/chef/spec/chef_server/controllers/log/merb_test.log b/chef/spec/chef_server/controllers/log/merb_test.log new file mode 100644 index 0000000000..86fbe4b9cb --- /dev/null +++ b/chef/spec/chef_server/controllers/log/merb_test.log @@ -0,0 +1,4 @@ +Tue, 27 May 2008 00:52:34 GMT ~ info ~ Logfile created + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions diff --git a/chef/spec/chef_server/controllers/nodes_spec.rb b/chef/spec/chef_server/controllers/nodes_spec.rb new file mode 100644 index 0000000000..65c2df301a --- /dev/null +++ b/chef/spec/chef_server/controllers/nodes_spec.rb @@ -0,0 +1,232 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", 'spec_helper.rb') +# +# describe Nodes, "index action" do +# it "should get a list of all the nodes" do +# Chef::Node.should_receive(:list).and_return(["one"]) +# dispatch_to(Nodes, :index) do |c| +# c.stub!(:display) +# end +# end +# +# it "should send a list of nodes to display" do +# Chef::Node.stub!(:list).and_return(["one"]) +# dispatch_to(Nodes, :index) do |c| +# c.should_receive(:display).with(["one"]) +# end +# end +# end +# +# describe Nodes, "show action" do +# it "should load a node from the filestore based on the id" do +# node = stub("Node", :null_object => true) +# Chef::Node.should_receive(:load).with("bond").once.and_return(node) +# dispatch_to(Nodes, :show, { :id => "bond" }) do |c| +# c.should_receive(:display).with(node).once.and_return(true) +# end +# end +# +# it "should return 200 on a well formed request" do +# node = stub("Node", :null_object => true) +# Chef::Node.should_receive(:load).with("bond").once.and_return(node) +# controller = dispatch_to(Nodes, :show, { :id => "bond" }) do |c| +# c.stub!(:display) +# end +# controller.status.should eql(200) +# end +# +# it "should raise a BadRequest if the id is not found" do +# Chef::Node.should_receive(:load).with("bond").once.and_raise(RuntimeError) +# lambda { +# dispatch_to(Nodes, :show, { :id => "bond" }) +# }.should raise_error(Merb::ControllerExceptions::BadRequest) +# end +# end +# +# describe Nodes, "create action" do +# it "should create a node from an inflated object" do +# mnode = mock("Node", :null_object => true) +# mnode.stub!(:name).and_return("bond") +# mnode.should_receive(:save).once.and_return(true) +# controller = dispatch_to(Nodes, :create) do |c| +# c.stub!(:params).and_return({ "inflated_object" => mnode }) +# c.stub!(:session).and_return({ +# :openid => 'http://localhost/openid/server/node/bond', +# :level => :node, +# :node_name => "bond", +# }) +# c.stub!(:display) +# end +# controller.status.should eql(202) +# end +# +# it "should raise an exception if it cannot inflate an object" do +# lambda { +# dispatch_to(Nodes, :create) do |c| +# c.stub!(:params).and_return({ }) +# end +# }.should raise_error(Merb::Controller::BadRequest) +# end +# end +# +# describe Nodes, "update action" do +# it "should update a node from an inflated object" do +# mnode = mock("Node", :null_object => true) +# mnode.stub!(:name).and_return("one") +# Chef::FileStore.should_receive(:store).with("node", "one", mnode).once.and_return(true) +# controller = dispatch_to(Nodes, :update, { :id => "one" }) do |c| +# c.stub!(:session).and_return({ +# :openid => 'http://localhost/openid/server/node/one', +# :level => :node, +# :node_name => "one", +# }) +# c.stub!(:params).and_return({ "inflated_object" => mnode }) +# c.stub!(:display) +# end +# controller.status.should eql(202) +# end +# +# it "should raise an exception if it cannot inflate an object" do +# lambda { dispatch_to(Nodes, :update) }.should raise_error(Merb::Controller::BadRequest) +# end +# end +# +# describe Nodes, "destroy action" do +# def do_destroy +# dispatch_to(Nodes, :destroy, { :id => "one" }) do |c| +# c.stub!(:display) +# end +# end +# +# it "should load the node it's about to destroy from the filestore" do +# mnode = stub("Node", :null_object => true) +# Chef::FileStore.should_receive(:load).with("node", "one").once.and_return(mnode) +# Chef::FileStore.stub!(:delete) +# do_destroy +# end +# +# it "should raise an exception if it cannot find the node to destroy" do +# Chef::FileStore.should_receive(:load).with("node", "one").once.and_raise(RuntimeError) +# lambda { do_destroy }.should raise_error(Merb::Controller::BadRequest) +# end +# +# it "should remove the node from the filestore" do +# mnode = stub("Node", :null_object => true) +# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode) +# Chef::FileStore.should_receive(:delete).with("node", "one") +# do_destroy +# end +# +# it "should remove the node from the search index" do +# mnode = stub("Node", :null_object => true) +# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode) +# Chef::FileStore.stub!(:delete) +# do_destroy +# end +# +# it "should return the node it just deleted" do +# mnode = stub("Node", :null_object => true) +# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode) +# Chef::FileStore.stub!(:delete) +# dispatch_to(Nodes, :destroy, { :id => "one" }) do |c| +# c.should_receive(:display).once.with(mnode) +# end +# end +# +# it "should return a status of 202" do +# mnode = stub("Node", :null_object => true) +# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode) +# Chef::FileStore.stub!(:delete) +# controller = do_destroy +# controller.status.should eql(202) +# end +# end +# +# describe Nodes, "compile action" do +# before(:each) do +# @compile = stub("Compile", :null_object => true) +# @node = stub("Node", :null_object => true) +# @node.stub!(:[]).and_return(true) +# @node.stub!(:[]=).and_return(true) +# @node.stub!(:recipes).and_return([]) +# @compile.stub!(:load_definitions).and_return(true) +# @compile.stub!(:load_recipes).and_return(true) +# @compile.stub!(:collection).and_return([]) +# @compile.stub!(:node, @node) +# @compile.stub!(:load_node).and_return(true) +# @stored_node = stub("Stored Node", :null_object => true) +# end +# +# def do_compile +# Chef::FileStore.stub!(:store).and_return(true) +# Chef::FileStore.stub!(:load).and_return(@stored_node) +# Chef::Compile.stub!(:new).and_return(@compile) +# dispatch_to(Nodes, :compile, { :id => "one" }) do |c| +# c.stub!(:display) +# end +# end +# +# it "should load the node from the node resource" do +# @compile.should_receive(:load_node).with("one").and_return(true) +# do_compile +# end +# +# it "should merge the data with the currently stored node" do +# node1 = Chef::Node.new +# node1.name "adam" +# node1.music "crowe" +# node1.recipes << "monkey" +# @compile.stub!(:node).and_return(node1) +# @stored_node = Chef::Node.new +# @stored_node.name "adam" +# @stored_node.music "crown" +# @stored_node.woot "woot" +# @stored_node.recipes << "monkeysoup" +# do_compile +# node1.name.should eql("adam") +# node1.music.should eql("crown") +# node1.woot.should eql("woot") +# node1.recipes.should eql([ "monkey", "monkeysoup" ]) +# end +# +# it "should load definitions" do +# @compile.should_receive(:load_definitions) +# do_compile +# end +# +# it "should load recipes" do +# @compile.should_receive(:load_recipes) +# do_compile +# end +# +# it "should display the collection and node object" do +# Chef::FileStore.stub!(:load).and_return(@stored_node) +# Chef::Compile.stub!(:new).and_return(@compile) +# dispatch_to(Nodes, :compile, { :id => "one" }) do |c| +# c.should_receive(:display).with({ :collection => [], :node => nil }) +# end +# end +# +# it "should return 200" do +# controller = do_compile +# controller.status.should eql(200) +# end +# +# end
\ No newline at end of file diff --git a/chef/spec/chef_server/controllers/openid_consumer_spec.rb b/chef/spec/chef_server/controllers/openid_consumer_spec.rb new file mode 100644 index 0000000000..0156fd8d49 --- /dev/null +++ b/chef/spec/chef_server/controllers/openid_consumer_spec.rb @@ -0,0 +1,47 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", 'spec_helper.rb') +# +# describe OpenidConsumer, "check_valid_openid_provider method" do +# it "should confirm the openid provider if the openid_providers config is nil" do +# Chef::Config[:openid_providers] = nil +# c = OpenidConsumer.new(true) +# c.send(:check_valid_openid_provider, "monkeyid").should eql(true) +# end +# +# it "should return true if the openid provider is in openid_providers list" do +# Chef::Config[:openid_providers] = [ 'monkeyid' ] +# c = OpenidConsumer.new(true) +# c.send(:check_valid_openid_provider, "monkeyid").should eql(true) +# end +# +# it "should return true if the openid provider is in openid_providers list with http://" do +# Chef::Config[:openid_providers] = [ 'monkeyid' ] +# c = OpenidConsumer.new(true) +# c.send(:check_valid_openid_provider, "http://monkeyid").should eql(true) +# end +# +# it "should raise an exception if the openid provider is not in openid_providers list" do +# Chef::Config[:openid_providers] = [ 'monkeyid' ] +# c = OpenidConsumer.new(true) +# lambda { +# c.send(:check_valid_openid_provider, "monkey") +# }.should raise_error(Merb::Controller::Unauthorized) +# end +# end
\ No newline at end of file diff --git a/chef/spec/chef_server/controllers/openid_register_spec.rb b/chef/spec/chef_server/controllers/openid_register_spec.rb new file mode 100644 index 0000000000..c883715fc6 --- /dev/null +++ b/chef/spec/chef_server/controllers/openid_register_spec.rb @@ -0,0 +1,111 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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.join(File.dirname(__FILE__), "..", 'spec_helper.rb') +# +# describe OpenidRegister, "index action" do +# it "should get a list of all registered nodes" do +# Chef::OpenIDRegistration.should_receive(:list).with(true).and_return(["one"]) +# dispatch_to(OpenidRegister, :index) do |c| +# c.stub!(:display) +# end +# end +# end +# +# describe OpenidRegister, "show action" do +# it "should raise a 404 if the nodes registration is not found" do +# Chef::OpenIDRegistration.should_receive(:load).with("foo").and_raise(RuntimeError) +# lambda { +# dispatch_to(OpenidRegister, :show, { :id => "foo" }) +# }.should raise_error(Merb::ControllerExceptions::NotFound) +# end +# +# it "should call display on the node registration" do +# Chef::OpenIDRegistration.stub!(:load).and_return(true) +# dispatch_to(OpenidRegister, :show, { :id => "foo" }) do |c| +# c.should_receive(:display).with(true) +# end +# end +# end +# +# describe OpenidRegister, "create action" do +# def do_create +# dispatch_to(OpenidRegister, :create, { :id => "foo", :password => "beck" }) do |c| +# c.stub!(:display) +# end +# end +# +# it "should require an id to register" do +# lambda { +# dispatch_to(OpenidRegister, :create, { :password => "beck" }) +# }.should raise_error(Merb::ControllerExceptions::BadRequest) +# end +# +# it "should require a password to register" do +# lambda { +# dispatch_to(OpenidRegister, :create, { :id => "foo" }) +# }.should raise_error(Merb::ControllerExceptions::BadRequest) +# end +# +# it "should return 400 if a node is already registered" do +# Chef::OpenIDRegistration.should_receive(:has_key?).with("foo").and_return(true) +# lambda { +# dispatch_to(OpenidRegister, :create, { :id => "foo", :password => "beck" }) +# }.should raise_error(Merb::ControllerExceptions::BadRequest) +# end +# +# it "should store the registration in a new Chef::OpenIDRegistration" do +# mock_reg = mock("Chef::OpenIDRegistration", :null_object => true) +# mock_reg.should_receive(:name=).with("foo").and_return(true) +# mock_reg.should_receive(:set_password).with("beck").and_return(true) +# mock_reg.should_receive(:save).and_return(true) +# Chef::OpenIDRegistration.stub!(:has_key?).and_return(false) +# Chef::OpenIDRegistration.should_receive(:new).and_return(mock_reg) +# do_create +# end +# end +# +# describe OpenidRegister, "update action" do +# it "should raise a 400 error" do +# lambda { +# dispatch_to(OpenidRegister, :update) +# } +# end +# end +# +# describe OpenidRegister, "destroy action" do +# def do_destroy +# dispatch_to(OpenidRegister, :destroy, { :id => "foo" }) do |c| +# c.stub!(:display) +# end +# end +# +# it "should return 400 if it cannot find the registration" do +# Chef::OpenIDRegistration.should_receive(:load).and_raise(ArgumentError) +# lambda { +# do_destroy +# }.should raise_error(Merb::ControllerExceptions::BadRequest) +# end +# +# it "should delete the registration from the store" do +# mock_reg = mock("OpenIDRegistration") +# mock_reg.should_receive(:destroy).and_return(true) +# Chef::OpenIDRegistration.should_receive(:load).with("foo").and_return(mock_reg) +# do_destroy +# end +# end
\ No newline at end of file diff --git a/chef/spec/chef_server/log/merb_test.log b/chef/spec/chef_server/log/merb_test.log new file mode 100644 index 0000000000..b2aa15df9a --- /dev/null +++ b/chef/spec/chef_server/log/merb_test.log @@ -0,0 +1,4 @@ +Mon, 16 Jun 2008 02:08:02 GMT ~ info ~ Logfile created + ~ Not Using Sessions + ~ Not Using Sessions + ~ Not Using Sessions diff --git a/chef/spec/chef_server/spec.opts b/chef/spec/chef_server/spec.opts new file mode 100644 index 0000000000..3a2ff550a2 --- /dev/null +++ b/chef/spec/chef_server/spec.opts @@ -0,0 +1,4 @@ +--color +--loadby +mtime + diff --git a/chef/spec/chef_server/spec_helper.rb b/chef/spec/chef_server/spec_helper.rb new file mode 100644 index 0000000000..90c1e0af7a --- /dev/null +++ b/chef/spec/chef_server/spec_helper.rb @@ -0,0 +1,16 @@ +require 'rubygems' +require 'merb-core' +require 'spec' # Satiates Autotest and anyone else not using the Rake tasks + +Merb.start_environment( + :testing => true, + :adapter => 'runner', + :environment => ENV['MERB_ENV'] || 'test', + :init_file => File.join(File.dirname(__FILE__), "..", "..", "lib", "chef_server", "init.rb") +) + +Spec::Runner.configure do |config| + config.include(Merb::Test::ViewHelper) + config.include(Merb::Test::RouteHelper) + config.include(Merb::Test::ControllerHelper) +end diff --git a/chef/spec/data/bad-config.rb b/chef/spec/data/bad-config.rb new file mode 100644 index 0000000000..5477a69366 --- /dev/null +++ b/chef/spec/data/bad-config.rb @@ -0,0 +1 @@ +monkey_soup("tastes nice")
\ No newline at end of file diff --git a/chef/spec/data/compile/cookbooks/test/attributes/george.rb b/chef/spec/data/compile/cookbooks/test/attributes/george.rb new file mode 100644 index 0000000000..5df9567761 --- /dev/null +++ b/chef/spec/data/compile/cookbooks/test/attributes/george.rb @@ -0,0 +1 @@ +george "washington"
\ No newline at end of file diff --git a/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb b/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb new file mode 100644 index 0000000000..a49b53348c --- /dev/null +++ b/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb @@ -0,0 +1,5 @@ +define :new_cat, :is_pretty => true do + cat "#{params[:name]}" do + pretty_kitty params[:is_pretty] + end +end diff --git a/chef/spec/data/compile/cookbooks/test/recipes/default.rb b/chef/spec/data/compile/cookbooks/test/recipes/default.rb new file mode 100644 index 0000000000..d769dc826d --- /dev/null +++ b/chef/spec/data/compile/cookbooks/test/recipes/default.rb @@ -0,0 +1,5 @@ + +cat "einstein" do + pretty_kitty true +end + diff --git a/chef/spec/data/compile/cookbooks/test/recipes/one.rb b/chef/spec/data/compile/cookbooks/test/recipes/one.rb new file mode 100644 index 0000000000..7795cc1d4a --- /dev/null +++ b/chef/spec/data/compile/cookbooks/test/recipes/one.rb @@ -0,0 +1,7 @@ +cat "loulou" do + pretty_kitty true +end + +new_cat "birthday" do + pretty_kitty false +end diff --git a/chef/spec/data/compile/cookbooks/test/recipes/two.rb b/chef/spec/data/compile/cookbooks/test/recipes/two.rb new file mode 100644 index 0000000000..01def1b2ac --- /dev/null +++ b/chef/spec/data/compile/cookbooks/test/recipes/two.rb @@ -0,0 +1,7 @@ +cat "peanut" do + pretty_kitty true +end + +new_cat "fat peanut" do + pretty_kitty false +end diff --git a/chef/spec/data/compile/nodes/compile.rb b/chef/spec/data/compile/nodes/compile.rb new file mode 100644 index 0000000000..84d52bb1cf --- /dev/null +++ b/chef/spec/data/compile/nodes/compile.rb @@ -0,0 +1,5 @@ +## +# Nodes should have a unique name +## +name "compile" +recipes "test", "test::one", "test::two" diff --git a/chef/spec/data/config.rb b/chef/spec/data/config.rb new file mode 100644 index 0000000000..0b3340ce57 --- /dev/null +++ b/chef/spec/data/config.rb @@ -0,0 +1,6 @@ +# +# Sample Chef Config File +# + +cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook" + diff --git a/chef/spec/data/cookbooks/openldap/attributes/default.rb b/chef/spec/data/cookbooks/openldap/attributes/default.rb new file mode 100644 index 0000000000..204ae9ed77 --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/attributes/default.rb @@ -0,0 +1,15 @@ +chef_env ||= nil +case chef_env +when "prod" + ldap_server "ops1prod" + ldap_basedn "dc=hjksolutions,dc=com" + ldap_replication_password "yes" +when "corp" + ldap_server "ops1prod" + ldap_basedn "dc=hjksolutions,dc=com" + ldap_replication_password "yougotit" +else + ldap_server "ops1prod" + ldap_basedn "dc=hjksolutions,dc=com" + ldap_replication_password "forsure" +end diff --git a/chef/spec/data/cookbooks/openldap/attributes/smokey.rb b/chef/spec/data/cookbooks/openldap/attributes/smokey.rb new file mode 100644 index 0000000000..63f5b56c7f --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/attributes/smokey.rb @@ -0,0 +1 @@ +smokey "robinson"
\ No newline at end of file diff --git a/chef/spec/data/cookbooks/openldap/definitions/client.rb b/chef/spec/data/cookbooks/openldap/definitions/client.rb new file mode 100644 index 0000000000..ac81831d11 --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/definitions/client.rb @@ -0,0 +1,5 @@ +define :openldap_client, :mothra => "a big monster" do + cat "#{params[:name]}" do + pretty_kitty true + end +end diff --git a/chef/spec/data/cookbooks/openldap/definitions/server.rb b/chef/spec/data/cookbooks/openldap/definitions/server.rb new file mode 100644 index 0000000000..2df437aa84 --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/definitions/server.rb @@ -0,0 +1,5 @@ +define :openldap_server, :mothra => "a big monster" do + cat "#{params[:name]}" do + pretty_kitty true + end +end diff --git a/chef/spec/data/cookbooks/openldap/ignore b/chef/spec/data/cookbooks/openldap/ignore new file mode 100644 index 0000000000..e96f4e7df4 --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/ignore @@ -0,0 +1,6 @@ +# +# The ignore file allows you to skip files in cookbooks with the same name that appear +# later in the search path. +# + +recipes/ignoreme.rb
\ No newline at end of file diff --git a/chef/spec/data/cookbooks/openldap/recipes/default.rb b/chef/spec/data/cookbooks/openldap/recipes/default.rb new file mode 100644 index 0000000000..0ac8a9bb4b --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/recipes/default.rb @@ -0,0 +1,3 @@ +cat "blanket" do + pretty_kitty true +end diff --git a/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb b/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb new file mode 100644 index 0000000000..b450eca7cd --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb @@ -0,0 +1,3 @@ +cat "blanket" do + pretty_kitty false +end diff --git a/chef/spec/data/cookbooks/openldap/recipes/one.rb b/chef/spec/data/cookbooks/openldap/recipes/one.rb new file mode 100644 index 0000000000..e1c3cff92e --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/recipes/one.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com default" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" diff --git a/chef/spec/data/cookbooks/openldap/templates/default/test.erb b/chef/spec/data/cookbooks/openldap/templates/default/test.erb new file mode 100644 index 0000000000..f39fa7da89 --- /dev/null +++ b/chef/spec/data/cookbooks/openldap/templates/default/test.erb @@ -0,0 +1 @@ +We could be diving for pearls! diff --git a/chef/spec/data/definitions/test.rb b/chef/spec/data/definitions/test.rb new file mode 100644 index 0000000000..b0d0effc27 --- /dev/null +++ b/chef/spec/data/definitions/test.rb @@ -0,0 +1,5 @@ +define :rico_suave, :rich => "smooth" do + zen_master "test" do + something "#{params[:rich]}" + end +end
\ No newline at end of file diff --git a/chef/spec/data/kitchen/openldap/attributes/default.rb b/chef/spec/data/kitchen/openldap/attributes/default.rb new file mode 100644 index 0000000000..d208959475 --- /dev/null +++ b/chef/spec/data/kitchen/openldap/attributes/default.rb @@ -0,0 +1,3 @@ +# +# Nothing to see here, move along +# diff --git a/chef/spec/data/kitchen/openldap/attributes/robinson.rb b/chef/spec/data/kitchen/openldap/attributes/robinson.rb new file mode 100644 index 0000000000..9d6b44d464 --- /dev/null +++ b/chef/spec/data/kitchen/openldap/attributes/robinson.rb @@ -0,0 +1,3 @@ +# +# Smokey lives here +#
\ No newline at end of file diff --git a/chef/spec/data/kitchen/openldap/definitions/client.rb b/chef/spec/data/kitchen/openldap/definitions/client.rb new file mode 100644 index 0000000000..d4c2263b54 --- /dev/null +++ b/chef/spec/data/kitchen/openldap/definitions/client.rb @@ -0,0 +1,3 @@ +# +# A sad client +# diff --git a/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb b/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb new file mode 100644 index 0000000000..510f0c35da --- /dev/null +++ b/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb @@ -0,0 +1,3 @@ +# +# Was in people magazine this month... +#
\ No newline at end of file diff --git a/chef/spec/data/kitchen/openldap/recipes/gigantor.rb b/chef/spec/data/kitchen/openldap/recipes/gigantor.rb new file mode 100644 index 0000000000..70a41960eb --- /dev/null +++ b/chef/spec/data/kitchen/openldap/recipes/gigantor.rb @@ -0,0 +1,3 @@ +cat "blanket" do + pretty_kitty true +end
\ No newline at end of file diff --git a/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb b/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb new file mode 100644 index 0000000000..15095986c6 --- /dev/null +++ b/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb @@ -0,0 +1,3 @@ +# +# this file will never be seen +#
\ No newline at end of file diff --git a/chef/spec/data/kitchen/openldap/recipes/woot.rb b/chef/spec/data/kitchen/openldap/recipes/woot.rb new file mode 100644 index 0000000000..44893dae36 --- /dev/null +++ b/chef/spec/data/kitchen/openldap/recipes/woot.rb @@ -0,0 +1,3 @@ +# +# Such a funny word.. +# diff --git a/chef/spec/data/nodes/default.rb b/chef/spec/data/nodes/default.rb new file mode 100644 index 0000000000..e1c3cff92e --- /dev/null +++ b/chef/spec/data/nodes/default.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com default" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" diff --git a/chef/spec/data/nodes/test.example.com.rb b/chef/spec/data/nodes/test.example.com.rb new file mode 100644 index 0000000000..9c374395bf --- /dev/null +++ b/chef/spec/data/nodes/test.example.com.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" diff --git a/chef/spec/data/nodes/test.rb b/chef/spec/data/nodes/test.rb new file mode 100644 index 0000000000..d968816d60 --- /dev/null +++ b/chef/spec/data/nodes/test.rb @@ -0,0 +1,15 @@ +## +# Nodes should have a unique name +## +name "test.example.com short" + +## +# Nodes can set arbitrary arguments +## +sunshine "in" +something "else" + +## +# Nodes should have recipes +## +recipes "operations-master", "operations-monitoring" diff --git a/chef/spec/data/recipes/test.rb b/chef/spec/data/recipes/test.rb new file mode 100644 index 0000000000..1c94b917f0 --- /dev/null +++ b/chef/spec/data/recipes/test.rb @@ -0,0 +1,7 @@ + +file "/etc/nsswitch.conf" do + action "create" + owner "root" + group "root" + mode 0644 +end diff --git a/chef/spec/data/seattle.txt b/chef/spec/data/seattle.txt new file mode 100644 index 0000000000..19f6290939 --- /dev/null +++ b/chef/spec/data/seattle.txt @@ -0,0 +1 @@ +Seattle is a great town. Next time you visit, you should buy me a beer.
\ No newline at end of file diff --git a/chef/spec/lib/chef/provider/easy.rb b/chef/spec/lib/chef/provider/easy.rb new file mode 100644 index 0000000000..b637efa768 --- /dev/null +++ b/chef/spec/lib/chef/provider/easy.rb @@ -0,0 +1,37 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Provider + class Easy < Chef::Provider + def load_current_resource + true + end + + def action_sell + true + end + + def action_buy + true + end + end + end +end
\ No newline at end of file diff --git a/chef/spec/lib/chef/provider/snakeoil.rb b/chef/spec/lib/chef/provider/snakeoil.rb new file mode 100644 index 0000000000..0486402686 --- /dev/null +++ b/chef/spec/lib/chef/provider/snakeoil.rb @@ -0,0 +1,37 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Provider + class SnakeOil < Chef::Provider + def load_current_resource + true + end + + def action_sell + true + end + + def action_buy + true + end + end + end +end
\ No newline at end of file diff --git a/chef/spec/lib/chef/resource/cat.rb b/chef/spec/lib/chef/resource/cat.rb new file mode 100644 index 0000000000..e651c44512 --- /dev/null +++ b/chef/spec/lib/chef/resource/cat.rb @@ -0,0 +1,42 @@ +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Resource + class Cat < Chef::Resource + + attr_accessor :action + + def initialize(name, collection=nil, node=nil) + @resource_name = :cat + super(name, collection, node) + @action = "sell" + end + + def pretty_kitty(arg=nil) + set_if_args(@pretty_kitty, arg) do + case arg + when true, false + @pretty_kitty = arg + end + end + end + end + end +end
\ No newline at end of file diff --git a/chef/spec/lib/chef/resource/zen_master.rb b/chef/spec/lib/chef/resource/zen_master.rb new file mode 100644 index 0000000000..8b4225fbd4 --- /dev/null +++ b/chef/spec/lib/chef/resource/zen_master.rb @@ -0,0 +1,44 @@ +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Resource + class ZenMaster < Chef::Resource + attr_reader :peace + + def initialize(name, collection=nil, node=nil) + @resource_name = :zen_master + super(name, collection, node) + end + + def peace(tf) + @peace = tf + end + + def something(arg=nil) + set_if_args(@something, arg) do + case arg + when true, false + @something = arg + end + end + end + end + end +end
\ No newline at end of file diff --git a/chef/spec/rcov.opts b/chef/spec/rcov.opts new file mode 100644 index 0000000000..484626ea9c --- /dev/null +++ b/chef/spec/rcov.opts @@ -0,0 +1,2 @@ +--exclude +spec,bin,/Library/Ruby diff --git a/chef/spec/spec.opts b/chef/spec/spec.opts new file mode 100644 index 0000000000..c9c9b4ddf0 --- /dev/null +++ b/chef/spec/spec.opts @@ -0,0 +1,3 @@ +--color +--loadby +mtime diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb new file mode 100644 index 0000000000..9120b2876c --- /dev/null +++ b/chef/spec/spec_helper.rb @@ -0,0 +1,23 @@ +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +require File.join(File.dirname(__FILE__), "..", "lib", "chef") +Dir[File.join(File.dirname(__FILE__), 'lib', '**', '*.rb')].sort.each { |lib| require lib } + +Chef::Config.log_level(:error) diff --git a/chef/spec/unit/chef_spec.rb b/chef/spec/unit/chef_spec.rb new file mode 100644 index 0000000000..7d2af6f88d --- /dev/null +++ b/chef/spec/unit/chef_spec.rb @@ -0,0 +1,25 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 do + it "should have a version defined" do + Chef::VERSION.should match(/(\d+)\.(\d+)\.(\d+)/) + end +end diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb new file mode 100644 index 0000000000..a2732f28c3 --- /dev/null +++ b/chef/spec/unit/client_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Client, "initialize" do + it "should create a new Chef::Client object" do + Chef::Client.new.should be_kind_of(Chef::Client) + end +end + +describe Chef::Client, "build_node" do + before(:each) do + @mock_facter_fqdn = mock("Facter FQDN") + @mock_facter_fqdn.stub!(:value).and_return("foo.bar.com") + @mock_facter_hostname = mock("Facter Hostname") + @mock_facter_hostname.stub!(:value).and_return("foo") + Facter.stub!(:[]).with("fqdn").and_return(@mock_facter_fqdn) + Facter.stub!(:[]).with("hostname").and_return(@mock_facter_hostname) + Facter.stub!(:each).and_return(true) + @client = Chef::Client.new + end + + it "should set the name equal to the FQDN" do + @client.build_node + @client.node.name.should eql("foo.bar.com") + end + + it "should set the name equal to the hostname if FQDN is not available" do + @mock_facter_fqdn.stub!(:value).and_return(nil) + @client.build_node + @client.node.name.should eql("foo") + end +end + +describe Chef::Client, "register" do + before(:each) do + @client = Chef::Client.new + end + + it "should check to see if it's already registered" + + it "should create a new passphrase if not registered" + + it "should create a new registration if it has not registered" +end
\ No newline at end of file diff --git a/chef/spec/unit/compile_spec.rb b/chef/spec/unit/compile_spec.rb new file mode 100644 index 0000000000..c7f76a4a58 --- /dev/null +++ b/chef/spec/unit/compile_spec.rb @@ -0,0 +1,71 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Compile do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "nodes")) + Chef::Config.cookbook_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "cookbooks")) + @compile = Chef::Compile.new + end + + it "should create a new Chef::Compile" do + @compile.should be_a_kind_of(Chef::Compile) + end + + it "should have a Chef::CookbookLoader" do + @compile.cookbook_loader.should be_a_kind_of(Chef::CookbookLoader) + end + + it "should have a Chef::ResourceCollection" do + @compile.collection.should be_a_kind_of(Chef::ResourceCollection) + end + + it "should have a hash of Definitions" do + @compile.definitions.should be_a_kind_of(Hash) + end + + it "should load a node by name" do + node = Chef::Node.new + Chef::Node.stub!(:load).and_return(node) + lambda { + @compile.load_node("compile") + }.should_not raise_error + @compile.node.name.should == "compile" + end + + it "should load all the definitions" do + lambda { @compile.load_definitions }.should_not raise_error + @compile.definitions.should have_key(:new_cat) + end + + it "should load all the recipes specified for this node" do + node = Chef::Node.new + Chef::Node.stub!(:load).and_return(node) + @compile.load_node("compile") + @compile.load_definitions + lambda { @compile.load_recipes }.should_not raise_error + @compile.collection[0].to_s.should == "cat[einstein]" + @compile.collection[1].to_s.should == "cat[loulou]" + @compile.collection[2].to_s.should == "cat[birthday]" + @compile.collection[3].to_s.should == "cat[peanut]" + @compile.collection[4].to_s.should == "cat[fat peanut]" + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/config_spec.rb b/chef/spec/unit/config_spec.rb new file mode 100644 index 0000000000..51e33401d8 --- /dev/null +++ b/chef/spec/unit/config_spec.rb @@ -0,0 +1,91 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Config do + + it "should load a .rb file in context" do + lambda { + Chef::Config.from_file(File.join(File.dirname(__FILE__), "..", "data", "config.rb")) + }.should_not raise_error + end + + it "should raise an ArgumentError with an explanation if you try and set a non-existent variable" do + lambda { + Chef::Config.from_file(File.join(File.dirname(__FILE__), "..", "data", "bad-config.rb")) + }.should raise_error(ArgumentError) + end + + it "should raise an IOError if it can't find the file" do + lambda { + Chef::Config.from_file("/tmp/timmytimmytimmy") + }.should raise_error(IOError) + end + + it "should have a default cookbook_path" do + Chef::Config.cookbook_path.should be_kind_of(Array) + end + + it "should allow you to set a cookbook_path with a string" do + Chef::Config.cookbook_path("/etc/chef/cookbook") + Chef::Config.cookbook_path.should eql("/etc/chef/cookbook") + end + + it "should allow you to set a cookbook_path with multiple strings" do + Chef::Config.cookbook_path("/etc/chef/cookbook", "/etc/chef/upstream-cookbooks") + Chef::Config.cookbook_path.should eql([ + "/etc/chef/cookbook", + "/etc/chef/upstream-cookbooks" + ]) + end + + it "should allow you to set a cookbook_path with an array" do + Chef::Config.cookbook_path ["one", "two"] + Chef::Config.cookbook_path.should eql(["one", "two"]) + end + + it "should allow you to reference a value by index" do + Chef::Config[:cookbook_path].should be_kind_of(Array) + end + + it "should allow you to set a value by index" do + Chef::Config[:cookbook_path] = "one" + Chef::Config[:cookbook_path].should == "one" + end + + it "should allow you to set config values with a block" do + Chef::Config.configure do |c| + c[:cookbook_path] = "monkey_rabbit" + c[:otherthing] = "boo" + end + Chef::Config.cookbook_path.should == "monkey_rabbit" + Chef::Config.otherthing.should == "boo" + end + + it "should raise an ArgumentError if you access a config option that does not exist" do + lambda { Chef::Config[:snob_hobbery] }.should raise_error(ArgumentError) + end + + it "should return true or false with has_key?" do + Chef::Config.has_key?(:monkey).should eql(false) + Chef::Config[:monkey] = "gotcha" + Chef::Config.has_key?(:monkey).should eql(true) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/cookbook_loader_spec.rb b/chef/spec/unit/cookbook_loader_spec.rb new file mode 100644 index 0000000000..501329777b --- /dev/null +++ b/chef/spec/unit/cookbook_loader_spec.rb @@ -0,0 +1,117 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::CookbookLoader do + before(:each) do + Chef::Config.cookbook_path [ + File.join(File.dirname(__FILE__), "..", "data", "cookbooks"), + File.join(File.dirname(__FILE__), "..", "data", "kitchen") + ] + @cl = Chef::CookbookLoader.new() + end + + it "should be a Chef::CookbookLoader object" do + @cl.should be_kind_of(Chef::CookbookLoader) + end + + it "should return cookbook objects with []" do + @cl[:openldap].should be_a_kind_of(Chef::Cookbook) + end + + it "should raise an exception if it cannot find a cookbook with []" do + lambda { @cl[:monkeypoop] }.should raise_error(ArgumentError) + end + + it "should allow you to look up available cookbooks with [] and a symbol" do + @cl[:openldap].name.should eql(:openldap) + end + + it "should allow you to look up available cookbooks with [] and a string" do + @cl["openldap"].name.should eql(:openldap) + end + + it "should allow you to iterate over cookbooks with each" do + seen = Hash.new + @cl.each do |cb| + seen[cb.name] = true + end + seen.should have_key(:openldap) + seen.should have_key(:apache2) + end + + it "should find all the cookbooks in the cookbook path" do + Chef::Config.cookbook_path << File.join(File.dirname(__FILE__), "..", "data", "hidden-cookbooks") + @cl.load_cookbooks + @cl.detect { |cb| cb.name == :openldap }.should_not eql(nil) + @cl.detect { |cb| cb.name == :apache2 }.should_not eql(nil) + end + + it "should allow you to override an attribute file via cookbook_path" do + @cl[:openldap].attribute_files.detect { |f| + f =~ /cookbooks\/openldap\/attributes\/default.rb/ + }.should_not eql(nil) + @cl[:openldap].attribute_files.detect { |f| + f =~ /kitchen\/openldap\/attributes\/default.rb/ + }.should eql(nil) + end + + it "should load different attribute files from deeper paths" do + @cl[:openldap].attribute_files.detect { |f| + f =~ /kitchen\/openldap\/attributes\/robinson.rb/ + }.should_not eql(nil) + end + + it "should allow you to override a definition file via cookbook_path" do + @cl[:openldap].definition_files.detect { |f| + f =~ /cookbooks\/openldap\/definitions\/client.rb/ + }.should_not eql(nil) + @cl[:openldap].definition_files.detect { |f| + f =~ /kitchen\/openldap\/definitions\/client.rb/ + }.should eql(nil) + end + + it "should load definition files from deeper paths" do + @cl[:openldap].definition_files.detect { |f| + f =~ /kitchen\/openldap\/definitions\/drewbarrymore.rb/ + }.should_not eql(nil) + end + + it "should allow you to override a recipe file via cookbook_path" do + @cl[:openldap].recipe_files.detect { |f| + f =~ /cookbooks\/openldap\/recipes\/gigantor.rb/ + }.should_not eql(nil) + @cl[:openldap].recipe_files.detect { |f| + f =~ /kitchen\/openldap\/recipes\/gigantor.rb/ + }.should eql(nil) + end + + it "should load recipe files from deeper paths" do + @cl[:openldap].recipe_files.detect { |f| + f =~ /kitchen\/openldap\/recipes\/woot.rb/ + }.should_not eql(nil) + end + + it "should allow you to have an 'ignore' file, which skips loading files in later cookbooks" do + @cl[:openldap].recipe_files.detect { |f| + f =~ /kitchen\/openldap\/recipes\/ignoreme.rb/ + }.should eql(nil) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/cookbook_spec.rb b/chef/spec/unit/cookbook_spec.rb new file mode 100644 index 0000000000..85b4040771 --- /dev/null +++ b/chef/spec/unit/cookbook_spec.rb @@ -0,0 +1,142 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Cookbook do + COOKBOOK_PATH = File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap") + + before(:each) do + @cookbook = Chef::Cookbook.new("openldap") + end + + it "should be a Chef::Cookbook object" do + @cookbook.should be_kind_of(Chef::Cookbook) + end + + it "should have a name" do + @cookbook.name.should eql("openldap") + end + + it "should have a list of attribute files" do + @cookbook.attribute_files.should be_kind_of(Array) + end + + it "should allow you to set the list of attribute files" do + @cookbook.attribute_files = [ "one", "two" ] + @cookbook.attribute_files.should eql(["one", "two"]) + end + + it "should allow you to load all the attributes" do + node = Chef::Node.new + node.name "Julia Child" + node.chef_env false + @cookbook.attribute_files = Dir[File.join(COOKBOOK_PATH, "attributes", "**", "*.rb")] + node = @cookbook.load_attributes(node) + node.ldap_server.should eql("ops1prod") + node.ldap_basedn.should eql("dc=hjksolutions,dc=com") + node.ldap_replication_password.should eql("forsure") + node.smokey.should eql("robinson") + end + + it "should raise an ArgumentError if you don't pass a node object to load_attributes" do + lambda { @cookbook.load_attributes("snake oil") }.should raise_error(ArgumentError) + end + + it "should have a list of definition files" do + @cookbook.definition_files.should be_a_kind_of(Array) + end + + it "should allow you to set the list of definition files" do + @cookbook.definition_files = [ "one", "two" ] + @cookbook.definition_files.should eql(["one", "two"]) + end + + it "should allow you to load all the definitions, returning a hash of ResourceDefinitions by name" do + @cookbook.definition_files = Dir[File.join(COOKBOOK_PATH, "definitions", "**", "*.rb")] + defs = @cookbook.load_definitions + defs.has_key?(:openldap_server).should eql(true) + defs[:openldap_server].should be_a_kind_of(Chef::ResourceDefinition) + defs.has_key?(:openldap_client).should eql(true) + defs[:openldap_client].should be_a_kind_of(Chef::ResourceDefinition) + end + + it "should have a list of recipe files" do + @cookbook.recipe_files.should be_a_kind_of(Array) + end + + it "should allow you to set the list of recipe files" do + @cookbook.recipe_files = [ "one", "two" ] + @cookbook.recipe_files.should eql(["one", "two"]) + end + + it "should have a list of recipes by name" do + @cookbook.recipe_files = [ "one", "two" ] + @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one") + @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two") + end + + it "should take a file /path.rb, and use the filename minus rb as a recipe name" do + @cookbook.recipe_files = [ "/something/one.rb", "/otherthing/two.rb" ] + @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one") + @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two") + end + + it "should take a file path.rb, and use the filename minus rb as a recipe name" do + @cookbook.recipe_files = [ "one.rb", "two.rb" ] + @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one") + @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two") + end + + it "should allow you to test for a recipe with recipe?" do + @cookbook.recipe_files = [ "one", "two" ] + @cookbook.recipe?("one").should eql(true) + @cookbook.recipe?("shanghai").should eql(false) + end + + it "should allow you to test for a recipe? with a fq recipe name" do + @cookbook.recipe_files = [ "one", "two" ] + @cookbook.recipe?("openldap::one").should eql(true) + @cookbook.recipe?("shanghai::city").should eql(false) + end + + it "should allow you to run a recipe by name via load_recipe" do + @cookbook.recipe_files = Dir[File.join(COOKBOOK_PATH, "recipes", "**", "*.rb")] + node = Chef::Node.new + node.name "Julia Child" + recipe = @cookbook.load_recipe("openldap::gigantor", node) + recipe.recipe_name.should eql("gigantor") + recipe.cookbook_name.should eql("openldap") + recipe.collection[0].name.should eql("blanket") + end + + it "should raise an ArgumentException if you try to load a bad recipe name" do + node = Chef::Node.new + node.name "Julia Child" + lambda { @cookbook.load_recipe("smackdown", node) }.should raise_error(ArgumentError) + end + + it "should load the attributes if it has not already when a recipe is loaded" do + @cookbook.attribute_files = Dir[File.join(COOKBOOK_PATH, "attributes", "smokey.rb")] + @cookbook.recipe_files = Dir[File.join(COOKBOOK_PATH, "recipes", "**", "*.rb")] + node = Chef::Node.new + node.name "Julia Child" + recipe = @cookbook.load_recipe("openldap::gigantor", node) + node.smokey.should == "robinson" + end +end
\ No newline at end of file diff --git a/chef/spec/unit/couchdb_spec.rb b/chef/spec/unit/couchdb_spec.rb new file mode 100644 index 0000000000..621439df51 --- /dev/null +++ b/chef/spec/unit/couchdb_spec.rb @@ -0,0 +1,212 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::CouchDB, "new" do + it "should create a new Chef::REST object from the default url" do + Chef::Config[:couchdb_url] = "http://monkey" + Chef::REST.should_receive(:new).with("http://monkey").and_return(true) + Chef::CouchDB.new + end + + it "should create a new Chef::REST object from a provided url" do + Chef::REST.should_receive(:new).with("http://monkeypants").and_return(true) + Chef::CouchDB.new("http://monkeypants") + end +end + +describe Chef::CouchDB, "create_db" do + before(:each) do + @mock_rest = mock("Chef::REST", :null_object => true) + @mock_rest.stub!(:get_rest).and_return([ "chef" ]) + @mock_rest.stub!(:put_rest).and_return(true) + Chef::REST.stub!(:new).and_return(@mock_rest) + end + + def do_create_db + couch = Chef::CouchDB.new + couch.create_db + end + + it "should get a list of current databases" do + @mock_rest.should_receive(:get_rest).and_return(["chef"]) + do_create_db + end + + it "should create the chef database if it does not exist" do + @mock_rest.stub!(:get_rest).and_return([]) + @mock_rest.should_receive(:put_rest).with("chef", {}).and_return(true) + do_create_db + end + + it "should not create the chef database if it does exist" do + @mock_rest.stub!(:get_rest).and_return(["chef"]) + @mock_rest.should_not_receive(:put_rest) + do_create_db + end + + it "should return 'chef'" do + do_create_db.should eql("chef") + end +end + +describe Chef::CouchDB, "create_design_document" do + before(:each) do + @mock_rest = mock("Chef::REST", :null_object => true) + @mock_design = { + "version" => 1, + "_rev" => 1 + } + @mock_data = { + "version" => 1, + "language" => "javascript", + "views" => { + "all" => { + "map" => <<-EOJS + function(doc) { + if (doc.chef_type == "node") { + emit(doc.name, doc); + } + } + EOJS + }, + } + } + @mock_rest.stub!(:get_rest).and_return(@mock_design) + @mock_rest.stub!(:put_rest).and_return(true) + Chef::REST.stub!(:new).and_return(@mock_rest) + end + + def do_create_design_document + couchdb = Chef::CouchDB.new + couchdb.create_design_document("bob", @mock_data) + end + + it "should fetch the existing design document" do + @mock_rest.should_receive(:get_rest).with("chef/_design%2Fbob") + do_create_design_document + end + + it "should populate the _rev in the new design if the versions dont match" do + @mock_data["version"] = 2 + do_create_design_document + @mock_data["_rev"].should eql(1) + end + + it "should create the view if it requires updating" do + @mock_data["version"] = 2 + @mock_rest.should_receive(:put_rest).with("chef/_design%2Fbob", @mock_data) + do_create_design_document + end + + it "should not create the view if it does not require updating" do + @mock_data["version"] = 1 + @mock_rest.should_not_receive(:put_rest) + do_create_design_document + end +end + +describe Chef::CouchDB, "store" do + it "should put the object into couchdb" do + @mock_rest = mock("Chef::REST", :null_object => true) + @mock_rest.should_receive(:put_rest).with("chef/node_bob", {}).and_return(true) + Chef::REST.stub!(:new).and_return(@mock_rest) + Chef::CouchDB.new.store("node", "bob", {}) + end +end + +describe Chef::CouchDB, "load" do + it "should load the object from couchdb" do + @mock_rest = mock("Chef::REST", :null_object => true) + @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(true) + Chef::REST.stub!(:new).and_return(@mock_rest) + Chef::CouchDB.new.load("node", "bob").should eql(true) + end +end + +describe Chef::CouchDB, "delete" do + before(:each) do + @mock_current = { + "version" => 1, + "_rev" => 1 + } + @mock_rest = mock("Chef::REST", :null_object => true) + @mock_rest.stub!(:get_rest).and_return(@mock_current) + @mock_rest.stub!(:delete_rest).and_return(true) + Chef::REST.stub!(:new).and_return(@mock_rest) + end + + def do_delete(rev=nil) + Chef::REST.stub!(:new).and_return(@mock_rest) + Chef::CouchDB.new.delete("node", "bob", rev) + end + + it "should remove the object from couchdb with a specific revision" do + @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1") + do_delete(1) + end + + it "should remove the object from couchdb based on the couchdb_rev of the current obj" do + mock_real = mock("Inflated Object") + mock_real.stub!(:respond_to?).and_return(true) + mock_real.stub!(:couchdb_rev).and_return(2) + @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(mock_real) + @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=2") + do_delete + end + + it "should remove the object from couchdb based on the current objects rev" do + @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1") + do_delete + end +end + +describe Chef::CouchDB, "list" do + before(:each) do + @mock_rest = mock("Chef::REST", :null_object => true) + Chef::REST.stub!(:new).and_return(@mock_rest) + end + + it "should get the view for all objects if inflate is true" do + @mock_rest.should_receive(:get_rest).with("chef/_view/node/all").and_return(true) + Chef::CouchDB.new.list("node", true) + end + + it "should get the view for just the object id's if inflate is false" do + @mock_rest.should_receive(:get_rest).with("chef/_view/node/all_id").and_return(true) + Chef::CouchDB.new.list("node", false) + end +end + +describe Chef::CouchDB, "has_key?" do + before(:each) do + @mock_rest = mock("Chef::REST", :null_object => true) + Chef::REST.stub!(:new).and_return(@mock_rest) + end + + it "should return true if the object exists" do + @mock_rest.should_receive(:get_rest).and_return(true) + Chef::CouchDB.new.has_key?("node", "bob").should eql(true) + end + + it "should return false if the object does not exist" do + @mock_rest.should_receive(:get_rest).and_raise(ArgumentError) + Chef::CouchDB.new.has_key?("node", "bob").should eql(false) + end +end diff --git a/chef/spec/unit/file_cache_spec.rb b/chef/spec/unit/file_cache_spec.rb new file mode 100644 index 0000000000..c84eea376e --- /dev/null +++ b/chef/spec/unit/file_cache_spec.rb @@ -0,0 +1,124 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::FileCache, "store method" do + before(:each) do + Chef::Config[:file_cache_path] = "/tmp/foo" + Dir.stub!(:mkdir).and_return(true) + File.stub!(:directory?).and_return(true) + @io = mock("IO", { :print => true, :close => true }) + File.stub!(:open).and_return(@io) + end + + it "should create the directories leading up to bang" do + File.stub!(:directory?).and_return(false) + Dir.should_receive(:mkdir).with("/tmp").and_return(true) + Dir.should_receive(:mkdir).with("/tmp/foo").and_return(true) + Dir.should_receive(:mkdir).with("/tmp/foo/whiz").and_return(true) + Dir.should_not_receive(:mkdir).with("/tmp/foo/whiz/bang").and_return(true) + Chef::FileCache.store("whiz/bang", "I found a poop") + end + + it "should create a file at /tmp/foo/whiz/bang" do + File.should_receive(:open).with("/tmp/foo/whiz/bang", "w").and_return(@io) + Chef::FileCache.store("whiz/bang", "I found a poop") + end + + it "should print the contents to the file" do + @io.should_receive(:print).with("I found a poop") + Chef::FileCache.store("whiz/bang", "I found a poop") + end + + it "should close the file" do + @io.should_receive(:close) + Chef::FileCache.store("whiz/bang", "I found a poop") + end + +end + +describe Chef::FileCache, "load method" do + before(:each) do + Chef::Config[:file_cache_path] = "/tmp/foo" + Dir.stub!(:mkdir).and_return(true) + File.stub!(:directory?).and_return(true) + File.stub!(:exists?).and_return(true) + File.stub!(:read).and_return("I found a poop") + end + + it "should find the full path to whiz/bang" do + File.should_receive(:read).with("/tmp/foo/whiz/bang").and_return(true) + Chef::FileCache.load('whiz/bang') + end + + it "should raise a Chef::Exception::FileNotFound if the file doesn't exist" do + File.stub!(:exists?).and_return(false) + lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exception::FileNotFound) + end +end + +describe Chef::FileCache, "delete method" do + before(:each) do + Chef::Config[:file_cache_path] = "/tmp/foo" + Dir.stub!(:mkdir).and_return(true) + File.stub!(:directory?).and_return(true) + File.stub!(:exists?).and_return(true) + File.stub!(:unlink).and_return(true) + end + + it "should unlink the full path to whiz/bang" do + File.should_receive(:unlink).with("/tmp/foo/whiz/bang").and_return(true) + Chef::FileCache.delete("whiz/bang") + end + +end + +describe Chef::FileCache, "list method" do + before(:each) do + Chef::Config[:file_cache_path] = "/tmp/foo" + Dir.stub!(:[]).and_return(["/tmp/foo/whiz/bang", "/tmp/foo/snappy/patter"]) + File.stub!(:file?).and_return(true) + end + + it "should return the relative paths" do + Chef::FileCache.list.should eql([ "whiz/bang", "snappy/patter" ]) + end +end + +describe Chef::FileCache, "has_key? method" do + before(:each) do + Chef::Config[:file_cache_path] = "/tmp/foo" + end + + it "should check the full path to the file" do + File.should_receive(:exists?).with("/tmp/foo/whiz/bang") + Chef::FileCache.has_key?("whiz/bang") + end + + it "should return true if the file exists" do + File.stub!(:exists?).and_return(true) + Chef::FileCache.has_key?("whiz/bang").should eql(true) + end + + it "should return false if the file does not exist" do + File.stub!(:exists?).and_return(false) + Chef::FileCache.has_key?("whiz/bang").should eql(false) + end +end + diff --git a/chef/spec/unit/file_store_spec.rb b/chef/spec/unit/file_store_spec.rb new file mode 100644 index 0000000000..8addeb9e31 --- /dev/null +++ b/chef/spec/unit/file_store_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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")) + +class Fakestore + attr_accessor :name + + def to_json(*a) + { :name => @name }.to_json(*a) + end + + def self.json_create(o) + new_fakestore = new + new_fakestore.name = o[:name] + new_fakestore + end +end + +describe Chef::FileStore do + before(:each) do + Chef::Config[:file_store_path] = "/tmp/chef-test" + @fakestore = Fakestore.new + @fakestore.name = "Landslide" + @fakestore_digest = "a56a428bddac69e505731708ba206da0bb75e8de883bb4d5ef6be9b327da556a" + end + + it "should return a path to a file given a type and key" do + Dir.stub!(:mkdir).and_return(true) + File.stub!(:directory?).and_return(true) + path = Chef::FileStore.create_store_path("fakestore", @fakestore.name) + path.should eql("/tmp/chef-test/fakestore/a/56a/Landslide") + end + + it "should create directories for the path if needed" do + File.stub!(:directory?).and_return(false) + Dir.should_receive(:mkdir).exactly(4).times.and_return(true) + Chef::FileStore.create_store_path("fakestore", @fakestore.name) + end + + it "should store an object with a type and key" do + Chef::FileStore.should_receive(:create_store_path).with("fakestore", @fakestore.name).and_return("/monkey") + File.stub!(:directory?).and_return(true) + ioobj = mock("IO", :null_object => true) + ioobj.should_receive(:puts).with(@fakestore.to_json) + ioobj.should_receive(:close).once.and_return(true) + File.should_receive(:open).with("/monkey", "w").and_return(ioobj) + Chef::FileStore.store("fakestore", @fakestore.name, @fakestore) + end + + it "should load an object from the store with type and key" do + Chef::FileStore.should_receive(:create_store_path).with("fakestore", @fakestore.name).and_return("/monkey") + File.stub!(:exists?).and_return(true) + IO.should_receive(:read).once.and_return(true) + JSON.should_receive(:parse).and_return(true) + Chef::FileStore.load("fakestore", @fakestore.name) + end + + it "should through an exception if it cannot load a file from the store" do + Chef::FileStore.should_receive(:create_store_path).and_return("/tmp") + File.stub!(:exists?).and_return(false) + lambda { Chef::FileStore.load("fakestore", @fakestore.name) }.should raise_error(RuntimeError) + end + + it "should delete a file from the store if it exists" do + Chef::FileStore.should_receive(:create_store_path).with("node", "nothing").and_return("/tmp/foolio") + File.stub!(:exists?).and_return(true) + File.should_receive(:unlink).with("/tmp/foolio").and_return(1) + Chef::FileStore.delete("node", "nothing") + end + + it "should list all the keys of a particular type" do + Dir.should_receive(:[]).with("/tmp/chef-test/node/**/*").and_return(["pool"]) + File.should_receive(:file?).with("pool").and_return(true) + Chef::FileStore.list("node").should eql(["pool"]) + end + + it "should return all the documents of a particular type with list and inflate" do + Dir.stub!(:[]).and_return(["/foo/pool"]) + File.stub!(:file?).and_return(true) + Chef::FileStore.should_receive(:load).with("node", "pool").and_return("monkey") + Chef::FileStore.list("node", true).should eql(["monkey"]) + end + + it "should let you test whether a key doesnt exist for an object type with has_key?" do + Dir.should_receive(:[]).with("/tmp/chef-test/node/**/*").and_return(["pool"]) + File.should_receive(:file?).with("pool").and_return(true) + Chef::FileStore.has_key?("node", "snake").should eql(false) + end +end
\ No newline at end of file diff --git a/chef/spec/unit/log/formatter_spec.rb b/chef/spec/unit/log/formatter_spec.rb new file mode 100644 index 0000000000..e0d8bfd773 --- /dev/null +++ b/chef/spec/unit/log/formatter_spec.rb @@ -0,0 +1,51 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'time' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Log::Formatter do + before(:each) do + @formatter = Chef::Log::Formatter.new + end + + it "should print raw strings with msg2str(string)" do + @formatter.msg2str("nuthin new").should == "nuthin new" + end + + it "should format exceptions properly with msg2str(e)" do + e = IOError.new("legendary roots crew") + @formatter.msg2str(e).should == "legendary roots crew (IOError)\n" + end + + it "should format random objects via inspect with msg2str(Object)" do + @formatter.msg2str([ "black thought", "?uestlove" ]).should == '["black thought", "?uestlove"]' + end + + it "should return a formatted string with call" do + time = Time.new + Chef::Log::Formatter.show_time = true + @formatter.call("monkey", time, "test", "mos def").should == "[#{time.rfc2822}] monkey: mos def\n" + end + + it "should allow you to turn the time on and off in the output" do + Chef::Log::Formatter.show_time = false + @formatter.call("monkey", Time.new, "test", "mos def").should == "monkey: mos def\n" + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/log_spec.rb b/chef/spec/unit/log_spec.rb new file mode 100644 index 0000000000..14892b1af4 --- /dev/null +++ b/chef/spec/unit/log_spec.rb @@ -0,0 +1,62 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'tempfile' +require 'logger' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe Chef::Log do + it "should accept regular options to Logger.new via init" do + tf = Tempfile.new("chef-test-log") + tf.open + lambda { Chef::Log.init(STDOUT) }.should_not raise_error + lambda { Chef::Log.init(tf) }.should_not raise_error + end + + it "should set the log level with :debug, :info, :warn, :error, or :fatal" do + levels = { + :debug => Logger::DEBUG, + :info => Logger::INFO, + :warn => Logger::WARN, + :error => Logger::ERROR, + :fatal => Logger::FATAL + } + levels.each do |symbol, constant| + Chef::Log.level(symbol) + Chef::Log.logger.level.should == constant + end + end + + it "should raise an ArgumentError if you try and set the level to something strange" do + lambda { Chef::Log.level(:the_roots) }.should raise_error(ArgumentError) + end + + it "should pass other method calls directly to logger" do + Chef::Log.level(:debug) + Chef::Log.should be_debug + lambda { Chef::Log.debug("Gimme some sugar!") }.should_not raise_error + end + + it "should default to STDOUT if init is called with no arguments" do + logger_mock = mock(Logger, :null_object => true) + Logger.stub!(:new).and_return(logger_mock) + Logger.should_receive(:new).with(STDOUT).and_return(logger_mock) + Chef::Log.init + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/mixin/params_validate_spec.rb b/chef/spec/unit/mixin/params_validate_spec.rb new file mode 100644 index 0000000000..937589b059 --- /dev/null +++ b/chef/spec/unit/mixin/params_validate_spec.rb @@ -0,0 +1,331 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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")) + +class TinyClass + include Chef::Mixin::ParamsValidate + + def music(is_good=true) + is_good + end +end + +describe Chef::Mixin::ParamsValidate do + before(:each) do + @vo = TinyClass.new() + end + + it "should allow a hash and a hash as arguments to validate" do + lambda { @vo.validate({:one => "two"}, {}) }.should_not raise_error(ArgumentError) + end + + it "should raise an argument error if validate is called incorrectly" do + lambda { @vo.validate("one", "two") }.should raise_error(ArgumentError) + end + + it "should require validation map keys to be symbols or strings" do + lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError) + lambda { @vo.validate({:one => "two"}, { "one" => true }) }.should_not raise_error(ArgumentError) + lambda { @vo.validate({:one => "two"}, { Hash.new => true }) }.should raise_error(ArgumentError) + end + + it "should allow options to be required with true" do + lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError) + end + + it "should allow options to be optional with false" do + lambda { @vo.validate({}, {:one => false})}.should_not raise_error(ArgumentError) + end + + it "should allow you to check what kind_of? thing an argument is with kind_of" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => String + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => Array + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should allow you to specify an argument is required with required" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :required => true + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:two => "string"}, + { + :one => { + :required => true + } + } + ) + }.should raise_error(ArgumentError) + + lambda { + @vo.validate( + {:two => "string"}, + { + :one => { + :required => false + } + } + ) + }.should_not raise_error(ArgumentError) + end + + it "should allow you to specify whether an object has a method with respond_to" do + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => "validate" + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => "monkey" + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should allow you to specify whether an object has all the given methods with respond_to and an array" do + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => ["validate", "music"] + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + {:one => @vo}, + { + :one => { + :respond_to => ["monkey", "validate"] + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you set a default value with default => value" do + arguments = Hash.new + @vo.validate(arguments, { + :one => { + :default => "is the loneliest number" + } + }) + arguments[:one].should == "is the loneliest number" + end + + it "should let you check regular expressions" do + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :regex => /^is good$/ + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :regex => /^is bad$/ + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you specify your own callbacks" do + lambda { + @vo.validate( + { :one => "is good" }, + { + :one => { + :callbacks => { + "should be equal to is good" => lambda { |a| + a == "is good" + }, + } + } + } + ) + }.should_not raise_error(ArgumentError) + + lambda { + @vo.validate( + { :one => "is bad" }, + { + :one => { + :callbacks => { + "should be equal to 'is good'" => lambda { |a| + a == "is good" + }, + } + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should let you combine checks" do + args = { :one => "is good", :two => "is bad" } + lambda { + @vo.validate( + args, + { + :one => { + :kind_of => String, + :respond_to => [ :to_s, :upcase ], + :regex => /^is good/, + :callbacks => { + "should be your friend" => lambda { |a| + a == "is good" + } + }, + :required => true + }, + :two => { + :kind_of => String, + :required => false + }, + :three => { :default => "neato mosquito" } + } + ) + }.should_not raise_error(ArgumentError) + args[:three].should == "neato mosquito" + lambda { + @vo.validate( + args, + { + :one => { + :kind_of => String, + :respond_to => [ :to_s, :upcase ], + :regex => /^is good/, + :callbacks => { + "should be your friend" => lambda { |a| + a == "is good" + } + }, + :required => true + }, + :two => { + :kind_of => Hash, + :required => false + }, + :three => { :default => "neato mosquito" } + } + ) + }.should raise_error(ArgumentError) + end + + it "should raise an ArgumentError if the validation map has an unknown check" do + lambda { @vo.validate( + { :one => "two" }, + { + :one => { + :busted => "check" + } + } + ) + }.should raise_error(ArgumentError) + end + + it "should accept keys that are strings in the options" do + lambda { + @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }}) + }.should_not raise_error(ArgumentError) + end + + it "should allow an array to kind_of" do + lambda { + @vo.validate( + {:one => "string"}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => ["string"]}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should_not raise_error(ArgumentError) + lambda { + @vo.validate( + {:one => Hash.new}, + { + :one => { + :kind_of => [ String, Array ] + } + } + ) + }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/mixin/template_spec.rb b/chef/spec/unit/mixin/template_spec.rb new file mode 100644 index 0000000000..0ff929e8f6 --- /dev/null +++ b/chef/spec/unit/mixin/template_spec.rb @@ -0,0 +1,60 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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")) + +class TinyTemplateClass; include Chef::Mixin::Template; end + +describe Chef::Mixin::Template, "render_template" do + + before(:each) do + @template = "abcnews" + @context = { :fine => "dear" } + @eruby = mock(:erubis, { :evaluate => "elvis costello" }) + Erubis::Eruby.stub!(:new).and_return(@eruby) + @tempfile = mock(:tempfile, { :print => true, :close => true }) + Tempfile.stub!(:new).and_return(@tempfile) + @tiny_template = TinyTemplateClass.new + end + + it "should create a new Erubis object from the template" do + Erubis::Eruby.should_receive(:new).with("abcnews").and_return(@eruby) + @tiny_template.render_template(@template, @context) + end + + it "should evaluate the template with the provided context" do + @eruby.should_receive(:evaluate).with(@context).and_return(true) + @tiny_template.render_template(@template, @context) + end + + it "should create a tempfile for the resulting file" do + Tempfile.should_receive(:new).and_return(@tempfile) + @tiny_template.render_template(@template, @context) + end + + it "should print the contents of the resulting template to the tempfile" do + @tempfile.should_receive(:print).with("elvis costello").and_return(true) + @tiny_template.render_template(@template, @context) + end + + it "should close the tempfile" do + @tempfile.should_receive(:close).and_return(true) + @tiny_template.render_template(@template, @context) + end +end + diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb new file mode 100644 index 0000000000..605bb8b628 --- /dev/null +++ b/chef/spec/unit/node_spec.rb @@ -0,0 +1,326 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Node, "new method" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should create a new Chef::Node" do + @node.should be_a_kind_of(Chef::Node) + end +end + +describe Chef::Node, "name" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should allow you to set a name with name(something)" do + lambda { @node.name("latte") }.should_not raise_error + end + + it "should return the name with name()" do + @node.name("latte") + @node.name.should eql("latte") + end + + it "should always have a string for name" do + lambda { @node.name(Hash.new) }.should raise_error(ArgumentError) + end + +end + +describe Chef::Node, "attributes" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should have attributes" do + @node.attribute.should be_a_kind_of(Hash) + end + + it "should allow attributes to be accessed by name or symbol directly on node[]" do + @node.attribute["locust"] = "something" + @node[:locust].should eql("something") + @node["locust"].should eql("something") + end + + it "should return nil if it cannot find an attribute with node[]" do + @node["secret"].should eql(nil) + end + + it "should allow you to set an attribute via node[]=" do + @node["secret"] = "shush" + @node["secret"].should eql("shush") + end + + it "should allow you to query whether an attribute exists with attribute?" do + @node.attribute["locust"] = "something" + @node.attribute?("locust").should eql(true) + @node.attribute?("no dice").should eql(false) + end + + it "should allow you to set an attribute via method_missing" do + @node.sunshine "is bright" + @node.attribute[:sunshine].should eql("is bright") + end + + it "should allow you get get an attribute via method_missing" do + @node.sunshine "is bright" + @node.sunshine.should eql("is bright") + end + + it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do + lambda { @node.sunshine }.should raise_error(ArgumentError) + end + + it "should allow you to iterate over attributes with each_attribute" do + @node.sunshine "is bright" + @node.canada "is a nice place" + seen_attributes = Hash.new + @node.each_attribute do |a,v| + seen_attributes[a] = v + end + seen_attributes.should have_key(:sunshine) + seen_attributes.should have_key(:canada) + seen_attributes[:sunshine].should == "is bright" + seen_attributes[:canada].should == "is a nice place" + end + +end + +describe Chef::Node, "recipes" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should have an array of recipes that should be applied" do + @node.recipes.should be_a_kind_of(Array) + end + + it "should allow you to query whether or not it has a recipe applied with recipe?" do + @node.recipes << "sunrise" + @node.recipe?("sunrise").should eql(true) + @node.recipe?("not at home").should eql(false) + end + + it "should allow you to set recipes with arguments" do + @node.recipes "one", "two" + @node.recipe?("one").should eql(true) + @node.recipe?("two").should eql(true) + end + +end + +describe Chef::Node, "from file" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should load a node from a ruby file" do + @node.from_file(File.join(File.dirname(__FILE__), "..", "data", "nodes", "test.rb")) + @node.name.should eql("test.example.com short") + @node.sunshine.should eql("in") + @node.something.should eql("else") + @node.recipes.should eql(["operations-master", "operations-monitoring"]) + end + + it "should raise an exception if the file cannot be found or read" do + lambda { @node.from_file("/tmp/monkeydiving") }.should raise_error(IOError) + end +end + +describe Chef::Node, "find_file" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should load a node from a file by fqdn" do + @node.find_file("test.example.com") + @node.name.should == "test.example.com" + end + + it "should load a node from a file by hostname" do + File.stub!(:exists?).and_return(true) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) + @node.find_file("test.example.com") + @node.name.should == "test.example.com short" + end + + it "should load a node from the default file" do + File.stub!(:exists?).and_return(true) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false) + @node.find_file("test.example.com") + @node.name.should == "test.example.com default" + end + + it "should raise an ArgumentError if it cannot find any node file at all" do + File.stub!(:exists?).and_return(true) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false) + File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "default.rb")).and_return(false) + lambda { @node.find_file("test.example.com") }.should raise_error(ArgumentError) + end +end + +describe Chef::Node, "json" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should serialize itself as json" do + @node.find_file("test.example.com") + json = @node.to_json() + json.should =~ /json_class/ + json.should =~ /name/ + json.should =~ /attributes/ + json.should =~ /recipes/ + end + + it "should deserialize itself from json" do + @node.find_file("test.example.com") + json = @node.to_json + serialized_node = JSON.parse(json) + serialized_node.should be_a_kind_of(Chef::Node) + serialized_node.name.should eql(@node.name) + @node.each_attribute do |k,v| + serialized_node[k].should eql(v) + end + serialized_node.recipes.should eql(@node.recipes) + end +end + +describe Chef::Node, "to_index" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + @node.foo("bar") + end + + it "should return a hash with :index attributes" do + @node.name("airplane") + @node.to_index.should == { :foo => "bar", :index_name => "node", :id => "node_airplane", :name => "airplane" } + end +end + + +describe Chef::Node, "to_s" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should turn into a string like node[name]" do + @node.name("airplane") + @node.to_s.should eql("node[airplane]") + end +end + +describe Chef::Node, "list" do + before(:each) do + mock_couch = mock("Chef::CouchDB") + mock_couch.stub!(:list).and_return( + { + "rows" => [ + { + "value" => "a", + "key" => "avenue" + } + ] + } + ) + Chef::CouchDB.stub!(:new).and_return(mock_couch) + end + + it "should retrieve a list of nodes from CouchDB" do + Chef::Node.list.should eql(["avenue"]) + end + + it "should return just the ids if inflate is false" do + Chef::Node.list(false).should eql(["avenue"]) + end + + it "should return the full objects if inflate is true" do + Chef::Node.list(true).should eql(["a"]) + end +end + +describe Chef::Node, "load" do + it "should load a node from couchdb by name" do + mock_couch = mock("Chef::CouchDB") + mock_couch.should_receive(:load).with("node", "coffee").and_return(true) + Chef::CouchDB.stub!(:new).and_return(mock_couch) + Chef::Node.load("coffee") + end +end + +describe Chef::Node, "destroy" do + it "should delete this node from couchdb" do + mock_couch = mock("Chef::CouchDB") + mock_couch.should_receive(:delete).with("node", "bob", 1).and_return(true) + Chef::CouchDB.stub!(:new).and_return(mock_couch) + node = Chef::Node.new + node.name "bob" + node.couchdb_rev = 1 + Chef::Queue.should_receive(:send_msg).with(:queue, :remove, node) + node.destroy + end +end + +describe Chef::Node, "save" do + before(:each) do + @mock_couch = mock("Chef::CouchDB") + @mock_couch.stub!(:store).and_return({ "rev" => 33 }) + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + Chef::Queue.stub!(:send_msg).and_return(true) + @node = Chef::Node.new + @node.name "bob" + @node.couchdb_rev = 1 + end + + it "should save the node to couchdb" do + Chef::Queue.should_receive(:send_msg).with(:queue, :index, @node) + @mock_couch.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 }) + @node.save + end + + it "should store the new couchdb_rev" do + @node.save + @node.couchdb_rev.should eql(33) + end +end + +describe Chef::Node, "create_design_document" do + it "should create our design document" do + mock_couch = mock("Chef::CouchDB") + mock_couch.should_receive(:create_design_document).with("nodes", Chef::Node::DESIGN_DOCUMENT) + Chef::CouchDB.stub!(:new).and_return(mock_couch) + Chef::Node.create_design_document + end +end diff --git a/chef/spec/unit/openid_registration_spec.rb b/chef/spec/unit/openid_registration_spec.rb new file mode 100644 index 0000000000..d849369796 --- /dev/null +++ b/chef/spec/unit/openid_registration_spec.rb @@ -0,0 +1,153 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::OpenIDRegistration, "initialize" do + it "should return a new Chef::OpenIDRegistration object" do + Chef::OpenIDRegistration.new.should be_kind_of(Chef::OpenIDRegistration) + end +end + +describe Chef::OpenIDRegistration, "set_password" do + it "should generate a salt for this object" do + oreg = Chef::OpenIDRegistration.new + oreg.salt.should eql(nil) + oreg.set_password("foolio") + oreg.salt.should_not eql(nil) + end + + it "should encrypt the password with the salt and the plaintext password" do + oreg = Chef::OpenIDRegistration.new + oreg.set_password("foolio") + oreg.password.should_not eql(nil) + end +end + +describe Chef::OpenIDRegistration, "to_json" do + it "should serialize itself as json" do + oreg = Chef::OpenIDRegistration.new + oreg.set_password("monkey") + json = oreg.to_json + %w{json_class chef_type name salt password validated}.each do |verify| + json.should =~ /#{verify}/ + end + end +end + +describe Chef::OpenIDRegistration, "from_json" do + it "should serialize itself as json" do + oreg = Chef::OpenIDRegistration.new() + oreg.name = "foobar" + oreg.set_password("monkey") + oreg_json = oreg.to_json + nreg = JSON.parse(oreg_json) + nreg.should be_a_kind_of(Chef::OpenIDRegistration) + %w{name salt password validated}.each do |verify| + nreg.send(verify.to_sym).should eql(oreg.send(verify.to_sym)) + end + end +end + +describe Chef::OpenIDRegistration, "list" do + before(:each) do + @mock_couch = mock("Chef::CouchDB") + @mock_couch.stub!(:list).and_return({ + "rows" => [ + { + "value" => "a", + "key" => "avenue" + } + ] + }) + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + end + + it "should retrieve a list of nodes from CouchDB" do + Chef::OpenIDRegistration.list.should eql(["avenue"]) + end + + it "should return just the ids if inflate is false" do + Chef::OpenIDRegistration.list(false).should eql(["avenue"]) + end + + it "should return the full objects if inflate is true" do + Chef::OpenIDRegistration.list(true).should eql(["a"]) + end +end + +describe Chef::OpenIDRegistration, "load" do + it "should load a registration from couchdb by name" do + @mock_couch = mock("Chef::CouchDB") + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + @mock_couch.should_receive(:load).with("openid_registration", "coffee").and_return(true) + Chef::OpenIDRegistration.load("coffee") + end +end + +describe Chef::OpenIDRegistration, "destroy" do + it "should delete this registration from couchdb" do + @mock_couch = mock("Chef::CouchDB") + @mock_couch.should_receive(:delete).with("openid_registration", "bob", 1).and_return(true) + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + reg = Chef::OpenIDRegistration.new + reg.name = "bob" + reg.couchdb_rev = 1 + reg.destroy + end +end + +describe Chef::OpenIDRegistration, "save" do + before(:each) do + @mock_couch = mock("Chef::CouchDB") + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + @reg = Chef::OpenIDRegistration.new + @reg.name = "bob" + @reg.couchdb_rev = 1 + end + + it "should save the registration to couchdb" do + @mock_couch.should_receive(:store).with("openid_registration", "bob", @reg).and_return({ "rev" => 33 }) + @reg.save + end + + it "should store the new couchdb_rev" do + @mock_couch.stub!(:store).with("openid_registration", "bob", @reg).and_return({ "rev" => 33 }) + @reg.save + @reg.couchdb_rev.should eql(33) + end +end + +describe Chef::OpenIDRegistration, "create_design_document" do + it "should create our design document" do + mock_couch = mock("Chef::CouchDB") + mock_couch.should_receive(:create_design_document).with("registrations", Chef::OpenIDRegistration::DESIGN_DOCUMENT) + Chef::CouchDB.stub!(:new).and_return(mock_couch) + Chef::OpenIDRegistration.create_design_document + end +end + +describe Chef::OpenIDRegistration, "has_key?" do + it "should check with CouchDB for a registration with this key" do + @mock_couch = mock("Chef::CouchDB") + @mock_couch.should_receive(:has_key?).with("openid_registration", "bob").and_return(true) + Chef::CouchDB.stub!(:new).and_return(@mock_couch) + Chef::OpenIDRegistration.has_key?("bob") + end +end + diff --git a/chef/spec/unit/platform_spec.rb b/chef/spec/unit/platform_spec.rb new file mode 100644 index 0000000000..4eeeebed89 --- /dev/null +++ b/chef/spec/unit/platform_spec.rb @@ -0,0 +1,209 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Platform do + before(:each) do + Chef::Platform.platforms = { + :darwin => { + "9.2.2" => { + :file => "darwinian", + :else => "thing" + }, + :default => { + :file => "old school", + :snicker => "snack" + } + }, + :mars_volta => { + }, + :default => { + :file => Chef::Provider::File, + :pax => "brittania", + :cat => "nice" + } + } + end + + it "should allow you to look up a platform by name and version, returning the provider map for it" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql("darwinian") + end + + it "should use the default providers for an os if the specific version does not exist" do + pmap = Chef::Platform.find("Darwin", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql("old school") + end + + it "should use the default providers if the os doesn't give me a default, but does exist" do + pmap = Chef::Platform.find("mars_volta", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql(Chef::Provider::File) + end + + it "should use the default provider if the os does not exist" do + pmap = Chef::Platform.find("AIX", "1") + pmap.should be_a_kind_of(Hash) + pmap[:file].should eql(Chef::Provider::File) + end + + it "should merge the defaults for an os with the specific version" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap[:file].should eql("darwinian") + pmap[:snicker].should eql("snack") + end + + it "should merge the defaults for an os with the universal defaults" do + pmap = Chef::Platform.find("Darwin", "9.2.2") + pmap[:file].should eql("darwinian") + pmap[:pax].should eql("brittania") + end + + it "should allow you to look up a provider for a platform directly by symbol" do + Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian") + end + + it "should raise an exception if a provider cannot be found for a resource type" do + lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError) + end + + it "should look up a provider for a resource with a Chef::Resource object" do + kitty = Chef::Resource::Cat.new("loulou") + Chef::Platform.find_provider("Darwin", "9.2.2", kitty) + end + + it "should look up a provider with a node and a Chef::Resource object" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + Chef::Platform.find_provider_for_node(node, kitty).should eql("nice") + end + + it "should prefer lsbdistid over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.lsbdistid("Not Linux") + Chef::Platform.set( + :platform => :not_linux, + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer macosx_productnmae over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.macosx_productname("Mac OS X") + Chef::Platform.set( + :platform => :mac_os_x, + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer lsbdistrelease over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.lsbdistrelease("10") + Chef::Platform.set( + :platform => :darwin, + :version => "10", + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should prefer macosx_productversion over operatingsystem when looking up via node" do + kitty = Chef::Resource::Cat.new("loulou") + node = Chef::Node.new + node.name("Intel") + node.operatingsystem("Darwin") + node.operatingsystemversion("9.2.2") + node.macosx_productversion("10") + Chef::Platform.set( + :platform => :darwin, + :version => "10", + :resource => :cat, + :provider => "bourbon" + ) + Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon") + end + + it "should update the provider map with map" do + Chef::Platform.set( + :platform => :darwin, + :version => "9.2.2", + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful") + Chef::Platform.set( + :platform => :darwin, + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful") + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + Chef::Platform.set( + :platform => :hero, + :version => "9.2.2", + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful") + + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + Chef::Platform.platforms = {} + + Chef::Platform.set( + :resource => :file, + :provider => "masterful" + ) + Chef::Platform.platforms[:default][:file].should eql("masterful") + + end + + +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/directory_spec.rb b/chef/spec/unit/provider/directory_spec.rb new file mode 100644 index 0000000000..4978b27288 --- /dev/null +++ b/chef/spec/unit/provider/directory_spec.rb @@ -0,0 +1,98 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'ostruct' + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::Directory do + before(:each) do + @new_resource = mock("New Resource", :null_object => true) + @new_resource.stub!(:name).and_return("directory") + @new_resource.stub!(:path).and_return("/tmp") + @new_resource.stub!(:owner).and_return(500) + @new_resource.stub!(:group).and_return(500) + @new_resource.stub!(:mode).and_return(0644) + @new_resource.stub!(:updated).and_return(false) + @node = Chef::Node.new + @node.name "latte" + @directory = Chef::Provider::Directory.new(@node, @new_resource) + end + + it "should load the current resource based on the new resource" do + File.should_receive(:exist?).once.and_return(true) + File.should_receive(:directory?).once.and_return(true) + cstats = mock("stats", :null_object => true) + cstats.stub!(:uid).and_return(500) + cstats.stub!(:gid).and_return(500) + cstats.stub!(:mode).and_return(0755) + File.should_receive(:stat).once.and_return(cstats) + @directory.load_current_resource + @directory.current_resource.path.should eql(@new_resource.path) + @directory.current_resource.owner.should eql(500) + @directory.current_resource.group.should eql(500) + @directory.current_resource.mode.should eql("755") + end + + it "should create a new directory on create, setting updated to true" do + load_mock_provider + File.should_receive(:exists?).once.and_return(false) + Dir.should_receive(:mkdir).with(@new_resource.path).once.and_return(true) + @directory.new_resource.should_receive(:updated=).with(true) + @directory.should_receive(:set_owner).once.and_return(true) + @directory.should_receive(:set_group).once.and_return(true) + @directory.should_receive(:set_mode).once.and_return(true) + @directory.action_create + end + + it "should not create the directory if it already exists" do + load_mock_provider + File.should_receive(:exists?).once.and_return(true) + Dir.should_not_receive(:mkdir).with(@new_resource.path) + @directory.stub!(:set_owner).and_return(true) + @directory.stub!(:set_group).and_return(true) + @directory.stub!(:set_mode).and_return(true) + @directory.action_create + end + + it "should delete the directory if it exists, and is writable with action_delete" do + load_mock_provider + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:writable?).once.and_return(true) + Dir.should_receive(:delete).with(@new_resource.path).once.and_return(true) + @directory.action_delete + end + + it "should raise an exception if it cannot delete the file due to bad permissions" do + load_mock_provider + File.stub!(:exists?).and_return(true) + File.stub!(:writable?).and_return(false) + lambda { @directory.action_delete }.should raise_error(RuntimeError) + end + + def load_mock_provider + File.stub!(:exist?).and_return(true) + File.stub!(:directory?).and_return(true) + cstats = mock("stats", :null_object => true) + cstats.stub!(:uid).and_return(500) + cstats.stub!(:gid).and_return(500) + cstats.stub!(:mode).and_return(0755) + File.stub!(:stat).once.and_return(cstats) + @directory.load_current_resource + end +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/file_spec.rb b/chef/spec/unit/provider/file_spec.rb new file mode 100644 index 0000000000..9ea628a357 --- /dev/null +++ b/chef/spec/unit/provider/file_spec.rb @@ -0,0 +1,224 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'ostruct' + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::File do + before(:each) do + @resource = Chef::Resource::File.new("seattle") + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::File.new(@node, @resource) + end + + it "should return a Chef::Provider::File" do + @provider.should be_a_kind_of(Chef::Provider::File) + end + + it "should store the resource passed to new as new_resource" do + @provider.new_resource.should eql(@resource) + end + + it "should store the node passed to new as node" do + @provider.node.should eql(@node) + end + + it "should load a current resource based on the one specified at construction" do + @provider.load_current_resource + @provider.current_resource.should be_a_kind_of(Chef::Resource::File) + @provider.current_resource.name.should eql(@resource.name) + @provider.current_resource.path.should eql(@resource.path) + @provider.current_resource.owner.should_not eql(nil) + @provider.current_resource.group.should_not eql(nil) + @provider.current_resource.mode.should_not eql(nil) + end + + it "should load a mostly blank current resource if the file specified in new_resource doesn't exist/isn't readable" do + resource = Chef::Resource::File.new("seattle") + resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "woot.txt")) + node = Chef::Node.new + node.name "latte" + provider = Chef::Provider::File.new(node, resource) + provider.load_current_resource + provider.current_resource.should be_a_kind_of(Chef::Resource::File) + provider.current_resource.name.should eql(resource.name) + provider.current_resource.path.should eql(resource.path) + provider.current_resource.owner.should eql(nil) + provider.current_resource.group.should eql(nil) + provider.current_resource.mode.should eql(nil) + end + + it "should load the correct value for owner of the current resource" do + stats = File.stat(@resource.path) + @provider.load_current_resource + @provider.current_resource.owner.should eql(stats.uid) + end + + it "should load an md5 sum for an existing file" do + @provider.load_current_resource + @provider.current_resource.checksum("8d6152c7d62ea9188eda596c4d31e732") + end + + it "should compare the current owner with the requested owner" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return("adam") + Etc.stub!(:getpwnam).and_return( + OpenStruct.new( + :name => "adam", + :passwd => "foo", + :uid => 501, + :gid => 501, + :gecos => "Adam Jacob", + :dir => "/Users/adam", + :shell => "/bin/zsh", + :change => "0", + :uclass => "", + :expire => 0 + ) + ) + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + + @provider.current_resource.owner(777) + @provider.compare_owner.should eql(false) + + @provider.new_resource.stub!(:owner).and_return(501) + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + + @provider.new_resource.stub!(:owner).and_return("501") + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + end + + it "should set the ownership on the file to the requested owner" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(9982398) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(9982398, nil, @provider.current_resource.path) + lambda { @provider.set_owner }.should_not raise_error + end + + it "should raise an exception if you are not root and try to change ownership" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(0) + if Process.uid != 0 + lambda { @provider.set_owner }.should raise_error + end + end + + it "should compare the current group with the requested group" do + @provider.load_current_resource + @provider.new_resource.stub!(:group).and_return("adam") + Etc.stub!(:getgrnam).and_return( + OpenStruct.new( + :name => "adam", + :gid => 501 + ) + ) + @provider.current_resource.group(501) + @provider.compare_group.should eql(true) + + @provider.current_resource.group(777) + @provider.compare_group.should eql(false) + + @provider.new_resource.stub!(:group).and_return(501) + @provider.current_resource.group(501) + @provider.compare_group.should eql(true) + + @provider.new_resource.stub!(:group).and_return("501") + @provider.current_resource.group(501) + @provider.compare_group.should eql(true) + end + + it "should set the group on the file to the requested group" do + @provider.load_current_resource + @provider.new_resource.stub!(:group).and_return(9982398) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(nil, 9982398, @provider.current_resource.path) + lambda { @provider.set_group }.should_not raise_error + end + + it "should raise an exception if you are not root and try to change the group" do + @provider.load_current_resource + @provider.new_resource.stub!(:group).and_return(0) + if Process.uid != 0 + lambda { @provider.set_group }.should raise_error + end + end + + it "should create the file if it is missing, then set the attributes on action_create" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(9982398) + @provider.new_resource.stub!(:group).and_return(9982398) + @provider.new_resource.stub!(:mode).and_return(0755) + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(nil, 9982398, @provider.new_resource.path) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(9982398, nil, @provider.new_resource.path) + File.stub!(:open).and_return(1) + File.should_receive(:chmod).with(0755, @provider.new_resource.path).and_return(1) + File.should_receive(:open).with(@provider.new_resource.path, "w+") + @provider.action_create + end + + it "should delete the file if it exists and is writable on action_delete" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + @provider.stub!(:backup).and_return(true) + File.should_receive("exists?").with(@provider.new_resource.path).and_return(true) + File.should_receive("writable?").with(@provider.new_resource.path).and_return(true) + File.should_receive(:delete).with(@provider.new_resource.path).and_return(true) + @provider.action_delete + end + + it "should raise an error if it cannot delete the file" do + @provider.load_current_resource + @provider.stub!(:backup).and_return(true) + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.should_receive("exists?").with(@provider.new_resource.path).and_return(false) + lambda { @provider.action_delete }.should raise_error() + end + + it "should update the atime/mtime on action_touch" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.should_receive(:utime).once.and_return(1) + File.stub!(:open).and_return(1) + File.stub!(:chown).and_return(1) + File.stub!(:chmod).and_return(1) + @provider.action_touch + end + + it "should backup a file no more than :backup times" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233") + @provider.new_resource.stub!(:backup).and_return(2) + Dir.stub!(:[]).and_return([ "/tmp/s-20080705111233", "/tmp/s-20080705111232", "/tmp/s-20080705111223"]) + FileUtils.should_receive(:rm).with("/tmp/s-20080705111232").once.and_return(true) + FileUtils.should_receive(:rm).with("/tmp/s-20080705111223").once.and_return(true) + FileUtils.stub!(:cp).and_return(true) + File.stub!(:exist?).and_return(true) + @provider.backup + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/link_spec.rb b/chef/spec/unit/provider/link_spec.rb new file mode 100644 index 0000000000..462ab9409b --- /dev/null +++ b/chef/spec/unit/provider/link_spec.rb @@ -0,0 +1,147 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'ostruct' + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::Link do + before(:each) do + @new_resource = mock("New Resource", :null_object => true) + @new_resource.stub!(:name).and_return("symlink") + @new_resource.stub!(:source_file).and_return("/tmp/fofile") + @new_resource.stub!(:target_file).and_return("/tmp/fofile-link") + @new_resource.stub!(:link_type).and_return(:symbolic) + @new_resource.stub!(:updated).and_return(false) + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::Link.new(@node, @new_resource) + end + + it "should load the current resource based on the new resource" do + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:symlink?).once.and_return(true) + File.should_receive(:readlink).once.and_return("/tmp/fofile") + @provider.load_current_resource + @provider.current_resource.name.should eql("symlink") + @provider.current_resource.source_file.should eql("/tmp/fofile") + @provider.current_resource.target_file.should eql("/tmp/fofile-link") + @provider.current_resource.link_type.should eql(:symbolic) + end + + it "should set the current resource's source_file to '' if the target_file doesn't exist" do + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:symlink?).once.and_return(false) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should load the current resource if it is a hard link" do + @new_resource.stub!(:link_type).and_return(:hard) + File.should_receive(:exists?).twice.and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(1) + File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat) + File.should_receive(:stat).with("/tmp/fofile").and_return(cstat) + @provider.load_current_resource + @provider.current_resource.name.should eql("symlink") + @provider.current_resource.source_file.should eql("/tmp/fofile") + @provider.current_resource.target_file.should eql("/tmp/fofile-link") + @provider.current_resource.link_type.should eql(:hard) + end + + it "should set the current resource's source_file to '' if the target_file doesn't exist" do + @new_resource.stub!(:link_type).and_return(:hard) + File.should_receive(:exists?).once.and_return(false) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should set the current resource's source_file to '' if the two files arent hardlinked" do + @new_resource.stub!(:link_type).and_return(:hard) + File.stub!(:exists?).and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(0) + bstat = mock("stats", :null_object => true) + bstat.stub!(:ino).and_return(1) + File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat) + File.should_receive(:stat).with("/tmp/fofile").and_return(bstat) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should create a new symlink on create, setting updated to true" do + load_mock_symlink_provider + @provider.current_resource.source_file("nil") + File.should_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) + @provider.action_create + end + + it "should not create a new symlink on create if it already exists" do + load_mock_symlink_provider + File.should_not_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file) + @provider.action_create + end + + it "should create a new hard link on create, setting updated to true" do + load_mock_hardlink_provider + @provider.current_resource.source_file("nil") + File.should_receive(:link).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) + @provider.action_create + end + + it "should not create a new hard link on create if it already exists" do + load_mock_symlink_provider + File.should_not_receive(:link).with(@new_resource.source_file, @new_resource.target_file) + @provider.action_create + end + + it "should delete the link if it exists, and is writable with action_delete" do + load_mock_symlink_provider + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:writable?).once.and_return(true) + File.should_receive(:delete).with(@new_resource.target_file).once.and_return(true) + @provider.action_delete + end + + it "should raise an exception if it cannot delete the link due to bad permissions" do + load_mock_symlink_provider + File.stub!(:exists?).and_return(true) + File.stub!(:writable?).and_return(false) + lambda { @provider.action_delete }.should raise_error(RuntimeError) + end + + def load_mock_symlink_provider + File.stub!(:exists?).and_return(true) + File.stub!(:symlink?).and_return(true) + File.stub!(:readlink).and_return("/tmp/fofile") + @provider.load_current_resource + end + + def load_mock_hardlink_provider + @new_resource.stub!(:link_type).and_return(:hard) + File.stub!(:exists?).twice.and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(1) + File.stub!(:stat).with("/tmp/fofile-link").and_return(cstat) + File.stub!(:stat).with("/tmp/fofile").and_return(cstat) + @provider.load_current_resource + end +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/remote_file_spec.rb b/chef/spec/unit/provider/remote_file_spec.rb new file mode 100644 index 0000000000..51c48d0f9a --- /dev/null +++ b/chef/spec/unit/provider/remote_file_spec.rb @@ -0,0 +1,152 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::RemoteFile, "action_create" do + before(:each) do + @resource = Chef::Resource::RemoteFile.new("seattle") + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @resource.source("http://foo") + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::RemoteFile.new(@node, @resource) + @provider.current_resource = @resource.clone + end + + it "should call do_remote_file" do + @provider.should_receive(:do_remote_file).with(@resource.source, @resource.path) + @provider.action_create + end + +end + +describe Chef::Provider::RemoteFile, "do_remote_file" do + before(:each) do + @rest = mock(Chef::REST, { }) + @tempfile = mock(Tempfile, { :path => "/tmp/foo", }) + @rest.stub!(:get_rest).and_return(@tempfile) + @resource = Chef::Resource::RemoteFile.new("seattle") + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @resource.source("foo") + @resource.cookbook_name = "monkey" + @node = Chef::Node.new + @node.name "latte" + @node.fqdn "latte.local" + @provider = Chef::Provider::RemoteFile.new(@node, @resource) + @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431609") + @provider.current_resource = @resource.clone + @provider.current_resource.checksum("dad86c61eea237932f201009e5431609") + File.stub!(:exists?).and_return(true) + FileUtils.stub!(:cp).and_return(true) + Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ]) + end + + def do_remote_file + Chef::REST.stub!(:new).and_return(@rest) + @provider.do_remote_file(@resource.source, @resource.path) + end + + it "should set the checksum if the file exists" do + @provider.should_receive(:checksum).with(@resource.path) + do_remote_file + end + + it "should not set the checksum if the file doesn't exist" do + File.stub!(:exists?).with(@resource.path).and_return(false) + @provider.should_not_receive(:checksum).with(@resource.path) + do_remote_file + end + + it "should call generate_url with the current checksum as an extra attribute" do + @provider.should_receive(:generate_url).with(@resource.source, "files", { :checksum => "dad86c61eea237932f201009e5431609"}) + do_remote_file + end + + it "should call get_rest with a correctly composed url" do + url = "cookbooks/#{@resource.cookbook_name}/files?id=#{@resource.source}" + url += "&platform=mac_os_x" + url += "&version=10.5.1" + url += "&fqdn=latte.local" + url += "&checksum=dad86c61eea237932f201009e5431609" + @rest.should_receive(:get_rest).with(url, true).and_return(@tempfile) + do_remote_file + end + + it "should not transfer the file if it has not been changed" do + r = Net::HTTPNotModified.new("one", "two", "three") + e = Net::HTTPRetriableError.new("304", r) + @rest.stub!(:get_rest).and_raise(e) + do_remote_file.should eql(false) + end + + it "should raise an exception if it's any other kind of retriable response than 304" do + r = Net::HTTPMovedPermanently.new("one", "two", "three") + e = Net::HTTPRetriableError.new("301", r) + @rest.stub!(:get_rest).and_raise(e) + lambda { do_remote_file }.should raise_error(Net::HTTPRetriableError) + end + + it "should raise an exception if anything else happens" do + r = Net::HTTPBadRequest.new("one", "two", "three") + e = Net::HTTPServerException.new("fake exception", r) + @rest.stub!(:get_rest).and_raise(e) + lambda { do_remote_file }.should raise_error(Net::HTTPServerException) + end + + it "should checksum the raw file" do + @provider.should_receive(:checksum).with(@tempfile.path).and_return("dad86c61eea237932f201009e5431608") + do_remote_file + end + + it "should backup the original file" do + @provider.should_receive(:backup).with(@resource.path).and_return(true) + do_remote_file + end + + it "should set the new resource to updated" do + @resource.should_receive(:updated=).with(true) + do_remote_file + end + + it "should copy the raw file to the new resource" do + FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) + do_remote_file + end + + it "should set the owner if provided" do + @resource.owner("adam") + @provider.should_receive(:set_owner).and_return(true) + do_remote_file + end + + it "should set the group if provided" do + @resource.group("adam") + @provider.should_receive(:set_group).and_return(true) + do_remote_file + end + + it "should set the mode if provided" do + @resource.mode(0676) + @provider.should_receive(:set_mode).and_return(true) + do_remote_file + end + +# TODO: Finish these tests + +end diff --git a/chef/spec/unit/provider/template_spec.rb b/chef/spec/unit/provider/template_spec.rb new file mode 100644 index 0000000000..abc98e2ea3 --- /dev/null +++ b/chef/spec/unit/provider/template_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Template, "action_create" do + before(:each) do + @rest = mock(Chef::REST, { :get_rest => "/tmp/foobar" }) + @tempfile = mock(Tempfile, { :path => "/tmp/foo", }) + @rest.stub!(:get_rest).and_return(@tempfile) + @resource = Chef::Resource::Template.new("seattle") + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @resource.source("http://foo") + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::Template.new(@node, @resource) + @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431609") + @provider.current_resource = @resource.clone + @provider.current_resource.checksum("dad86c61eea237932f201009e5431609") + FileUtils.stub!(:cp).and_return(true) + end + + def do_action_create + Chef::REST.stub!(:new).and_return(@rest) + @provider.action_create + end + + it "should get the template based on the resources source value" do + @rest.should_receive(:get_rest).with(@resource.source, true).and_return(@tempfile) + do_action_create + end + + it "should set the checksum of the new resource to the value of the returned template" do + @resource.should_receive(:checksum).with("dad86c61eea237932f201009e5431609").once + @resource.should_receive(:checksum).twice + do_action_create + end + + it "should not copy the tempfile to the real file if the checksums match" do + FileUtils.should_not_receive(:cp) + do_action_create + end + + it "should copy the tempfile to the real file if the checksums do not match" do + @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431607") + FileUtils.should_receive(:cp).once + @provider.stub!(:backup).and_return(true) + do_action_create + end + + it "should set the owner if provided" do + @resource.owner("adam") + @provider.should_receive(:set_owner).and_return(true) + do_action_create + end + + it "should set the group if provided" do + @resource.group("adam") + @provider.should_receive(:set_group).and_return(true) + do_action_create + end + + it "should set the mode if provided" do + @resource.mode(0676) + @provider.should_receive(:set_mode).and_return(true) + do_action_create + end +end + +describe Chef::Provider::Template, "generate_url" do + + before(:each) do + @resource = Chef::Resource::Template.new("seattle") + @resource.cookbook_name = "daft" + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::Template.new(@node, @resource) + end + + it "should return a raw url if it starts with http" do + @provider.generate_url('http://foobar', "templates").should eql("http://foobar") + end + + it "should return a composed url if it does not start with http" do + Chef::Platform.stub!(:find_platform_and_version).and_return(["monkey", "1.0"]) + @node.fqdn("monkeynode") + @provider.generate_url('default/something', "templates").should eql("cookbooks/daft/templates?id=default/something&platform=monkey&version=1.0&fqdn=monkeynode") + end +end
\ No newline at end of file diff --git a/chef/spec/unit/provider_spec.rb b/chef/spec/unit/provider_spec.rb new file mode 100644 index 0000000000..0512012598 --- /dev/null +++ b/chef/spec/unit/provider_spec.rb @@ -0,0 +1,48 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 do + before(:each) do + @resource = Chef::Resource.new("funk") + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider.new(@node, @resource) + end + + it "should return a Chef::Provider" do + @provider.should be_a_kind_of(Chef::Provider) + end + + it "should store the resource passed to new as new_resource" do + @provider.new_resource.should eql(@resource) + end + + it "should store the node passed to new as node" do + @provider.node.should eql(@node) + end + + it "should have nil for current_resource by default" do + @provider.current_resource.should eql(nil) + end + + it "should return true for action_nothing" do + @provider.action_nothing.should eql(true) + end +end
\ No newline at end of file diff --git a/chef/spec/unit/queue_spec.rb b/chef/spec/unit/queue_spec.rb new file mode 100644 index 0000000000..e902f44631 --- /dev/null +++ b/chef/spec/unit/queue_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Queue do + + it "should connect to a stomp server on localhost and 61613" do + Stomp::Connection.should_receive(:open).with("", "", "localhost", 61613, false).once + Chef::Queue.connect + end + + it "should allow config options to override defaults on connect" do + Chef::Config[:queue_user] = "monkey" + Chef::Config[:queue_password] = "password" + Chef::Config[:queue_host] = "10.10.10.10" + Chef::Config[:queue_port] = 61614 + Stomp::Connection.should_receive(:open).with("monkey", "password", "10.10.10.10", 61614, false).once + Chef::Queue.connect + end + + it "should make a url based on type and name" do + Chef::Queue.make_url("topic", "goal").should eql("/topic/chef/goal") + Chef::Queue.make_url("queue", "pool").should eql("/queue/chef/pool") + end + + it "should allow you to subscribe to a queue" do + queue = mock("Queue", :null_object => true) + queue.should_receive(:subscribe).with(Chef::Queue.make_url(:topic, :node)).once + Stomp::Connection.stub!(:open).and_return(queue) + Chef::Queue.connect + Chef::Queue.subscribe(:topic, :node) + end + + it "should allow you to send a message" do + message = mock("Message", :null_object => true) + message.should_receive(:to_json).once.and_return("some json") + connection = mock("Connection", :null_object => true) + connection.should_receive(:send).with(Chef::Queue.make_url(:queue, :node), "some json").once.and_return(true) + Stomp::Connection.stub!(:open).and_return(connection) + Chef::Queue.connect + Chef::Queue.send_msg(:queue, :node, message) + end + + it "should receive a message with receive_msg" do + raw_msg = mock("Stomp Message", :null_object => true) + raw_msg.should_receive(:body).twice.and_return("the body") + connection = mock("Connection", :null_object => true) + connection.should_receive(:receive).once.and_return(raw_msg) + JSON.should_receive(:parse).with("the body").and_return("the body") + Stomp::Connection.stub!(:open).and_return(connection) + Chef::Queue.connect + Chef::Queue.receive_msg.should eql([ "the body", raw_msg ]) + end + + it "should poll for a message with poll_msg, returning a message if there is one" do + raw_msg = mock("Stomp Message", :null_object => true) + raw_msg.should_receive(:body).once.and_return("the body") + connection = mock("Connection", :null_object => true) + connection.should_receive(:poll).once.and_return(raw_msg) + JSON.should_receive(:parse).with("the body").and_return("the body") + Stomp::Connection.stub!(:open).and_return(connection) + Chef::Queue.connect + Chef::Queue.poll_msg.should eql("the body") + end + + it "should poll for a message with poll_msg, returning nil if there is not a message" do + connection = mock("Connection", :null_object => true) + connection.should_receive(:poll).once.and_return(nil) + JSON.should_not_receive(:parse).with(nil) + Stomp::Connection.stub!(:open).and_return(connection) + Chef::Queue.connect + Chef::Queue.poll_msg.should eql(nil) + end + + it "should raise an exception if you disconnect without a connection" do + Stomp::Connection.stub!(:open).and_return(nil) + Chef::Queue.connect + lambda { Chef::Queue.disconnect }.should raise_error(ArgumentError) + end + + it "should disconnect an active connection" do + connection = mock("Connection", :null_object => true) + connection.should_receive(:disconnect).once.and_return(true) + Stomp::Connection.stub!(:open).and_return(connection) + Chef::Queue.connect + Chef::Queue.disconnect + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/recipe_spec.rb b/chef/spec/unit/recipe_spec.rb new file mode 100644 index 0000000000..77d5354993 --- /dev/null +++ b/chef/spec/unit/recipe_spec.rb @@ -0,0 +1,143 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Recipe do + before(:each) do + @recipe = Chef::Recipe.new("hjk", "test", Chef::Node.new) + end + + it "should load a two word (zen_master) resource" do + lambda do + @recipe.zen_master "monkey" do + peace true + end + end.should_not raise_error(ArgumentError) + end + + it "should load a one word (cat) resource" do + lambda do + @recipe.cat "loulou" do + pretty_kitty true + end + end.should_not raise_error(ArgumentError) + end + + it "should throw an error if you access a resource that we can't find" do + lambda { @recipe.not_home { || } }.should raise_error(NameError) + end + + it "should allow regular errors (not NameErrors) to pass unchanged" do + lambda { + @recipe.cat { || raise ArgumentError, "You Suck" } + }.should raise_error(ArgumentError) + end + + it "should add our zen_master to the collection" do + @recipe.zen_master "monkey" do + peace true + end + @recipe.collection.lookup("zen_master[monkey]").name.should eql("monkey") + end + + it "should add our zen masters to the collection in the order they appear" do + %w{monkey dog cat}.each do |name| + @recipe.zen_master name do + peace true + end + end + @recipe.collection.each_index do |i| + case i + when 0 + @recipe.collection[i].name.should eql("monkey") + when 1 + @recipe.collection[i].name.should eql("dog") + when 2 + @recipe.collection[i].name.should eql("cat") + end + end + end + + it "should return the new resource after creating it" do + res = @recipe.zen_master "makoto" do + peace true + end + res.resource_name.should eql(:zen_master) + res.name.should eql("makoto") + end + + it "should handle an instance_eval properly" do + code = <<-CODE +zen_master "gnome" do + peace = true +end +CODE + lambda { @recipe.instance_eval(code) }.should_not raise_error + @recipe.resources(:zen_master => "gnome").name.should eql("gnome") + end + + it "should execute defined resources" do + crow_define = Chef::ResourceDefinition.new + crow_define.define :crow, :peace => false, :something => true do + zen_master "lao tzu" do + peace params[:peace] + something params[:something] + end + end + @recipe.definitions[:crow] = crow_define + @recipe.crow "mine" do + peace true + end + @recipe.resources(:zen_master => "lao tzu").name.should eql("lao tzu") + @recipe.resources(:zen_master => "lao tzu").something.should eql(true) + end + + it "should load a resource from a ruby file" do + @recipe.from_file(File.join(File.dirname(__FILE__), "..", "data", "recipes", "test.rb")) + res = @recipe.resources(:file => "/etc/nsswitch.conf") + res.name.should eql("/etc/nsswitch.conf") + res.action.should eql(:create) + res.owner.should eql("root") + res.group.should eql("root") + res.mode.should eql(0644) + end + + it "should raise an exception if the file cannot be found or read" do + lambda { @recipe.from_file("/tmp/monkeydiving") }.should raise_error(IOError) + end + + it "should evaluate another recipe with recipe_require" do + Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + @recipe.cookbook_loader.load_cookbooks + @recipe.require_recipe "openldap::gigantor" + res = @recipe.resources(:cat => "blanket") + res.name.should eql("blanket") + res.pretty_kitty.should eql(false) + end + + it "should load the default recipe for a cookbook if require_recipe is called without a ::" do + Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + @recipe.cookbook_loader.load_cookbooks + @recipe.require_recipe "openldap" + res = @recipe.resources(:cat => "blanket") + res.name.should eql("blanket") + res.pretty_kitty.should eql(true) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/bash_spec.rb b/chef/spec/unit/resource/bash_spec.rb new file mode 100644 index 0000000000..f86e63d32e --- /dev/null +++ b/chef/spec/unit/resource/bash_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Bash do + + before(:each) do + @resource = Chef::Resource::Bash.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Bash" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Bash) + end + + it "should have a resource name of :bash" do + @resource.resource_name.should eql(:bash) + end + + it "should have an interpreter of bash" do + @resource.interpreter.should eql("bash") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/csh_spec.rb b/chef/spec/unit/resource/csh_spec.rb new file mode 100644 index 0000000000..67ba4499fa --- /dev/null +++ b/chef/spec/unit/resource/csh_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Csh do + + before(:each) do + @resource = Chef::Resource::Csh.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Csh" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Csh) + end + + it "should have a resource name of :csh" do + @resource.resource_name.should eql(:csh) + end + + it "should have an interpreter of csh" do + @resource.interpreter.should eql("csh") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/directory_spec.rb b/chef/spec/unit/resource/directory_spec.rb new file mode 100644 index 0000000000..98cc28fbc6 --- /dev/null +++ b/chef/spec/unit/resource/directory_spec.rb @@ -0,0 +1,79 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Directory do + + before(:each) do + @resource = Chef::Resource::Directory.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Directory" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Directory) + end + + it "should have a name" do + @resource.name.should eql("fakey_fakerton") + end + + it "should have a default action of 'create'" do + @resource.action.should eql(:create) + end + + it "should accept create or delete for action" do + lambda { @resource.action "create" }.should_not raise_error(ArgumentError) + lambda { @resource.action "delete" }.should_not raise_error(ArgumentError) + lambda { @resource.action "blues" }.should raise_error(ArgumentError) + end + + it "should accept a group name or id for group" do + lambda { @resource.group "root" }.should_not raise_error(ArgumentError) + lambda { @resource.group 123 }.should_not raise_error(ArgumentError) + lambda { @resource.group "root*goo" }.should raise_error(ArgumentError) + end + + it "should accept a valid unix file mode" do + lambda { @resource.mode 0444 }.should_not raise_error(ArgumentError) + lambda { @resource.mode 444 }.should_not raise_error(ArgumentError) + lambda { @resource.mode 4 }.should raise_error(ArgumentError) + end + + it "should accept a user name or id for owner" do + lambda { @resource.owner "root" }.should_not raise_error(ArgumentError) + lambda { @resource.owner 123 }.should_not raise_error(ArgumentError) + lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError) + end + + it "should use the object name as the path by default" do + @resource.path.should eql("fakey_fakerton") + end + + it "should accept a string as the path" do + lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError) + lambda { @resource.path Hash.new }.should raise_error(ArgumentError) + end + + it "should allow you to have specify whether the action is recursive with true/false" do + lambda { @resource.recursive true }.should_not raise_error(ArgumentError) + lambda { @resource.recursive false }.should_not raise_error(ArgumentError) + lambda { @resource.recursive "monkey" }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/execute_spec.rb b/chef/spec/unit/resource/execute_spec.rb new file mode 100644 index 0000000000..94cb3d3d92 --- /dev/null +++ b/chef/spec/unit/resource/execute_spec.rb @@ -0,0 +1,102 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Execute do + + before(:each) do + @resource = Chef::Resource::Execute.new("some command") + end + + it "should create a new Chef::Resource::Execute" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Execute) + end + + it "should set the command to the first argument to new" do + @resource.command.should eql("some command") + end + + it "should accept a string for the command to run" do + @resource.command "something" + @resource.command.should eql("something") + end + + it "should accept a string for the cwd" do + @resource.cwd "something" + @resource.cwd.should eql("something") + end + + it "should accept a hash for the environment" do + test_hash = { :one => :two } + @resource.environment(test_hash) + @resource.environment.should eql(test_hash) + end + + it "should accept a string for the group" do + @resource.group "something" + @resource.group.should eql("something") + end + + it "should accept an integer for the group" do + @resource.group 1 + @resource.group.should eql(1) + end + + it "should accept a string for onlyif" do + @resource.onlyif "woot" + @resource.onlyif.should eql("woot") + end + + it "should accept an array for the execution path" do + @resource.path ["woot"] + @resource.path.should eql(["woot"]) + end + + it "should accept an integer for the return code" do + @resource.returns 1 + @resource.returns.should eql(1) + end + + it "should accept an integer for the timeout" do + @resource.timeout 1 + @resource.timeout.should eql(1) + end + + it "should accept a string for unless" do + @resource.unless "woot" + @resource.unless.should eql("woot") + end + + it "should accept a string for the user" do + @resource.user "something" + @resource.user.should eql("something") + end + + it "should accept an integer for the user" do + @resource.user 1 + @resource.user.should eql(1) + end + + it "should accept a string for creates" do + @resource.creates "something" + @resource.creates.should eql("something") + end + +end diff --git a/chef/spec/unit/resource/file_spec.rb b/chef/spec/unit/resource/file_spec.rb new file mode 100644 index 0000000000..b67598d738 --- /dev/null +++ b/chef/spec/unit/resource/file_spec.rb @@ -0,0 +1,92 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::File do + + before(:each) do + @resource = Chef::Resource::File.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::File" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::File) + end + + it "should have a name" do + @resource.name.should eql("fakey_fakerton") + end + + it "should have a default action of 'create'" do + @resource.action.should eql("create") + end + + it "should be set to back up 5 files by default" do + @resource.backup.should eql(5) + end + + it "should only accept false or a number for backup" do + lambda { @resource.backup true }.should raise_error(ArgumentError) + lambda { @resource.backup false }.should_not raise_error(ArgumentError) + lambda { @resource.backup 10 }.should_not raise_error(ArgumentError) + lambda { @resource.backup "blues" }.should raise_error(ArgumentError) + end + + it "should accept an md5sum for checksum" do + lambda { @resource.checksum "bfda9e7a13afb123433667c2c7801d11" }.should_not raise_error(ArgumentError) + lambda { @resource.checksum "monkey!" }.should raise_error(ArgumentError) + end + + it "should accept create, delete or touch for action" do + lambda { @resource.action "create" }.should_not raise_error(ArgumentError) + lambda { @resource.action "delete" }.should_not raise_error(ArgumentError) + lambda { @resource.action "touch" }.should_not raise_error(ArgumentError) + lambda { @resource.action "blues" }.should raise_error(ArgumentError) + end + + it "should accept a group name or id for group" do + lambda { @resource.group "root" }.should_not raise_error(ArgumentError) + lambda { @resource.group 123 }.should_not raise_error(ArgumentError) + lambda { @resource.group "root*goo" }.should raise_error(ArgumentError) + end + + it "should accept a valid unix file mode" do + lambda { @resource.mode 0444 }.should_not raise_error(ArgumentError) + @resource.mode.should eql(0444) + lambda { @resource.mode 444 }.should_not raise_error(ArgumentError) + lambda { @resource.mode 4 }.should raise_error(ArgumentError) + end + + it "should accept a user name or id for owner" do + lambda { @resource.owner "root" }.should_not raise_error(ArgumentError) + lambda { @resource.owner 123 }.should_not raise_error(ArgumentError) + lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError) + end + + it "should use the object name as the path by default" do + @resource.path.should eql("fakey_fakerton") + end + + it "should accept a string as the path" do + lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError) + @resource.path.should eql("/tmp") + lambda { @resource.path Hash.new }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/link_spec.rb b/chef/spec/unit/resource/link_spec.rb new file mode 100644 index 0000000000..fa0810cced --- /dev/null +++ b/chef/spec/unit/resource/link_spec.rb @@ -0,0 +1,78 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Link do + + before(:each) do + @resource = Chef::Resource::Link.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Link" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Link) + end + + it "should have a name" do + @resource.name.should eql("fakey_fakerton") + end + + it "should have a default action of 'create'" do + @resource.action.should eql(:create) + end + + it "should accept create or delete for action" do + lambda { @resource.action "create" }.should_not raise_error(ArgumentError) + lambda { @resource.action "delete" }.should_not raise_error(ArgumentError) + lambda { @resource.action "blues" }.should raise_error(ArgumentError) + end + + it "should use the object name as the source_file by default" do + @resource.source_file.should eql("fakey_fakerton") + end + + it "should accept a string as the source_file" do + lambda { @resource.source_file "/tmp" }.should_not raise_error(ArgumentError) + lambda { @resource.source_file Hash.new }.should raise_error(ArgumentError) + end + + it "should allow you to set a target_file" do + @resource.target_file "/tmp/foo" + @resource.target_file.should eql("/tmp/foo") + end + + it "should allow you to specify the link type" do + @resource.link_type "symbolic" + @resource.link_type.should eql(:symbolic) + end + + it "should default to a symbolic link" do + @resource.link_type.should eql(:symbolic) + end + + it "should accept a hard link_type" do + @resource.link_type :hard + @resource.link_type.should eql(:hard) + end + + it "should reject any other link_type but :hard and :symbolic" do + lambda { @resource.link_type "x-men" }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/package_spec.rb b/chef/spec/unit/resource/package_spec.rb new file mode 100644 index 0000000000..8756e4f603 --- /dev/null +++ b/chef/spec/unit/resource/package_spec.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Package do + + before(:each) do + @resource = Chef::Resource::Package.new("emacs") + end + + it "should create a new Chef::Resource::Package" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Package) + end + + it "should set the package_name to the first argument to new" do + @resource.package_name.should eql("emacs") + end + + it "should accept a string for the package name" do + @resource.package_name "something" + @resource.package_name.should eql("something") + end + + it "should accept a string for the version" do + @resource.version "something" + @resource.version.should eql("something") + end + + it "should accept a string for the response file" do + @resource.response_file "something" + @resource.response_file.should eql("something") + end + + it "should accept a string for the source" do + @resource.source "something" + @resource.source.should eql("something") + end + +end diff --git a/chef/spec/unit/resource/perl_spec.rb b/chef/spec/unit/resource/perl_spec.rb new file mode 100644 index 0000000000..528bcef08c --- /dev/null +++ b/chef/spec/unit/resource/perl_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Perl do + + before(:each) do + @resource = Chef::Resource::Perl.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Perl" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Perl) + end + + it "should have a resource name of :perl" do + @resource.resource_name.should eql(:perl) + end + + it "should have an interpreter of perl" do + @resource.interpreter.should eql("perl") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/python_spec.rb b/chef/spec/unit/resource/python_spec.rb new file mode 100644 index 0000000000..11c5b84d04 --- /dev/null +++ b/chef/spec/unit/resource/python_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Python do + + before(:each) do + @resource = Chef::Resource::Python.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Python" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Python) + end + + it "should have a resource name of :python" do + @resource.resource_name.should eql(:python) + end + + it "should have an interpreter of python" do + @resource.interpreter.should eql("python") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/remote_directory_spec.rb b/chef/spec/unit/resource/remote_directory_spec.rb new file mode 100644 index 0000000000..2d9ce122af --- /dev/null +++ b/chef/spec/unit/resource/remote_directory_spec.rb @@ -0,0 +1,71 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::RemoteDirectory do + + before(:each) do + @resource = Chef::Resource::RemoteDirectory.new("/etc/dunk") + end + + it "should create a new Chef::Resource::RemoteDirectory" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::RemoteDirectory) + end + + it "should set the path to the first argument to new" do + @resource.path.should eql("/etc/dunk") + end + + it "should accept a string for the remote directory source" do + @resource.source "foo" + @resource.source.should eql("foo") + end + + it "should accept a number for the remote files backup" do + @resource.files_backup 1 + @resource.files_backup.should eql(1) + end + + it "should accept false for the remote files backup" do + @resource.files_backup false + @resource.files_backup.should eql(false) + end + + it "should accept 3 or 4 digets for the files_mode" do + @resource.files_mode 100 + @resource.files_mode.should eql(100) + @resource.files_mode 1000 + @resource.files_mode.should eql(1000) + end + + it "should accept a string or number for the files group" do + @resource.files_group "heart" + @resource.files_group.should eql("heart") + @resource.files_group 1000 + @resource.files_group.should eql(1000) + end + + it "should accept a string or number for the files owner" do + @resource.files_owner "heart" + @resource.files_owner.should eql("heart") + @resource.files_owner 1000 + @resource.files_owner.should eql(1000) + end +end diff --git a/chef/spec/unit/resource/remote_file_spec.rb b/chef/spec/unit/resource/remote_file_spec.rb new file mode 100644 index 0000000000..4c052e0260 --- /dev/null +++ b/chef/spec/unit/resource/remote_file_spec.rb @@ -0,0 +1,38 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::RemoteFile do + + before(:each) do + @resource = Chef::Resource::RemoteFile.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::RemoteFile" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::File) + @resource.should be_a_kind_of(Chef::Resource::RemoteFile) + end + + it "should accept a string for the remote file source" do + @resource.source "something" + @resource.source.should eql("something") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/ruby_spec.rb b/chef/spec/unit/resource/ruby_spec.rb new file mode 100644 index 0000000000..5351462154 --- /dev/null +++ b/chef/spec/unit/resource/ruby_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Ruby do + + before(:each) do + @resource = Chef::Resource::Ruby.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Ruby" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Ruby) + end + + it "should have a resource name of :ruby" do + @resource.resource_name.should eql(:ruby) + end + + it "should have an interpreter of ruby" do + @resource.interpreter.should eql("ruby") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/script_spec.rb b/chef/spec/unit/resource/script_spec.rb new file mode 100644 index 0000000000..f003028fd6 --- /dev/null +++ b/chef/spec/unit/resource/script_spec.rb @@ -0,0 +1,50 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Script do + + before(:each) do + @resource = Chef::Resource::Script.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Script" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Script) + end + + it "should have a resource name of :script" do + @resource.resource_name.should eql(:script) + end + + it "should set command to the argument provided to new" do + @resource.command.should eql("fakey_fakerton") + end + + it "should accept a string for the code" do + @resource.code "hey jude" + @resource.code.should eql("hey jude") + end + + it "should accept a string for the interpreter" do + @resource.interpreter "naaaaNaNaNaaNaaNaaNaa" + @resource.interpreter.should eql("naaaaNaNaNaaNaaNaaNaa") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/service_spec.rb b/chef/spec/unit/resource/service_spec.rb new file mode 100644 index 0000000000..f5941f89ae --- /dev/null +++ b/chef/spec/unit/resource/service_spec.rb @@ -0,0 +1,100 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Service do + + before(:each) do + @resource = Chef::Resource::Service.new("chef") + end + + it "should create a new Chef::Resource::Service" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Service) + end + + it "should set the service_name to the first argument to new" do + @resource.service_name.should eql("chef") + end + + it "should set the pattern to be the service name by default" do + @resource.pattern.should eql("chef") + end + + it "should accept a string for the service name" do + @resource.service_name "something" + @resource.service_name.should eql("something") + end + + it "should accept a string for the service pattern" do + @resource.pattern ".*" + @resource.pattern.should eql(".*") + end + + it "should not accept a regexp for the service pattern" do + lambda { + @resource.pattern /.*/ + }.should raise_error(ArgumentError) + end + + it "should accept a string for the service start command" do + @resource.start_command "/etc/init.d/chef start" + @resource.start_command.should eql("/etc/init.d/chef start") + end + + it "should not accept a regexp for the service start command" do + lambda { + @resource.start_command /.*/ + }.should raise_error(ArgumentError) + end + + it "should accept a string for the service stop command" do + @resource.stop_command "/etc/init.d/chef stop" + @resource.stop_command.should eql("/etc/init.d/chef stop") + end + + it "should not accept a regexp for the service stop command" do + lambda { + @resource.stop_command /.*/ + }.should raise_error(ArgumentError) + end + + it "should accept a string for the service status command" do + @resource.status_command "/etc/init.d/chef status" + @resource.status_command.should eql("/etc/init.d/chef status") + end + + it "should not accept a regexp for the service status command" do + lambda { + @resource.status_command /.*/ + }.should raise_error(ArgumentError) + end + + it "should accept a string for the service restart command" do + @resource.restart_command "/etc/init.d/chef restart" + @resource.restart_command.should eql("/etc/init.d/chef restart") + end + + it "should not accept a regexp for the service restart command" do + lambda { + @resource.restart_command /.*/ + }.should raise_error(ArgumentError) + end + +end diff --git a/chef/spec/unit/resource/template_spec.rb b/chef/spec/unit/resource/template_spec.rb new file mode 100644 index 0000000000..106fe77bda --- /dev/null +++ b/chef/spec/unit/resource/template_spec.rb @@ -0,0 +1,43 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Template do + + before(:each) do + @resource = Chef::Resource::Template.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Template" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::File) + @resource.should be_a_kind_of(Chef::Resource::Template) + end + + it "should accept a string for the template source" do + @resource.source "something" + @resource.source.should eql("something") + end + + it "should accept a hash for the variable list" do + @resource.variables({ :reluctance => :awkward }) + @resource.variables.should == { :reluctance => :awkward } + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource_collection_spec.rb b/chef/spec/unit/resource_collection_spec.rb new file mode 100644 index 0000000000..37eeac18db --- /dev/null +++ b/chef/spec/unit/resource_collection_spec.rb @@ -0,0 +1,201 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::ResourceCollection do + + before(:each) do + @rc = Chef::ResourceCollection.new() + @resource = Chef::Resource::ZenMaster.new("makoto") + end + + it "should return a Chef::ResourceCollection" do + @rc.should be_kind_of(Chef::ResourceCollection) + end + + it "should accept Chef::Resources through [index]" do + lambda { @rc[0] = @resource }.should_not raise_error + lambda { @rc[0] = "string" }.should raise_error + end + + it "should not accept duplicate resources [index]=" do + @rc[0] = @resource + lambda { @rc[1] = @resource }.should raise_error(ArgumentError) + end + + it "should accept Chef::Resources through pushing" do + lambda { @rc.push(@resource) }.should_not raise_error + lambda { @rc.push("string") }.should raise_error + end + + it "should not accept duplicate resources through pushing" do + lambda { @rc.push(@resource) }.should_not raise_error + lambda { @rc.push(@resource) }.should raise_error(ArgumentError) + end + + it "should allow you to fetch Chef::Resources by position" do + @rc[0] = @resource + @rc[0].should eql(@resource) + end + + it "should accept the << operator" do + lambda { @rc << @resource }.should_not raise_error + end + + it "should not accept duplicate resources through the << operator" do + lambda { @rc << @resource }.should_not raise_error + lambda { @rc << @resource }.should raise_error(ArgumentError) + end + + it "should allow you to iterate over every resource in the collection" do + load_up_resources + results = Array.new + lambda { + @rc.each do |r| + results << r.name + end + }.should_not raise_error + results.each_index do |i| + case i + when 0 + results[i].should eql("dog") + when 1 + results[i].should eql("cat") + when 2 + results[i].should eql("monkey") + end + end + end + + it "should allow you to iterate over every resource by index" do + load_up_resources + results = Array.new + lambda { + @rc.each_index do |i| + results << @rc[i].name + end + }.should_not raise_error() + results.each_index do |i| + case i + when 0 + results[i].should eql("dog") + when 1 + results[i].should eql("cat") + when 2 + results[i].should eql("monkey") + end + end + end + + it "should allow you to find resources by name via lookup" do + zmr = Chef::Resource::ZenMaster.new("dog") + @rc << zmr + @rc.lookup(zmr.to_s).should eql(zmr) + + zmr = Chef::Resource::ZenMaster.new("cat") + @rc[0] = zmr + @rc.lookup(zmr).should eql(zmr) + + zmr = Chef::Resource::ZenMaster.new("monkey") + @rc.push(zmr) + @rc.lookup(zmr).should eql(zmr) + end + + it "should raise an exception if you send something strange to lookup" do + lambda { @rc.lookup(:symbol) }.should raise_error(ArgumentError) + end + + it "should raise an exception if it cannot find a resource with lookup" do + lambda { @rc.lookup("zen_master[dog]") }.should raise_error(ArgumentError) + end + + it "should find a resource by symbol and name (:zen_master => monkey)" do + load_up_resources + @rc.resources(:zen_master => "monkey").name.should eql("monkey") + end + + it "should find a resource by symbol and array of names (:zen_master => [a,b])" do + load_up_resources + results = @rc.resources(:zen_master => [ "monkey", "dog" ]) + results.length.should eql(2) + check_by_names(results, "monkey", "dog") + end + + it "should find resources of multiple kinds (:zen_master => a, :file => b)" do + load_up_resources + results = @rc.resources(:zen_master => "monkey", :file => "something") + results.length.should eql(2) + check_by_names(results, "monkey", "something") + end + + it "should find a resource by string zen_master[a]" do + load_up_resources + @rc.resources("zen_master[monkey]").name.should eql("monkey") + end + + it "should find resources by strings of zen_master[a,b]" do + load_up_resources + results = @rc.resources("zen_master[monkey,dog]") + results.length.should eql(2) + check_by_names(results, "monkey", "dog") + end + + it "should find resources of multiple types by strings of zen_master[a]" do + load_up_resources + results = @rc.resources("zen_master[monkey]", "file[something]") + results.length.should eql(2) + check_by_names(results, "monkey", "something") + end + + it "should raise an exception if you pass a bad name to resources" do + lambda { @rc.resources("michael jackson") }.should raise_error(ArgumentError) + end + + it "should raise an exception if you pass something other than a string or hash to resource" do + lambda { @rc.resources([Array.new]) }.should raise_error(ArgumentError) + end + + it "should serialize to json" do + json = @rc.to_json + json.should =~ /json_class/ + json.should =~ /instance_vars/ + end + + it "should deserialize itself from json" do + @rc << @resource + json = @rc.to_json + s_rc = JSON.parse(json) + s_rc.should be_a_kind_of(Chef::ResourceCollection) + s_rc[0].name.should eql(@resource.name) + end + + def check_by_names(results, *names) + names.each do |res_name| + results.detect{ |res| res.name == res_name }.should_not eql(nil) + end + end + + def load_up_resources + %w{dog cat monkey}.each do |n| + @rc << Chef::Resource::ZenMaster.new(n) + end + @rc << Chef::Resource::File.new("something") + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource_definition_spec.rb b/chef/spec/unit/resource_definition_spec.rb new file mode 100644 index 0000000000..8ba7556b70 --- /dev/null +++ b/chef/spec/unit/resource_definition_spec.rb @@ -0,0 +1,83 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::ResourceDefinition do + before(:each) do + @def = Chef::ResourceDefinition.new() + end + + it "should accept a new definition with a symbol for a name" do + lambda { + @def.define :smoke do + end + }.should_not raise_error(ArgumentError) + lambda { + @def.define "george washington" do + end + }.should raise_error(ArgumentError) + @def.name.should eql(:smoke) + end + + it "should accept a new definition with a hash" do + lambda { + @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do + end + }.should_not raise_error(ArgumentError) + end + + it "should expose the prototype hash params in the params hash" do + @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do + end + @def.params[:cigar].should eql("cuban") + @def.params[:cigarette].should eql("marlboro") + end + + it "should store the block passed to define as a proc under recipe" do + @def.define :smoke do + "I am what I am" + end + @def.recipe.should be_a_kind_of(Proc) + @def.recipe.call.should eql("I am what I am") + end + + it "should set paramaters based on method_missing" do + @def.mind "to fly" + @def.params[:mind].should eql("to fly") + end + + it "should raise an exception if prototype_params is not a hash" do + lambda { + @def.define :monkey, Array.new do + end + }.should raise_error(ArgumentError) + end + + it "should raise an exception if define is called without a block" do + lambda { + @def.define :monkey + }.should raise_error(ArgumentError) + end + + it "should load a description from a file" do + @def.from_file(File.join(File.dirname(__FILE__), "..", "data", "definitions", "test.rb")) + @def.name.should eql(:rico_suave) + @def.params[:rich].should eql("smooth") + end +end
\ No newline at end of file diff --git a/chef/spec/unit/resource_spec.rb b/chef/spec/unit/resource_spec.rb new file mode 100644 index 0000000000..3ca48060fa --- /dev/null +++ b/chef/spec/unit/resource_spec.rb @@ -0,0 +1,144 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 do + before(:each) do + @resource = Chef::Resource.new("funk") + end + + it "should create a new Chef::Resource" do + @resource.should be_a_kind_of(Chef::Resource) + end + + it "should have a name" do + @resource.name.should eql("funk") + end + + it "should let you set a new name" do + @resource.name "monkey" + @resource.name.should eql("monkey") + end + + it "should not be valid without a name" do + lambda { @resource.name false }.should raise_error(ArgumentError) + end + + it "should always have a string for name" do + lambda { @resource.name Hash.new }.should raise_error(ArgumentError) + end + + it "should accept true or false for noop" do + lambda { @resource.noop true }.should_not raise_error(ArgumentError) + lambda { @resource.noop false }.should_not raise_error(ArgumentError) + lambda { @resource.noop "eat it" }.should raise_error(ArgumentError) + end + + it "should make notified resources appear in the actions hash" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + @resource.notifies :reload, @resource.resources(:zen_master => "coffee") + @resource.actions[:reload][:delayed][0].name.should eql("coffee") + end + + it "should make notified resources be capable of acting immediately" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + @resource.notifies :reload, @resource.resources(:zen_master => "coffee"), :immediate + @resource.actions[:reload][:immediate][0].name.should eql("coffee") + end + + it "should raise an exception if told to act in other than :delay or :immediate(ly)" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + lambda { + @resource.notifies :reload, @resource.resources(:zen_master => "coffee"), :someday + }.should raise_error(ArgumentError) + end + + it "should allow multiple notified resources appear in the actions hash" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + @resource.notifies :reload, @resource.resources(:zen_master => "coffee") + @resource.actions[:reload][:delayed][0].name.should eql("coffee") + @resource.collection << Chef::Resource::ZenMaster.new("beans") + @resource.notifies :reload, @resource.resources(:zen_master => "beans") + @resource.actions[:reload][:delayed][1].name.should eql("beans") + end + + it "should make resources appear in the actions hash of subscribed nodes" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + zr = @resource.resources(:zen_master => "coffee") + @resource.subscribes :reload, zr + zr.actions[:reload][:delayed][0].name.should eql("funk") + end + + it "should make resources appear in the actions hash of subscribed nodes" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + zr = @resource.resources(:zen_master => "coffee") + @resource.subscribes :reload, zr + zr.actions[:reload][:delayed][0].name.should eql("funk") + + @resource.collection << Chef::Resource::ZenMaster.new("bean") + zrb = @resource.resources(:zen_master => "bean") + zrb.subscribes :reload, zr + zr.actions[:reload][:delayed][1].name.should eql("bean") + end + + it "should make subscribed resources be capable of acting immediately" do + @resource.collection << Chef::Resource::ZenMaster.new("coffee") + zr = @resource.resources(:zen_master => "coffee") + @resource.subscribes :reload, zr, :immediately + zr.actions[:reload][:immediate][0].name.should eql("funk") + end + + it "should return a value if not defined" do + zm = Chef::Resource::ZenMaster.new("coffee") + zm.something(true).should eql(true) + zm.something.should eql(true) + zm.something(false).should eql(false) + zm.something.should eql(false) + end + + it "should become a string like resource_name[name]" do + zm = Chef::Resource::ZenMaster.new("coffee") + zm.to_s.should eql("zen_master[coffee]") + end + + it "should return the arguments passed with 'is'" do + zm = Chef::Resource::ZenMaster.new("coffee") + res = zm.is("one", "two", "three") + res.should eql([ "one", "two", "three" ]) + end + + it "should allow arguments preceeded by is to methods" do + @resource.noop(@resource.is(true)) + @resource.noop.should eql(true) + end + + it "should serialize to json" do + json = @resource.to_json + json.should =~ /json_class/ + json.should =~ /instance_vars/ + end + + it "should deserialize itself from json" do + json = @resource.to_json + serialized_node = JSON.parse(json) + serialized_node.should be_a_kind_of(Chef::Resource) + serialized_node.name.should eql(@resource.name) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/rest_spec.rb b/chef/spec/unit/rest_spec.rb new file mode 100644 index 0000000000..56c2954a1a --- /dev/null +++ b/chef/spec/unit/rest_spec.rb @@ -0,0 +1,228 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'uri' +require 'net/https' + +describe Chef::REST, "initialize method" do + it "should create a new Chef::REST" do + Chef::REST.new("url").should be_kind_of(Chef::REST) + end +end + +describe Chef::REST, "get_rest method" do + it "should create a url from the path and base url" do + URI.should_receive(:parse).with("url/monkey") + r = Chef::REST.new("url") + r.stub!(:run_request) + r.get_rest("monkey") + end + + it "should call run_request :GET with the composed url object" do + URI.stub!(:parse).and_return(true) + r = Chef::REST.new("url") + r.should_receive(:run_request).with(:GET, true, false, 10, false).and_return(true) + r.get_rest("monkey") + end +end + +describe Chef::REST, "delete_rest method" do + it "should create a url from the path and base url" do + URI.should_receive(:parse).with("url/monkey") + r = Chef::REST.new("url") + r.stub!(:run_request) + r.delete_rest("monkey") + end + + it "should call run_request :DELETE with the composed url object" do + URI.stub!(:parse).and_return(true) + r = Chef::REST.new("url") + r.should_receive(:run_request).with(:DELETE, true).and_return(true) + r.delete_rest("monkey") + end +end + +describe Chef::REST, "post_rest method" do + it "should create a url from the path and base url" do + URI.should_receive(:parse).with("url/monkey") + r = Chef::REST.new("url") + r.stub!(:run_request) + r.post_rest("monkey", "data") + end + + it "should call run_request :POST with the composed url object and data" do + URI.stub!(:parse).and_return(true) + r = Chef::REST.new("url") + r.should_receive(:run_request).with(:POST, true, "data").and_return(true) + r.post_rest("monkey", "data") + end +end + +describe Chef::REST, "put_rest method" do + it "should create a url from the path and base url" do + URI.should_receive(:parse).with("url/monkey") + r = Chef::REST.new("url") + r.stub!(:run_request) + r.put_rest("monkey", "data") + end + + it "should call run_request :PUT with the composed url object and data" do + URI.stub!(:parse).and_return(true) + r = Chef::REST.new("url") + r.should_receive(:run_request).with(:PUT, true, "data").and_return(true) + r.put_rest("monkey", "data") + end +end + +describe Chef::REST, "run_request method" do + before(:each) do + @r = Chef::REST.new("url") + @url_mock = mock("URI", :null_object => true) + @url_mock.stub!(:host).and_return("one") + @url_mock.stub!(:port).and_return("80") + @url_mock.stub!(:path).and_return("/") + @url_mock.stub!(:query).and_return("foo=bar") + @url_mock.stub!(:scheme).and_return("https") + @url_mock.stub!(:to_s).and_return("https://one:80/?foo=bar") + @http_response_mock = mock("Net::HTTPSuccess", :null_object => true) + @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(true) + @http_response_mock.stub!(:body).and_return("ninja") + @http_mock = mock("Net::HTTP", :null_object => true) + @http_mock.stub!(:verify_mode=).and_return(true) + @http_mock.stub!(:read_timeout=).and_return(true) + @http_mock.stub!(:use_ssl=).with(true).and_return(true) + @data_mock = mock("Data", :null_object => true) + @data_mock.stub!(:to_json).and_return('{ "one": "two" }') + @request_mock = mock("Request", :null_object => true) + @request_mock.stub!(:body=).and_return(true) + @request_mock.stub!(:method).and_return(true) + @request_mock.stub!(:path).and_return(true) + @http_mock.stub!(:request).and_return(@http_response_mock) + @tf_mock = mock(Tempfile, { :print => true, :close => true }) + Tempfile.stub!(:new).with("chef-rest").and_return(@tf_mock) + end + + def do_run_request(method=:GET, data=false, limit=10, raw=false) + Net::HTTP.stub!(:new).and_return(@http_mock) + @r.run_request(method, @url_mock, data, limit, raw) + end + + it "should raise an exception if the redirect limit is 0" do + lambda { @r.run_request(:GET, "/", false, 0)}.should raise_error(ArgumentError) + end + + it "should use SSL if the url starts with https" do + @url_mock.should_receive(:scheme).and_return("https") + @http_mock.should_receive(:use_ssl=).with(true).and_return(true) + do_run_request + end + + it "should set the OpenSSL Verify Mode to verify_none if requested" do + @http_mock.should_receive(:verify_mode=).and_return(true) + do_run_request + end + + it "should set a read timeout based on the rest_timeout config option" do + Chef::Config[:rest_timeout] = 10 + @http_mock.should_receive(:read_timeout=).with(10).and_return(true) + do_run_request + end + + it "should build a new HTTP GET request" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json' } + ).and_return(@request_mock) + do_run_request + end + + it "should build a new HTTP POST request" do + Net::HTTP::Post.should_receive(:new).with("/", + { 'Accept' => 'application/json', "Content-Type" => 'application/json' } + ).and_return(@request_mock) + do_run_request(:POST, @data_mock) + end + + it "should build a new HTTP PUT request" do + Net::HTTP::Put.should_receive(:new).with("/", + { 'Accept' => 'application/json', "Content-Type" => 'application/json' } + ).and_return(@request_mock) + do_run_request(:PUT, @data_mock) + end + + it "should build a new HTTP DELETE request" do + Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json' } + ).and_return(@request_mock) + do_run_request(:DELETE) + end + + it "should raise an error if the method is not GET/PUT/POST/DELETE" do + lambda { do_run_request(:MONKEY) }.should raise_error(ArgumentError) + end + + it "should run an http request" do + @http_mock.should_receive(:request).and_return(@http_response_mock) + do_run_request + end + + it "should return the body of the response on success" do + do_run_request.should eql("ninja") + end + + it "should inflate the body as to an object if JSON is returned" do + @http_response_mock.stub!(:[]).with('content-type').and_return("application/json") + JSON.should_receive(:parse).with("ninja").and_return(true) + do_run_request + end + + it "should call run_request again on a Redirect response" do + @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) + @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(true) + @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path) + lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError) + end + + it "should raise an exception on an unsuccessful request" do + @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) + @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false) + @http_response_mock.should_receive(:error!) + do_run_request + end + + it "should build a new HTTP GET request without the application/json accept header for raw reqs" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {}).and_return(@request_mock) + do_run_request(:GET, false, 10, true) + end + + it "should create a tempfile for the output of a raw request" do + Tempfile.should_receive(:new).with("chef-rest").and_return(@tf_mock) + do_run_request(:GET, false, 10, true).should eql(@tf_mock) + end + + it "should populate the tempfile with the value of the raw request" do + @tf_mock.should_receive(:print, "ninja").once.and_return(true) + do_run_request(:GET, false, 10, true) + end + + it "should close the tempfile if we're doing a raw request" do + @tf_mock.should_receive(:close).once.and_return(true) + do_run_request(:GET, false, 10, true) + end + +end diff --git a/chef/spec/unit/runner_spec.rb b/chef/spec/unit/runner_spec.rb new file mode 100644 index 0000000000..f73d5efb22 --- /dev/null +++ b/chef/spec/unit/runner_spec.rb @@ -0,0 +1,103 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Runner do + before(:each) do + @mock_node = mock("Node", :null_object => true) + @mock_collection = mock("Resource Collection", :null_object => true) + @mock_provider = mock("Provider", :null_object => true) + @mock_resource = mock("Resource", :null_object => true) + new_runner + end + + it "should require a Node and a ResourceCollection" do + @mock_node.should_receive(:kind_of?).once.and_return(true) + @mock_collection.should_receive(:kind_of?).once.and_return(true) + runner = Chef::Runner.new(@mock_node, @mock_collection) + runner.should be_a_kind_of(Chef::Runner) + end + + it "should raise an exception if you pass the wrong kind of object to new" do + @mock_node.stub!(:kind_of?).and_return(false) + @mock_collecton.stub!(:kind_of?).and_return(false) + lambda { Chef::Runner.new(@mock_node, @mock_collection) }.should raise_error(ArgumentError) + end + + it "should pass each resource in the collection to a provider" do + @collection.should_receive(:each).once + @runner.converge + end + + it "should use the provider specified by the resource (if it has one)" do + provider = Chef::Provider::Easy.new(@node, @collection[0]) + @collection[0].should_receive(:provider).once.and_return(Chef::Provider::Easy) + Chef::Provider::Easy.should_receive(:new).once.and_return(provider) + @runner.converge + end + + it "should use the platform provider if it has one" do + Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) + @runner.converge + end + + it "should run the action for each resource" do + Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + provider.should_receive(:action_sell).once.and_return(true) + Chef::Provider::SnakeOil.should_receive(:new).once.and_return(provider) + @runner.converge + end + + it "should execute immediate actions on changed resources" do + Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider) + @collection << Chef::Resource::Cat.new("peanut", @collection) + @collection[1].notifies :buy, @collection[0], :immediately + @collection[1].updated = true + provider.should_receive(:action_buy).once.and_return(true) + @runner.converge + end + + it "should execute delayed actions on changed resources" do + Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil) + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider) + @collection << Chef::Resource::Cat.new("peanut", @collection) + @collection[1].notifies :buy, @collection[0], :delayed + @collection[1].updated = true + provider.should_receive(:action_buy).once.and_return(true) + @runner.converge + end + + def new_runner + @node = Chef::Node.new + @node.name "latte" + @node.operatingsystem "mac_os_x" + @node.operatingsystemversion "10.5.1" + @collection = Chef::ResourceCollection.new() + @collection << Chef::Resource::Cat.new("loulou", @collection) + Chef::Platform.set( + :resource => :cat, + :provider => Chef::Provider::SnakeOil + ) + @runner = Chef::Runner.new(@node, @collection) + end +end
\ No newline at end of file diff --git a/chef/spec/unit/search_index_spec.rb b/chef/spec/unit/search_index_spec.rb new file mode 100644 index 0000000000..ff234c6a41 --- /dev/null +++ b/chef/spec/unit/search_index_spec.rb @@ -0,0 +1,136 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::SearchIndex, "initialize method" do + it "should create a new Chef::SearchIndex object" do + mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.stub!(:new).and_return(mf) + Chef::SearchIndex.new.should be_kind_of(Chef::SearchIndex) + end + + it "should create a Ferret Indexer" do + mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.should_receive(:new).and_return(mf) + Chef::SearchIndex.new + end +end + +describe Chef::SearchIndex, "create_index_object method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + def do_create_index_object + index = Chef::SearchIndex.new + index.create_index_object(@fakeobj) + end + + it "should call to_index if the passed object responds to it" do + @fakeobj.should_receive(:respond_to?).with(:to_index).and_return(true) + @fakeobj.should_receive(:to_index).and_return(@the_pigeon) + do_create_index_object + end + + it "should use a hash if the passed argument does not have to_index (but is a hash)" do + @fakeobj.stub!(:respond_to?).with(:to_index).and_return(false) + @fakeobj.should_receive(:kind_of?).with(Hash).and_return(true) + do_create_index_object + end + + it "should raise SearchIndex exception if the hash does not contain an :id field" do + @the_pigeon.delete(:id) + lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex) + end + + it "should raise SearchIndex exception if the hash does not contain an :index_name field" do + @the_pigeon.delete(:index_name) + lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex) + end + + it "should raise SearchIndex exception if the hash new_object cannot be indexed" do + @fakeobj.stub!(:respond_to?).and_return(false) + @fakeobj.stub!(:kind_of?).and_return(false) + lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex) + end + + it "should turn index hash keys in to symbols if it has strings" do + @the_pigeon["john"] = "and_yoko" + do_create_index_object.should have_key(:john) + end +end + +describe Chef::SearchIndex, "add method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + def do_add + index = Chef::SearchIndex.new + index.add(@fakeobj) + end + + it "should send the resulting hash to the index" do + @mf.should_receive(:add_document).with(@the_pigeon) + do_add + end +end + +describe Chef::SearchIndex, "delete method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + def do_delete(object) + index = Chef::SearchIndex.new + index.delete(object) + end + + it "should delete the resulting hash to the index" do + @mf.should_receive(:delete).with(@the_pigeon[:id]) + do_delete(@fakeobj) + end +end + +describe Chef::SearchIndex, "commit method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + it "should commit index to disk" do + @mf.should_receive(:commit) + Chef::SearchIndex.new.commit + end +end diff --git a/chef/spec/unit/search_spec.rb b/chef/spec/unit/search_spec.rb new file mode 100644 index 0000000000..94e3560a23 --- /dev/null +++ b/chef/spec/unit/search_spec.rb @@ -0,0 +1,74 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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::Search, "initialize method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + it "should build a Chef::Search object" do + Chef::Search.new.should be_a_kind_of(Chef::Search) + end + + it "should build a Ferret search backend" do + Ferret::Index::Index.should_receive(:new).and_return(@mf) + Chef::Search.new + end +end + +describe Chef::Search, "search method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + end + + def do_search(type, query, &block) + Ferret::Index::Index.stub!(:new).and_return(@mf) + cs = Chef::Search.new + if Kernel.block_given? + cs.search(type, query, &block) + else + cs.search(type, query) + end + end + + it "should build the search query from the type and query provided" do + Ferret::Index::Index.stub!(:new).and_return(@mf) + cs = Chef::Search.new + cs.should_receive(:build_search_query).with(:node, "tag:monkey") + cs.search(:node, "tag:monkey") + end + + it "should call search_each with the custom block if a block is given" do + cp = lambda { |n,u| "noting to do here" } + @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", { :limit => :all }, &cp) + do_search(:node, "tag:monkey", &cp) + end + + it "should call search_each if a block is not given" do + @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", {:limit => :all}) + do_search(:node, "tag:monkey") + end + + it "should return the search results" do + @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", :limit => :all).and_return(true) + do_search(:node, "tag:monkey").should eql([]) + end +end
\ No newline at end of file diff --git a/chef/stories/chef-client b/chef/stories/chef-client new file mode 100644 index 0000000000..93b8656268 --- /dev/null +++ b/chef/stories/chef-client @@ -0,0 +1,26 @@ +Story: Applying a meal with chef-client + + As a systems administrator + I want to configure a system + So that I can drink more beer + + Scenario: The client should be rejected if the node is not verified + Given the node 'latte' + When it runs the chef-client + Then it should register with the Chef Server + + Scenario: The node registers for an OpenID + + Given the node 'latte' + When it runs the chef-client + Then it should register with the Chef Server + And CouchDB should have a 'openid_registration_latte' document + + Scenario: An administrator validates the OpenID Registration + + Given the user 'openid.hjksolutions.com/adam' + And the openid registration for 'latte' + When the user validates the registration + Then the registration validation should be true + + diff --git a/chef/stories/chef-client.rb b/chef/stories/chef-client.rb new file mode 100644 index 0000000000..3e07b8a847 --- /dev/null +++ b/chef/stories/chef-client.rb @@ -0,0 +1,46 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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__), "story_helper")) + +steps_for(:chef_client) do + # Given the node 'latte' + Given("the node '$node'") do |node| + @client = Chef::Client.new + @client.build_node(node) + end + + # Given it has not registered before + Given("it has not registered before") do + Chef::FileStore.load("registration", @client.safe_name) + end + + # When it runs the chef-client + + # Then it should register with the Chef Server + + # Then CouchDB should have a 'openid_registration_latte' document + + # Then the registration validation should be 'false' + +end + +with_steps_for(:chef_client) do + create_couchdb_database + run File.join(File.dirname(__FILE__), "chef-client") +end
\ No newline at end of file diff --git a/chef/stories/story_helper.rb b/chef/stories/story_helper.rb new file mode 100644 index 0000000000..cb0d1eb375 --- /dev/null +++ b/chef/stories/story_helper.rb @@ -0,0 +1,32 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# 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 'spec/story' + +require File.join(File.dirname(__FILE__), "..", "lib", "chef") +Dir[File.join(File.dirname(__FILE__), 'lib', '**', '*.rb')].sort.each { |lib| require lib } + +Chef::Config.log_level(:fatal) +Chef::Config[:couchdb_database] = "chef_test" + +def create_couchdb_database + c = Chef::CouchDB.new + c.create_db +end + diff --git a/chef/tasks/rspec.rb b/chef/tasks/rspec.rb new file mode 100644 index 0000000000..80b0a79557 --- /dev/null +++ b/chef/tasks/rspec.rb @@ -0,0 +1,54 @@ +require 'rubygems' +require 'rake' +require 'spec/rake/spectask' + +#desc "Run all examples" +#Spec::Rake::SpecTask.new('spec') do |t| +# t.spec_files = FileList[File.join(File.dirname(__FILE__), "..", "spec", "**", "*.rb")] +#end + +require 'spec/rake/spectask' +require 'spec/translator' + +CHEF_ROOT = File.join(File.dirname(__FILE__), "..") + +task :default => :spec + +desc "Run all specs in spec directory" +Spec::Rake::SpecTask.new(:spec) do |t| + t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['spec/**/*_spec.rb'] +end + +namespace :spec do + desc "Run all specs in spec directory with RCov" + Spec::Rake::SpecTask.new(:rcov) do |t| + t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['spec/**/*_spec.rb'] + t.rcov = true + t.rcov_opts = lambda do + IO.readlines("#{CHEF_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten + end + end + + desc "Print Specdoc for all specs" + Spec::Rake::SpecTask.new(:doc) do |t| + t.spec_opts = ["--format", "specdoc", "--dry-run"] + t.spec_files = FileList['spec/**/*_spec.rb'] + end + + [:unit].each do |sub| + desc "Run the specs under spec/#{sub}" + Spec::Rake::SpecTask.new(sub) do |t| + t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""] + t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"] + end + end + + desc "Translate/upgrade specs using the built-in translator" + task :translate do + translator = ::Spec::Translator.new + dir = CHEF_ROOT + '/spec' + translator.translate(dir, dir) + end +end
\ No newline at end of file |