diff options
author | Seth Chisamore <schisamo@opscode.com> | 2012-10-30 10:04:20 -0400 |
---|---|---|
committer | Seth Chisamore <schisamo@opscode.com> | 2012-10-30 10:04:20 -0400 |
commit | 9348c1c9c80ee757354d624b7dc1b78ebc7605c4 (patch) | |
tree | 4b929d5421f514b477aaa82d98516f82f1e62045 /chef | |
parent | 265a1b898c7235198d60b14d83b75754133b0884 (diff) | |
parent | 8366c2259a62c02cee2137a869cb401b578bada0 (diff) | |
download | chef-9348c1c9c80ee757354d624b7dc1b78ebc7605c4.tar.gz |
Merge branch 'OC-3564-remove-chef-server'
Diffstat (limited to 'chef')
51 files changed, 60 insertions, 5967 deletions
diff --git a/chef/Gemfile b/chef/Gemfile index b538e0bc1f..40868b9677 100644 --- a/chef/Gemfile +++ b/chef/Gemfile @@ -2,7 +2,6 @@ source :rubygems gemspec -gem "dep_selector", :group => :server, :platform => "ruby" gem "activesupport", :group => :compat_testing, :platform => "ruby" gem "ronn" diff --git a/chef/README.rdoc b/chef/README.rdoc index 43feffebaf..4a1801643c 100644 --- a/chef/README.rdoc +++ b/chef/README.rdoc @@ -2,9 +2,9 @@ == DESCRIPTION: -Chef is a configuration management tool designed to bring automation to your entire infrastructure. +Chef is a configuration management tool designed to bring automation to your entire infrastructure. -The Chef Wiki is the definitive source of user documentation. +The Chef Wiki is the definitive source of user documentation. * http://wiki.opscode.com/display/chef/Home @@ -22,7 +22,7 @@ You will also need to set up the repository with the appropriate branches. We do * http://wiki.opscode.com/display/opscode/Working+with+Git -Once your repository is set up, you can start working on the code. We do use BDD/TDD with RSpec and Cucumber, so you'll need to get a development environment running. +Once your repository is set up, you can start working on the code. We do use TDD with RSpec, so you'll need to get a development environment running. == REQUIREMENTS: @@ -39,16 +39,12 @@ In order to have a development environment where changes to the Chef code can be Install these via your platform's preferred method; for example apt, yum, ports, emerge, etc. * Git[http://git-scm.com/] -* Erlang/OTP[http://www.erlang.org/] -* CouchDB[http://couchdb.apache.org/] -* RabbitMQ[http://www.rabbitmq.com/] * GCC and C Standard Libraries, header files, etc. (i.e., build-essential on debian/ubuntu) * Ruby development package === Runtime Rubygem Dependencies ==== Chef Client and Solo * ohai -* bunny * erubis * highline * json (1.4.4 - 1.4.6) @@ -63,61 +59,12 @@ Install these via your platform's preferred method; for example apt, yum, ports, * net-ssh * fog -==== Chef Server, WebUI and Solr -All of the above, plus the following: -* coderay -* haml -* merb-assets -* merb-core -* merb-haml -* merb-helpers -* merb-param-protection -* ruby-openid -* thin - === Development Rubygem Dependencies * rake[http://rake.rubyforge.org/] * rspec[http://rspec.info/] -* cucumber[http://cukes.info/] Ohai is also by Opscode and available on GitHub, http://github.com/opscode/ohai/tree/master. -== Starting the Environment: - -=== On Mac OS X: -For ease of debugging, Chef includes a script to start each of the required -daemons in a separate Terminal.app tab via applescript: - - scripts/mac-dev-start features - -=== On Linux and BSD - -run the dev:features rake task. You may need to run it as root depending on how -your system is configured. - - rake dev:features - -=== Daemons -After starting the environment, you should have the following processes running: -* couchdb listening on port 5984 -* rabbitmq listening on port 5672 -* solr listening on port 8983 -* chef-solr-indexer connected as a client to rabbitmq -* chef-server listening on port 4000 -* chef-server-webui listening on port 4040 - -You'll know its running when you see: - - merb : chef-server (api) : worker (port 4000) ~ Starting Thin at port 4000 - merb : chef-server (api) : worker (port 4000) ~ Using Thin adapter on host 0.0.0.0 and port 4000. - merb : chef-server (api) : worker (port 4000) ~ Successfully bound to port 4000 - -You'll want to leave this terminal running the dev environment. - -=== Web Interface: - -With the dev environment running, you can now access the web interface via http://localhost:4040/. - == Spec testing: We use RSpec for unit/spec tests. It is not necessary to start the development @@ -125,23 +72,6 @@ environment to run the specs--they are completely standalone. rake spec -== Integration testing: - -We test integration with Cucumber. To run the full suite, run the rake task: - - rake features - -Subsets of the integration tests can be run with the various tasks in the -features namespace. To see the full list, run - - rake -T - -To run individual feature tests, you can take advantage of cucumber's tagging -support. Tag the feature you wish to run (tags are denoted with a leading `@' -sign), then use the cucumber command: - - cucumber -t @my_tag - == LINKS: Source: diff --git a/chef/chef.gemspec b/chef/chef.gemspec index 732601cc79..c807a85bf2 100644 --- a/chef/chef.gemspec +++ b/chef/chef.gemspec @@ -21,10 +21,8 @@ Gem::Specification.new do |s| s.add_dependency "ohai", ">= 0.6.0" s.add_dependency "rest-client", ">= 1.0.4", "< 1.7.0" - s.add_dependency "bunny", ">= 0.6.0", "< 0.8.0" s.add_dependency "json", ">= 1.4.4", "<= 1.6.1" s.add_dependency "yajl-ruby", "~> 1.1" - s.add_dependency "treetop", "~> 1.4.9" s.add_dependency "net-ssh", "~> 2.2.2" s.add_dependency "net-ssh-multi", "~> 1.1.0" # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not. diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb index e56e805dee..ef173c4212 100644 --- a/chef/lib/chef.rb +++ b/chef/lib/chef.rb @@ -6,9 +6,9 @@ # 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. @@ -27,8 +27,6 @@ require 'chef/resources' require 'chef/shell_out' require 'chef/daemon' -require 'chef/webui_user' -require 'chef/openid_registration' require 'chef/run_status' require 'chef/handler' diff --git a/chef/lib/chef/api_client.rb b/chef/lib/chef/api_client.rb index f95978afba..da05939c24 100644 --- a/chef/lib/chef/api_client.rb +++ b/chef/lib/chef/api_client.rb @@ -20,9 +20,6 @@ require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/couchdb' -require 'chef/certificate' -require 'chef/index_queue' require 'chef/mash' require 'chef/json_compat' require 'chef/search/query' @@ -32,51 +29,13 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable - - - DESIGN_DOCUMENT = { - "version" => 1, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "client") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "client") { - emit(doc.name, doc.name); - } - } - EOJS - } - } - } - - INDEX_OBJECT_TYPE = 'client'.freeze - - def self.index_object_type - INDEX_OBJECT_TYPE - end - - attr_accessor :couchdb_rev, :couchdb_id, :couchdb # Create a new Chef::ApiClient object. - def initialize(couchdb=nil) + def initialize @name = '' @public_key = nil @private_key = nil - @couchdb_rev = nil - @couchdb_id = nil @admin = false - @couchdb = (couchdb || Chef::CouchDB.new) end # Gets or sets the client name. @@ -127,17 +86,6 @@ class Chef ) end - # Creates a new public/private key pair, and populates the public_key and - # private_key attributes. - # - # @return [True] - def create_keys - results = Chef::Certificate.gen_keypair(self.name) - self.public_key(results[0].to_s) - self.private_key(results[1].to_s) - true - end - # The hash representation of the object. Includes the name and public_key, # but never the private key. # @@ -150,7 +98,6 @@ class Chef 'json_class' => self.class.name, "chef_type" => "client" } - result["_rev"] = @couchdb_rev if @couchdb_rev result end @@ -166,20 +113,9 @@ class Chef client.name(o["name"] || o["clientname"]) client.public_key(o["public_key"]) client.admin(o["admin"]) - client.couchdb_rev = o["_rev"] - client.couchdb_id = o["_id"] - client.index_id = client.couchdb_id client end - # List all the Chef::ApiClient objects in the CouchDB. If inflate is set - # to true, you will get the full list of all ApiClients, fully inflated. - def self.cdb_list(inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).list("clients", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - def self.list(inflate=false) if inflate response = Hash.new @@ -193,14 +129,6 @@ class Chef end end - # Load a client by name from CouchDB - # - # @params [String] The name of the client to load - # @return [Chef::ApiClient] The resulting Chef::ApiClient object - def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("client", name) - end - # Load a client by name via the API def self.load(name) response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{name}") @@ -213,24 +141,11 @@ class Chef end end - # Remove this client from the CouchDB - # - # @params [String] The name of the client to delete - # @return [Chef::ApiClient] The last version of the object - def cdb_destroy - @couchdb.delete("client", @name, @couchdb_rev) - end - # Remove this client via the REST API def destroy Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("clients/#{@name}") end - # Save this client to the CouchDB - def cdb_save - @couchdb_rev = @couchdb.store("client", @name, self)["rev"] - end - # Save this client via the REST API, returns a hash including the private key def save(new_key=false, validation=false) if validation @@ -256,11 +171,6 @@ class Chef Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("clients", self) end - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb ||= Chef::CouchDB.new).create_design_document("clients", DESIGN_DOCUMENT) - end - # As a string def to_s "client[#{@name}]" diff --git a/chef/lib/chef/certificate.rb b/chef/lib/chef/certificate.rb deleted file mode 100644 index 7943589c8e..0000000000 --- a/chef/lib/chef/certificate.rb +++ /dev/null @@ -1,161 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Christopher Brown (<cb@opscode.com>) -# Copyright:: Copyright (c) 2009 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/log' -require 'chef/config' -require 'chef/api_client' -require 'openssl' -require 'fileutils' - -class Chef - class Certificate - class << self - - # Generates a new CA Certificate and Key, and writes them out to - # Chef::Config[:signing_ca_cert] and Chef::Config[:signing_ca_key]. - def generate_signing_ca - ca_cert_file = Chef::Config[:signing_ca_cert] - ca_keypair_file = Chef::Config[:signing_ca_key] - - unless File.exists?(ca_cert_file) && File.exists?(ca_keypair_file) - Chef::Log.info("Creating new signing certificate") - - [ ca_cert_file, ca_keypair_file ].each do |f| - ca_basedir = File.dirname(f) - FileUtils.mkdir_p ca_basedir - end - - keypair = OpenSSL::PKey::RSA.generate(1024) - - ca_cert = OpenSSL::X509::Certificate.new - ca_cert.version = 3 - ca_cert.serial = 1 - info = [ - ["C", Chef::Config[:signing_ca_country]], - ["ST", Chef::Config[:signing_ca_state]], - ["L", Chef::Config[:signing_ca_location]], - ["O", Chef::Config[:signing_ca_org]], - ["OU", "Certificate Service"], - ["CN", "#{Chef::Config[:signing_ca_domain]}/emailAddress=#{Chef::Config[:signing_ca_email]}"] - ] - ca_cert.subject = ca_cert.issuer = OpenSSL::X509::Name.new(info) - ca_cert.not_before = Time.now - ca_cert.not_after = Time.now + 10 * 365 * 24 * 60 * 60 # 10 years - ca_cert.public_key = keypair.public_key - - ef = OpenSSL::X509::ExtensionFactory.new - ef.subject_certificate = ca_cert - ef.issuer_certificate = ca_cert - ca_cert.extensions = [ - ef.create_extension("basicConstraints", "CA:TRUE", true), - ef.create_extension("subjectKeyIdentifier", "hash"), - ef.create_extension("keyUsage", "cRLSign,keyCertSign", true), - ] - ca_cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") - ca_cert.sign keypair, OpenSSL::Digest::SHA1.new - - File.open(ca_cert_file, "w") { |f| f.write ca_cert.to_pem } - File.open(ca_keypair_file, File::WRONLY|File::EXCL|File::CREAT, 0600) { |f| f.write keypair.to_pem } - if (Chef::Config[:signing_ca_user] && Chef::Config[:signing_ca_group]) - FileUtils.chown(Chef::Config[:signing_ca_user], Chef::Config[:signing_ca_group], ca_keypair_file) - end - end - self - end - - # Creates a new key pair, and signs them with the signing certificate - # and key generated from generate_signing_ca above. - # - # All arguments are unused, though two arguments are accepted for compatibility. - # - # returns an array of [public_key, private_key] - def gen_keypair(common_name=nil, subject_alternative_name = nil) - - Chef::Log.info("Creating new key pair for #{common_name}") - - # generate client keypair - client_keypair = OpenSSL::PKey::RSA.generate(2048) - - return client_keypair.public_key, client_keypair - end - - def gen_validation_key(name=Chef::Config[:validation_client_name], key_file=Chef::Config[:validation_key], admin=false) - # Create the validation key - api_client = Chef::ApiClient.new - api_client.name(name) - api_client.admin(admin) - - begin - # If both the couch record and file exist, don't do anything. Otherwise, - # re-generate the validation key. - Chef::ApiClient.cdb_load(name) - - # The couch document was loaded successfully if we got to here; if we - # can't also load the file on the filesystem, we'll regenerate it all. - File.open(key_file, "r") do |file| - end - rescue Chef::Exceptions::CouchDBNotFound - create_validation_key(api_client, key_file) - rescue - if $!.class.name =~ /Errno::/ - Chef::Log.error("Error opening validation key: #{$!} -- destroying and regenerating") - begin - api_client.cdb_destroy - rescue Bunny::ServerDownError => e - # create_validation_key is gonna fail anyway, so let's just bail out. - Chef::Log.fatal("Could not de-index (to rabbitmq) previous validation key - rabbitmq is down! Start rabbitmq then restart chef-server to re-generate it") - raise - end - - create_validation_key(api_client, key_file) - else - raise - end - end - end - - private - def create_validation_key(api_client, key_file) - Chef::Log.info("Creating validation key...") - - api_client.create_keys - begin - api_client.cdb_save - rescue Bunny::ServerDownError => e - # If rabbitmq is down, the client will have been saved in CouchDB, - # but not in the index. - Chef::Log.fatal("Could not index (to rabbitmq) validation key - rabbitmq is down! Start rabbitmq then restart chef-server to re-generate it") - - # re-raise so the error bubbles out and nukes chef-server - raise e - end - - key_dir = File.dirname(key_file) - FileUtils.mkdir_p(key_dir) unless File.directory?(key_dir) - File.open(key_file, File::WRONLY|File::CREAT, 0600) do |f| - f.print(api_client.private_key) - end - if (Chef::Config[:signing_ca_user] && Chef::Config[:signing_ca_group]) - FileUtils.chown(Chef::Config[:signing_ca_user], Chef::Config[:signing_ca_group], key_file) - end - end - - end - end -end diff --git a/chef/lib/chef/checksum.rb b/chef/lib/chef/checksum.rb deleted file mode 100644 index fc1931174b..0000000000 --- a/chef/lib/chef/checksum.rb +++ /dev/null @@ -1,167 +0,0 @@ -# -# Author:: Tim Hinderliter (<tim@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'chef/log' -require 'chef/checksum/storage' -require 'uuidtools' - -class Chef - # == Chef::Checksum - # Checksum for an individual file; e.g., used for sandbox/cookbook uploading - # to track which files the system already manages. - class Checksum - attr_accessor :checksum, :create_time - attr_accessor :couchdb_id, :couchdb_rev - - attr_reader :storage - - # When a Checksum commits a sandboxed file to its final home in the checksum - # repo, this attribute will have the original on-disk path where the file - # was stored; it will be used if the commit is reverted to restore the sandbox - # to the pre-commit state. - attr_reader :original_committed_file_location - - DESIGN_DOCUMENT = { - "version" => 1, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "checksum") { - emit(doc.checksum, doc); - } - } - EOJS - }, - } - } - - # Creates a new Chef::Checksum object. - # === Arguments - # checksum::: the MD5 content hash of the file - # couchdb::: An instance of Chef::CouchDB - # - # === Returns - # object<Chef::Checksum>:: Duh. :) - def initialize(checksum=nil, couchdb=nil) - @create_time = Time.now.iso8601 - @checksum = checksum - @original_committed_file_location = nil - @storage = Storage::Filesystem.new(Chef::Config.checksum_path, checksum) - end - - def to_json(*a) - result = { - :checksum => checksum, - :create_time => create_time, - :json_class => self.class.name, - :chef_type => 'checksum', - - # For Chef::CouchDB (id_to_name, name_to_id) - :name => checksum - } - result.to_json(*a) - end - - def self.json_create(o) - checksum = new(o['checksum']) - checksum.create_time = o['create_time'] - - if o.has_key?('_rev') - checksum.couchdb_rev = o["_rev"] - o.delete("_rev") - end - if o.has_key?("_id") - checksum.couchdb_id = o["_id"] - o.delete("_id") - end - checksum - end - - # Moves the given +sandbox_file+ into the checksum repo using the path - # given by +file_location+ and saves the Checksum to the database - def commit_sandbox_file(sandbox_file) - @original_committed_file_location = sandbox_file - Chef::Log.info("Commiting sandbox file: move #{sandbox_file} to #{@storage}") - @storage.commit(sandbox_file) - cdb_save - end - - # Moves the checksum file back to its pre-commit location and deletes - # the checksum object from the database, effectively undoing +commit_sandbox_file+. - # Raises Chef::Exceptions::IllegalChecksumRevert if the original file location - # is unknown, which is will be the case if commit_sandbox_file was not - # previously called - def revert_sandbox_file_commit - unless original_committed_file_location - raise Chef::Exceptions::IllegalChecksumRevert, "Checksum #{self.inspect} cannot be reverted because the original sandbox file location is not known" - end - - Chef::Log.warn("Reverting sandbox file commit: moving #{@storage} back to #{original_committed_file_location}") - @storage.revert(original_committed_file_location) - cdb_destroy - end - - # Removes the on-disk file backing this checksum object, then removes it - # from the database - def purge - purge_file - cdb_destroy - end - - ## - # Couchdb - ## - - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("checksums", DESIGN_DOCUMENT) - end - - def self.cdb_list(inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).list("checksums", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - - def self.cdb_all_checksums(couchdb = nil) - rs = (couchdb || Chef::CouchDB.new).list("checksums", true) - rs["rows"].inject({}) { |hash_result, r| hash_result[r['key']] = 1; hash_result } - end - - def self.cdb_load(checksum, couchdb=nil) - # Probably want to look for a view here at some point - (couchdb || Chef::CouchDB.new).load("checksum", checksum) - end - - def cdb_destroy(couchdb=nil) - (couchdb || Chef::CouchDB.new).delete("checksum", checksum, @couchdb_rev) - end - - def cdb_save(couchdb=nil) - @couchdb_rev = (couchdb || Chef::CouchDB.new).store("checksum", checksum, self)["rev"] - end - - - private - - def purge_file - @storage.purge - end - - end -end diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb index 924a20ab3b..61c8806a66 100644 --- a/chef/lib/chef/config.rb +++ b/chef/lib/chef/config.rb @@ -130,8 +130,6 @@ class Chef # Used when OpenID authentication is enabled in the Web UI authorized_openid_identifiers nil authorized_openid_providers nil - openid_cstore_couchdb false - openid_cstore_path "/var/chef/openid/cstore" # The number of times the client should retry when registering with the server client_registration_retries 5 @@ -150,11 +148,6 @@ class Chef # Where cookbook files are stored on the server (by content checksum) checksum_path "/var/chef/checksums" - # CouchDB database name to use - couchdb_database "chef" - - couchdb_url "http://localhost:5984" - # Where chef's cache files should be stored file_cache_path platform_specific_path("/var/chef/cache") @@ -173,7 +166,6 @@ class Chef ## Daemonization Settings ## # What user should Chef run as? user nil - # What group should the chef-server, -solr, -solr-indexer run as group nil umask 0022 @@ -212,7 +204,7 @@ class Chef client_fork false enable_reporting true enable_reporting_url_fatals false - + # Set these to enable SSL authentication / mutual-authentication # with the server ssl_client_cert nil @@ -230,23 +222,6 @@ class Chef # Where should chef-solo download recipes from? recipe_url nil - solr_url "http://localhost:8983/solr" - solr_jetty_path "/var/chef/solr-jetty" - solr_data_path "/var/chef/solr/data" - solr_home_path "/var/chef/solr" - solr_heap_size "256M" - solr_java_opts nil - - # Parameters for connecting to RabbitMQ - amqp_host '0.0.0.0' - amqp_port '5672' - amqp_user 'chef' - amqp_pass 'testing' - amqp_vhost '/chef' - # Setting this to a UUID string also makes the queue durable - # (persist across rabbitmq restarts) - amqp_consumer_id "default" - # Sets the version of the signed header authentication protocol to use (see # the 'mixlib-authorization' project for more detail). Currently, versions # 1.0 and 1.1 are available; however, the chef-server must first be diff --git a/chef/lib/chef/cookbook_uploader.rb b/chef/lib/chef/cookbook_uploader.rb index 351a239bb8..8dd50ac043 100644 --- a/chef/lib/chef/cookbook_uploader.rb +++ b/chef/lib/chef/cookbook_uploader.rb @@ -4,7 +4,6 @@ require 'rest_client' require 'chef/exceptions' require 'chef/knife/cookbook_metadata' require 'chef/checksum_cache' -require 'chef/sandbox' require 'chef/cookbook_version' require 'chef/cookbook/syntax_check' require 'chef/cookbook/file_system_file_vendor' diff --git a/chef/lib/chef/cookbook_version.rb b/chef/lib/chef/cookbook_version.rb index a6f95e2c3b..0e11174a07 100644 --- a/chef/lib/chef/cookbook_version.rb +++ b/chef/lib/chef/cookbook_version.rb @@ -24,86 +24,11 @@ require 'chef/node' require 'chef/resource_definition_list' require 'chef/recipe' require 'chef/cookbook/file_vendor' -require 'chef/checksum' require 'chef/cookbook/metadata' require 'chef/version_class' class Chef - #== Chef::MinimalCookbookVersion - # MinimalCookbookVersion is a duck type of CookbookVersion, used - # internally by Chef Server as an optimization when determining the - # optimal cookbook set for a chef-client. - # - # MinimalCookbookVersion objects contain only enough information to - # solve the cookbook collection for a given run list. They *do not* - # contain enough information to generate the response. - # - # See also: Chef::CookbookVersionSelector - class MinimalCookbookVersion - - include Comparable - - ID = "id".freeze - NAME = 'name'.freeze - KEY = 'key'.freeze - VERSION = 'version'.freeze - VALUE = 'value'.freeze - DEPS = 'deps'.freeze - - DEPENDENCIES = 'dependencies'.freeze - - # Loads the full list of cookbooks, using a couchdb view to fetch - # only the id, name, version, and dependency constraints. This is - # enough information to solve for the cookbook collection for a - # given run list. After solving for the cookbook collection, you - # need to call +load_full_versions_of+ to convert - # MinimalCookbookVersion objects to their non-minimal counterparts - def self.load_all(couchdb) - # Example: - # {"id"=>"1a806f1c-b409-4d8e-abab-fa414ff5b96d", "key"=>"activemq", "value"=>{"version"=>"0.3.3", "deps"=>{"java"=>">= 0.0.0", "runit"=>">= 0.0.0"}}} - couchdb ||= Chef::CouchDB.new - couchdb.get_view("cookbooks", "all_with_version_and_deps")["rows"].map {|params| self.new(params) } - end - - # Loads the non-minimal CookbookVersion objects corresponding to - # +minimal_cookbook_versions+ from couchdb using a bulk GET. - def self.load_full_versions_of(minimal_cookbook_versions, couchdb) - database_ids = Array(minimal_cookbook_versions).map {|mcv| mcv.couchdb_id } - couchdb ||= Chef::CouchDB.new - couchdb.bulk_get(*database_ids) - end - - attr_reader :couchdb_id - attr_reader :name - attr_reader :version - attr_reader :deps - - def initialize(params) - @couchdb_id = params[ID] - @name = params[KEY] - @version = params[VALUE][VERSION] - @deps = params[VALUE][DEPS] - end - - # Returns the Cookbook::MinimalMetadata object for this cookbook - # version. - def metadata - @metadata ||= Cookbook::MinimalMetadata.new(@name, DEPENDENCIES => @deps) - end - - def legit_version - @legit_version ||= Chef::Version.new(@version) - end - - def <=>(o) - raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name - raise "Unexpected comparison to #{o}" unless o.respond_to?(:legit_version) - legit_version <=> o.legit_version - end - end - - # == Chef::CookbookVersion # CookbookVersion is a model object encapsulating the data about a Chef # cookbook. Chef supports maintaining multiple versions of a cookbook on a @@ -113,140 +38,10 @@ class Chef # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=, # recipe_filenames.insert) should dirty the manifest so it gets regenerated. class CookbookVersion - include Chef::IndexQueue::Indexable include Comparable COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] - DESIGN_DOCUMENT = { - "version" => 8, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.name, doc.name); - } - } - EOJS - }, - "all_with_version" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.cookbook_name, doc.version); - } - } - EOJS - }, - "all_with_version_and_deps" => { - "map" => <<-JS - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.cookbook_name, {version: doc.version, deps: doc.metadata.dependencies}); - } - } - JS - }, - "all_latest_version" => { - "map" => %q@ - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.cookbook_name, doc.version); - } - } - @, - "reduce" => %q@ - function(keys, values, rereduce) { - var result = null; - - for (var idx in values) { - var value = values[idx]; - - if (idx == 0) { - result = value; - continue; - } - - var valueParts = value.split('.').map(function(v) { return parseInt(v); }); - var resultParts = result.split('.').map(function(v) { return parseInt(v); }); - - if (valueParts[0] != resultParts[0]) { - if (valueParts[0] > resultParts[0]) { - result = value; - } - } - else if (valueParts[1] != resultParts[1]) { - if (valueParts[1] > resultParts[1]) { - result = value; - } - } - else if (valueParts[2] != resultParts[2]) { - if (valueParts[2] > resultParts[2]) { - result = value; - } - } - } - return result; - } - @ - }, - "all_latest_version_by_id" => { - "map" => %q@ - function(doc) { - if (doc.chef_type == "cookbook_version") { - emit(doc.cookbook_name, {version: doc.version, id:doc._id}); - } - } - @, - "reduce" => %q@ - function(keys, values, rereduce) { - var result = null; - - for (var idx in values) { - var value = values[idx]; - - if (idx == 0) { - result = value; - continue; - } - - var valueParts = value.version.split('.').map(function(v) { return parseInt(v); }); - var resultParts = result.version.split('.').map(function(v) { return parseInt(v); }); - - if (valueParts[0] != resultParts[0]) { - if (valueParts[0] > resultParts[0]) { - result = value; - } - } - else if (valueParts[1] != resultParts[1]) { - if (valueParts[1] > resultParts[1]) { - result = value; - } - } - else if (valueParts[2] != resultParts[2]) { - if (valueParts[2] > resultParts[2]) { - result = value; - } - } - } - return result; - } - @ - }, - } - } - attr_accessor :root_dir attr_accessor :definition_filenames attr_accessor :template_filenames @@ -259,10 +54,6 @@ class Chef attr_accessor :metadata attr_accessor :metadata_filenames attr_accessor :status - attr_accessor :couchdb_rev - attr_accessor :couchdb - - attr_reader :couchdb_id # attribute_filenames also has a setter that has non-default # functionality. @@ -401,7 +192,7 @@ class Chef # # === Returns # object<Chef::CookbookVersion>:: Duh. :) - def initialize(name, couchdb=nil) + def initialize(name) @name = name @frozen = false @attribute_filenames = Array.new @@ -416,9 +207,6 @@ class Chef @metadata_filenames = Array.new @root_dir = nil @root_filenames = Array.new - @couchdb_id = nil - @couchdb = couchdb || Chef::CouchDB.new - @couchdb_rev = nil @status = :ready @manifest = nil @file_vendor = nil @@ -761,7 +549,6 @@ class Chef result = manifest.dup result['frozen?'] = frozen_version? result['chef_type'] = 'cookbook_version' - result["_rev"] = couchdb_rev if couchdb_rev result.to_hash end @@ -773,15 +560,6 @@ class Chef def self.json_create(o) cookbook_version = new(o["cookbook_name"]) - if o.has_key?('_rev') - cookbook_version.couchdb_rev = o["_rev"] if o.has_key?("_rev") - o.delete("_rev") - end - if o.has_key?("_id") - cookbook_version.couchdb_id = o["_id"] if o.has_key?("_id") - cookbook_version.index_id = cookbook_version.couchdb_id - o.delete("_id") - end # We want the Chef::Cookbook::Metadata object to always be inflated cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) cookbook_version.manifest = o @@ -888,83 +666,6 @@ class Chef chef_server_rest.get_rest('cookbooks/_latest') end - ## - # Couchdb - ## - - def self.cdb_by_name(cookbook_name, couchdb=nil) - cdb = (couchdb || Chef::CouchDB.new) - options = { :startkey => cookbook_name, :endkey => cookbook_name } - rs = cdb.get_view("cookbooks", "all_with_version", options) - rs["rows"].inject({}) { |memo, row| memo.has_key?(row["key"]) ? memo[row["key"]] << row["value"] : memo[row["key"]] = [ row["value"] ]; memo } - end - - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("cookbooks", DESIGN_DOCUMENT) - end - - def self.cdb_list_latest(inflate=false, couchdb=nil) - couchdb ||= Chef::CouchDB.new - if inflate - doc_ids = cdb_list_latest_ids.map {|i|i["id"]} - couchdb.bulk_get(doc_ids) - else - results = couchdb.get_view("cookbooks", "all_latest_version", :group=>true)["rows"] - results.inject({}) { |mapped, row| mapped[row["key"]] = row["value"]; mapped} - end - end - - def self.cdb_list_latest_ids(inflate=false, couchdb=nil) - couchdb ||= Chef::CouchDB.new - results = couchdb.get_view("cookbooks", "all_latest_version_by_id", :group=>true)["rows"] - results.map { |name_and_id| name_and_id["value"]} - end - - def self.cdb_list(inflate=false, couchdb=nil) - couchdb ||= Chef::CouchDB.new - if inflate - couchdb.list("cookbooks", true)["rows"].collect{|r| r["value"]} - else - # If you modify this, please make sure the desc sorted order on the versions doesn't get broken. - couchdb.get_view("cookbooks", "all_with_version")["rows"].inject({}) { |mapped, row| mapped[row["key"]]||=Array.new; mapped[row["key"]].push(Chef::Version.new(row["value"])); mapped[row["key"]].sort!.reverse!; mapped} - end - end - - def self.cdb_load(name, version='latest', couchdb=nil) - cdb = couchdb || Chef::CouchDB.new - if version == "latest" || version == "_latest" - rs = cdb.get_view("cookbooks", "all_latest_version", :key => name, :descending => true, :group => true, :reduce => true)["rows"].first - cdb.load("cookbook_version", "#{rs["key"]}-#{rs["value"]}") - else - cdb.load("cookbook_version", "#{name}-#{version}") - end - end - - def cdb_destroy - (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev) - end - - # Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated - # checksum documents - def purge - checksums.keys.each do |checksum| - begin - Chef::Checksum.cdb_load(checksum, couchdb).purge - rescue Chef::Exceptions::CouchDBNotFound - end - end - cdb_destroy - end - - def cdb_save - @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"] - end - - def couchdb_id=(value) - @couchdb_id = value - @index_id = value - end - def <=>(o) raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name # FIXME: can we change the interface to the Metadata class such diff --git a/chef/lib/chef/cookbook_version_selector.rb b/chef/lib/chef/cookbook_version_selector.rb deleted file mode 100644 index 9e60f85639..0000000000 --- a/chef/lib/chef/cookbook_version_selector.rb +++ /dev/null @@ -1,168 +0,0 @@ -# -# Author:: Tim Hinderliter (<tim@opscode.com>) -# Copyright:: Copyright (c) 2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'dep_selector' - -class Chef - module CookbookVersionSelector - # This method replaces verbiage from DepSelector messages with - # Chef-domain-specific verbiage, such as replacing package with - # cookbook. - # - # TODO [cw, 2011/2/25]: this is a near-term hack. In the long run, - # we'll do this better. - def self.filter_dep_selector_message(message) - m = message - m.gsub!("Package", "Cookbook") - m.gsub!("package", "cookbook") - m.gsub!("Solution constraint", "Run list item") - m.gsub!("solution constraint", "run list item") - m - end - - # all_cookbooks - a hash mapping cookbook names to an array of - # available CookbookVersions. - # - # Creates a DependencyGraph from CookbookVersion objects - def self.create_dependency_graph_from_cookbooks(all_cookbooks) - dep_graph = DepSelector::DependencyGraph.new - - all_cookbooks.each do |cb_name, cb_versions| - cb_versions.each do |cb_version| - cb_version_deps = cb_version.metadata.dependencies - # TODO [cw. 2011/2/10]: CookbookVersion#version returns a - # String even though we're storing as a DepSelector::Version - # object underneath. This should be changed so that we - # return the object and handle proper serialization and - # de-serialization. For now, I'm just going to create a - # Version object from the String representation. - pv = dep_graph.package(cb_name).add_version(Chef::Version.new(cb_version.version)) - cb_version_deps.each_pair do |dep_name, constraint_str| - # if the dependency is specified as cookbook::recipe, - # extract the cookbook component - dep_cb_name = dep_name.split("::").first - constraint = Chef::VersionConstraint.new(constraint_str) - pv.dependencies << DepSelector::Dependency.new(dep_graph.package(dep_cb_name), constraint) - end - end - end - - dep_graph - end - - # Return a hash mapping cookbook names to a CookbookVersion - # object. If there is no solution that satisfies the constraints, - # the first run list item that caused unsatisfiability is - # returned. - # - # This is the final version-resolved list of cookbooks for the - # RunList. - # - # all_cookbooks - a hash mapping cookbook names to an array of - # available CookbookVersions. - # - # recipe_constraints - an array of hashes describing the expanded - # run list. Each element is a hash containing keys :name and - # :version_constraint. The :name component is either the - # fully-qualified recipe name (e.g. "cookbook1::non_default_recipe") - # or just a cookbook name, indicating the default recipe is to be - # run (e.g. "cookbook1"). - def self.constrain(all_cookbooks, recipe_constraints) - dep_graph = create_dependency_graph_from_cookbooks(all_cookbooks) - - # extract cookbook names from (possibly) fully-qualified recipe names - cookbook_constraints = recipe_constraints.map do |recipe_spec| - cookbook_name = (recipe_spec[:name][/^(.+)::/, 1] || recipe_spec[:name]) - DepSelector::SolutionConstraint.new(dep_graph.package(cookbook_name), - recipe_spec[:version_constraint]) - end - - # Pass in the list of all available cookbooks (packages) so that - # DepSelector can distinguish between "no version available for - # cookbook X" and "no such cookbook X" when an environment - # filters out all versions for a given cookbook. - all_packages = all_cookbooks.inject([]) do |acc, (cookbook_name, cookbook_versions)| - acc << dep_graph.package(cookbook_name) - acc - end - - # find a valid assignment of CoookbookVersions. If no valid - # assignment exists, indicate which run_list_item causes the - # unsatisfiability and try to hint at what might be wrong. - soln = - begin - DepSelector::Selector.new(dep_graph).find_solution(cookbook_constraints, all_packages) - rescue DepSelector::Exceptions::InvalidSolutionConstraints => e - non_existent_cookbooks = e.non_existent_packages.map {|constraint| constraint.package.name} - cookbooks_with_no_matching_versions = e.constrained_to_no_versions.map {|constraint| constraint.package.name} - - # Spend a whole lot of effort for pluralizing and - # prettifying the message. - message = "" - if non_existent_cookbooks.length > 0 - message += "no such " + (non_existent_cookbooks.length > 1 ? "cookbooks" : "cookbook") - message += " #{non_existent_cookbooks.join(", ")}" - end - - if cookbooks_with_no_matching_versions.length > 0 - if message.length > 0 - message += "; " - end - - message += "no versions match the constraints on " + (cookbooks_with_no_matching_versions.length > 1 ? "cookbooks" : "cookbook") - message += " #{cookbooks_with_no_matching_versions.join(", ")}" - end - - message = "Run list contains invalid items: #{message}." - - raise Chef::Exceptions::CookbookVersionSelection::InvalidRunListItems.new(message, non_existent_cookbooks, cookbooks_with_no_matching_versions) - rescue DepSelector::Exceptions::NoSolutionExists => e - raise Chef::Exceptions::CookbookVersionSelection::UnsatisfiableRunListItem.new(filter_dep_selector_message(e.message), e.unsatisfiable_solution_constraint, e.disabled_non_existent_packages, e.disabled_most_constrained_packages) - end - - - # map assignment back to CookbookVersion objects - selected_cookbooks = {} - soln.each_pair do |cb_name, cb_version| - # TODO [cw, 2011/2/10]: related to the TODO in - # create_dependency_graph_from_cookbooks, cbv.version - # currently returns a String, so we must compare to - # cb_version.to_s, since it's a for-real Version object. - selected_cookbooks[cb_name] = all_cookbooks[cb_name].find{|cbv| cbv.version == cb_version.to_s} - end - selected_cookbooks - end - - # Expands the run_list, constrained to the environment's CookbookVersion - # constraints. - # - # Returns: - # Hash of: name to CookbookVersion - def self.expand_to_cookbook_versions(run_list, environment, couchdb=nil) - # expand any roles in this run_list. - expanded_run_list = run_list.expand(environment, 'couchdb', :couchdb => couchdb).recipes.with_version_constraints - - cookbooks_for_environment = Chef::Environment.cdb_minimal_filtered_versions(environment, couchdb) - cookbook_collection = constrain(cookbooks_for_environment, expanded_run_list) - full_cookbooks = Chef::MinimalCookbookVersion.load_full_versions_of(cookbook_collection.values, couchdb) - full_cookbooks.inject({}) do |cb_map, cookbook_version| - cb_map[cookbook_version.name] = cookbook_version - cb_map - end - end - end -end diff --git a/chef/lib/chef/couchdb.rb b/chef/lib/chef/couchdb.rb deleted file mode 100644 index 71ee196462..0000000000 --- a/chef/lib/chef/couchdb.rb +++ /dev/null @@ -1,246 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Christopher Brown (<cb@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'chef/mixin/params_validate' -require 'chef/config' -require 'chef/rest' -require 'chef/log' -require 'digest/sha2' -require 'chef/json_compat' - -# We want to fail on create if uuidtools isn't installed -begin - require 'uuidtools' -rescue LoadError -end - -class Chef - class CouchDB - include Chef::Mixin::ParamsValidate - - def initialize(url=nil, db=Chef::Config[:couchdb_database]) - url ||= Chef::Config[:couchdb_url] - @db = db - @rest = Chef::REST.new(url, nil, nil) - end - - def couchdb_database(args=nil) - @db = args || @db - end - - def create_id_map - create_design_document( - "id_map", - { - "version" => 1, - "language" => "javascript", - "views" => { - "name_to_id" => { - "map" => <<-EOJS - function(doc) { - emit([ doc.chef_type, doc.name], doc._id); - } - EOJS - }, - "id_to_name" => { - "map" => <<-EOJS - function(doc) { - emit(doc._id, [ doc.chef_type, doc.name ]); - } - EOJS - } - } - } - ) - end - - def create_db(check_for_existing=true) - @database_list = @rest.get_rest("_all_dbs") - if !check_for_existing || !@database_list.any? { |db| db == couchdb_database } - response = @rest.put_rest(couchdb_database, Hash.new) - end - couchdb_database - end - - def create_design_document(name, data) - to_update = true - begin - old_doc = @rest.get_rest("#{couchdb_database}/_design/#{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 because: #{$!}") - end - if to_update - @rest.put_rest("#{couchdb_database}/_design%2F#{name}", data) - end - true - end - - # Save the object to Couch. Add to index if the object supports it. - def store(obj_type, name, object) - validate( - { - :obj_type => obj_type, - :name => name, - :object => object, - }, - { - :object => { :respond_to => :to_json }, - } - ) - rows = get_view("id_map", "name_to_id", :key => [ obj_type, name ])["rows"] - uuid = rows.empty? ? UUIDTools::UUID.random_create.to_s : rows.first.fetch("id") - - db_put_response = @rest.put_rest("#{couchdb_database}/#{uuid}", object) - - if object.respond_to?(:add_to_index) - Chef::Log.info("Sending #{obj_type}(#{uuid}) to the index queue for addition.") - object.add_to_index(:database => couchdb_database, :id => uuid, :type => obj_type) - end - - db_put_response - end - - def load(obj_type, name) - validate( - { - :obj_type => obj_type, - :name => name, - }, - { - :obj_type => { :kind_of => String }, - :name => { :kind_of => String }, - } - ) - doc = find_by_name(obj_type, name) - doc.couchdb = self if doc.respond_to?(:couchdb) - doc - end - - def delete(obj_type, name, rev=nil) - validate( - { - :obj_type => obj_type, - :name => name, - }, - { - :obj_type => { :kind_of => String }, - :name => { :kind_of => String }, - } - ) - del_id = nil - object, uuid = find_by_name(obj_type, name, true) - unless rev - if object.respond_to?(:couchdb_rev) - rev = object.couchdb_rev - else - rev = object['_rev'] - end - end - response = @rest.delete_rest("#{couchdb_database}/#{uuid}?rev=#{rev}") - response.couchdb = self if response.respond_to?(:couchdb=) - - if object.respond_to?(:delete_from_index) - Chef::Log.info("Sending #{obj_type}(#{uuid}) to the index queue for deletion..") - object.delete_from_index(:database => couchdb_database, :id => uuid, :type => obj_type) - end - - response - end - - def list(view, inflate=false) - validate( - { - :view => view, - }, - { - :view => { :kind_of => String } - } - ) - if inflate - r = @rest.get_rest(view_uri(view, "all")) - r["rows"].each { |i| i["value"].couchdb = self if i["value"].respond_to?(:couchdb=) } - r - else - r = @rest.get_rest(view_uri(view, "all_id")) - end - r - end - - def has_key?(obj_type, name) - validate( - { - :obj_type => obj_type, - :name => name, - }, - { - :obj_type => { :kind_of => String }, - :name => { :kind_of => String }, - } - ) - begin - find_by_name(obj_type, name) - true - rescue - false - end - end - - def find_by_name(obj_type, name, with_id=false) - r = get_view("id_map", "name_to_id", :key => [ obj_type, name ], :include_docs => true) - if r["rows"].length == 0 - raise Chef::Exceptions::CouchDBNotFound, "Cannot find #{obj_type} #{name} in CouchDB!" - end - if with_id - [ r["rows"][0]["doc"], r["rows"][0]["id"] ] - else - r["rows"][0]["doc"] - end - end - - def get_view(design, view, options={}) - view_string = view_uri(design, view) - view_string << "?" if options.length != 0 - view_string << options.map { |k,v| "#{k}=#{URI.escape(v.to_json)}"}.join('&') - @rest.get_rest(view_string) - end - - def bulk_get(*to_fetch) - response = @rest.post_rest("#{couchdb_database}/_all_docs?include_docs=true", { "keys" => to_fetch.flatten }) - response["rows"].collect { |r| r["doc"] } - end - - def view_uri(design, view) - "#{couchdb_database}/_design/#{design}/_view/#{view}" - end - - def server_stats - @rest.get_rest('/') - end - - def db_stats - @rest.get_rest("/#{@db}") - end - - end -end diff --git a/chef/lib/chef/data_bag.rb b/chef/lib/chef/data_bag.rb index 32188c0861..9ce6215b20 100644 --- a/chef/lib/chef/data_bag.rb +++ b/chef/lib/chef/data_bag.rb @@ -21,9 +21,7 @@ require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/couchdb' require 'chef/data_bag_item' -require 'chef/index_queue' require 'chef/mash' require 'chef/json_compat' @@ -32,58 +30,18 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable VALID_NAME = /^[\-[:alnum:]_]+$/ - DESIGN_DOCUMENT = { - "version" => 2, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "data_bag") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "data_bag") { - emit(doc.name, doc.name); - } - } - EOJS - }, - "entries" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "data_bag_item") { - emit(doc.data_bag, doc.raw_data.id); - } - } - EOJS - } - } - } - def self.validate_name!(name) unless name =~ VALID_NAME raise Exceptions::InvalidDataBagName, "DataBags must have a name matching #{VALID_NAME.inspect}, you gave #{name.inspect}" end end - attr_accessor :couchdb_rev, :couchdb_id, :couchdb - # Create a new Chef::DataBag - def initialize(couchdb=nil) + def initialize @name = '' - @couchdb_rev = nil - @couchdb_id = nil - @couchdb = (couchdb || Chef::CouchDB.new) end def name(arg=nil) @@ -100,7 +58,6 @@ class Chef 'json_class' => self.class.name, "chef_type" => "data_bag", } - result["_rev"] = @couchdb_rev if @couchdb_rev result end @@ -121,20 +78,9 @@ class Chef def self.json_create(o) bag = new bag.name(o["name"]) - bag.couchdb_rev = o["_rev"] if o.has_key?("_rev") - bag.couchdb_id = o["_id"] if o.has_key?("_id") - bag.index_id = bag.couchdb_id bag end - # List all the Chef::DataBag objects in the CouchDB. If inflate is set to true, you will get - # the full list of all Roles, fully inflated. - def self.cdb_list(inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).list("data_bags", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - def self.list(inflate=false) if inflate # Can't search for all data bags like other objects, fall back to N+1 :( @@ -147,11 +93,6 @@ class Chef end end - # Load a Data Bag by name from CouchDB - def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("data_bag", name) - end - # Load a Data Bag by name via either the RESTful API or local data_bag_path if run in solo mode def self.load(name) if Chef::Config[:solo] @@ -169,27 +110,10 @@ class Chef end end - # Remove this Data Bag from CouchDB - def cdb_destroy - removed = @couchdb.delete("data_bag", @name, @couchdb_rev) - rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name) - rs["rows"].each do |row| - row["doc"].couchdb = couchdb - row["doc"].cdb_destroy - end - removed - end - def destroy chef_server_rest.delete_rest("data/#{@name}") end - # Save this Data Bag to the CouchDB - def cdb_save - results = @couchdb.store("data_bag", @name, self) - @couchdb_rev = results["rev"] - end - # Save the Data Bag via RESTful API def save begin @@ -211,24 +135,6 @@ class Chef self end - # List all the items in this Bag from CouchDB - # The self.load method does this through the REST API - def list(inflate=false) - rs = nil - if inflate - rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name) - rs["rows"].collect { |r| r["doc"] } - else - rs = @couchdb.get_view("data_bags", "entries", :startkey => @name, :endkey => @name) - rs["rows"].collect { |r| r["value"] } - end - end - - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("data_bags", DESIGN_DOCUMENT) - end - # As a string def to_s "data_bag[#{@name}]" diff --git a/chef/lib/chef/data_bag_item.rb b/chef/lib/chef/data_bag_item.rb index 87bde509a5..3528ba724a 100644 --- a/chef/lib/chef/data_bag_item.rb +++ b/chef/lib/chef/data_bag_item.rb @@ -23,8 +23,6 @@ require 'forwardable' require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/couchdb' -require 'chef/index_queue' require 'chef/data_bag' require 'chef/mash' require 'chef/json_compat' @@ -36,35 +34,9 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable VALID_ID = /^[\-[:alnum:]_]+$/ - DESIGN_DOCUMENT = { - "version" => 1, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "data_bag_item") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "data_bag_item") { - emit(doc.name, doc.name); - } - } - EOJS - } - } - } - def self.validate_id!(id_str) if id_str.nil? || ( id_str !~ VALID_ID ) raise Exceptions::InvalidDataBagItemID, "Data Bag items must have an id matching #{VALID_ID.inspect}, you gave: #{id_str.inspect}" @@ -74,16 +46,12 @@ class Chef # Define all Hash's instance methods as delegating to @raw_data def_delegators(:@raw_data, *(Hash.instance_methods - Object.instance_methods)) - attr_accessor :couchdb_rev, :couchdb_id, :couchdb attr_reader :raw_data # Create a new Chef::DataBagItem - def initialize(couchdb=nil) - @couchdb_rev = nil - @couchdb_id = nil + def initialize @data_bag = nil @raw_data = Mash.new - @couchdb = couchdb || Chef::CouchDB.new end def chef_server_rest @@ -138,7 +106,6 @@ class Chef result = self.raw_data result["chef_type"] = "data_bag_item" result["data_bag"] = self.data_bag - result["_rev"] = @couchdb_rev if @couchdb_rev result end @@ -151,7 +118,6 @@ class Chef "data_bag" => self.data_bag, "raw_data" => self.raw_data } - result["_rev"] = @couchdb_rev if @couchdb_rev result.to_json(*a) end @@ -169,24 +135,11 @@ class Chef o.delete("chef_type") o.delete("json_class") o.delete("name") - if o.has_key?("_rev") - bag_item.couchdb_rev = o["_rev"] - o.delete("_rev") - end - if o.has_key?("_id") - bag_item.couchdb_id = o["_id"] - bag_item.index_id = bag_item.couchdb_id - o.delete("_id") - end + bag_item.raw_data = Mash.new(o["raw_data"]) bag_item end - # Load a Data Bag Item by name from CouchDB - def self.cdb_load(data_bag, name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("data_bag_item", object_name(data_bag, name)) - end - # Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode def self.load(data_bag, name) if Chef::Config[:solo] @@ -205,21 +158,10 @@ class Chef end end - # Remove this Data Bag Item from CouchDB - def cdb_destroy - Chef::Log.debug "Destroying data bag item: #{self.inspect}" - @couchdb.delete("data_bag_item", object_name, @couchdb_rev) - end - def destroy(data_bag=data_bag, databag_item=name) chef_server_rest.delete_rest("data/#{data_bag}/#{databag_item}") end - # Save this Data Bag Item to CouchDB - def cdb_save - @couchdb_rev = @couchdb.store("data_bag_item", object_name, self)["rev"] - end - # Save this Data Bag Item via RESTful API def save(item_id=@raw_data['id']) r = chef_server_rest @@ -242,11 +184,6 @@ class Chef self end - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("data_bag_items", DESIGN_DOCUMENT) - end - def ==(other) other.respond_to?(:to_hash) && other.respond_to?(:data_bag) && diff --git a/chef/lib/chef/environment.rb b/chef/lib/chef/environment.rb index 1e2cea282b..00cc253083 100644 --- a/chef/lib/chef/environment.rb +++ b/chef/lib/chef/environment.rb @@ -22,8 +22,6 @@ require 'chef/config' require 'chef/mash' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/couchdb' -require 'chef/index_queue' require 'chef/version_constraint' class Chef @@ -33,52 +31,15 @@ class Chef include Chef::Mixin::ParamsValidate include Chef::Mixin::FromFile - include Chef::IndexQueue::Indexable COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:[\s]+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:[\s]+).+)$/.freeze - attr_accessor :couchdb, :couchdb_rev - attr_reader :couchdb_id - - DESIGN_DOCUMENT = { - "version" => 1, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "environment") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "environment") { - emit(doc.name, doc.name); - } - } - EOJS - } - } - } - - def initialize(couchdb=nil) + def initialize @name = '' @description = '' @default_attributes = Mash.new @override_attributes = Mash.new @cookbook_versions = Hash.new - @couchdb_rev = nil - @couchdb_id = nil - @couchdb = couchdb || Chef::CouchDB.new - end - - def couchdb_id=(value) - @couchdb_id = value - self.index_id = value end def chef_server_rest @@ -163,7 +124,6 @@ class Chef "default_attributes" => @default_attributes, "override_attributes" => @override_attributes } - result["_rev"] = couchdb_rev if couchdb_rev result end @@ -260,17 +220,9 @@ class Chef environment.cookbook_versions(o["cookbook_versions"]) environment.default_attributes(o["default_attributes"]) environment.override_attributes(o["override_attributes"]) - environment.couchdb_rev = o["_rev"] if o.has_key?("_rev") - environment.couchdb_id = o["_id"] if o.has_key?("_id") environment end - def self.cdb_list(inflate=false, couchdb=nil) - es = (couchdb || Chef::CouchDB.new).list("environments", inflate) - lookup = (inflate ? "value" : "key") - es["rows"].collect { |e| e[lookup] } - end - def self.list(inflate=false) if inflate response = Hash.new @@ -283,34 +235,14 @@ class Chef end end - def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("environment", name) - end - def self.load(name) chef_server_rest.get_rest("environments/#{name}") end - def self.exists?(name, couchdb) - begin - self.cdb_load(name, couchdb) - rescue Chef::Exceptions::CouchDBNotFound - nil - end - end - - def cdb_destroy - couchdb.delete("environment", @name, couchdb_rev) - end - def destroy chef_server_rest.delete_rest("environments/#{@name}") end - def cdb_save - self.couchdb_rev = couchdb.store("environment", @name, self)["rev"] - end - def save begin chef_server_rest.put_rest("environments/#{@name}", self) @@ -326,106 +258,6 @@ class Chef self end - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("environments", DESIGN_DOCUMENT) - end - - # Loads the set of Chef::CookbookVersion objects available to a given environment - # === Returns - # Hash - # i.e. - # { - # "cookbook_name" => [ Chef::CookbookVersion ... ] ## the array of CookbookVersions is sorted highest to lowest - # } - # - # There will be a key for every cookbook. If no CookbookVersions - # are available for the specified environment the value will be an - # empty list. - # - def self.cdb_load_filtered_cookbook_versions(name, couchdb=nil) - version_constraints = cdb_load(name, couchdb).cookbook_versions.inject({}) {|res, (k,v)| res[k] = Chef::VersionConstraint.new(v); res} - - # inject all cookbooks into the hash while filtering out restricted versions, then sort the individual arrays - cookbook_list = Chef::CookbookVersion.cdb_list(true, couchdb) - - filtered_list = cookbook_list.inject({}) do |res, cookbook| - # FIXME: should cookbook.version return a Chef::Version? - version = Chef::Version.new(cookbook.version) - requirement_satisfied = version_constraints.has_key?(cookbook.name) ? version_constraints[cookbook.name].include?(version) : true - # we want a key for every cookbook, even if no versions are available - res[cookbook.name] ||= [] - res[cookbook.name] << cookbook if requirement_satisfied - res - end - - sorted_list = filtered_list.inject({}) do |res, (cookbook_name, versions)| - res[cookbook_name] = versions.sort.reverse - res - end - - sorted_list - end - - # Like +cdb_load_filtered_cookbook_versions+, loads the set of - # cookbooks available in a given environment. The difference is that - # this method will load Chef::MinimalCookbookVersion objects that - # contain only the information necessary for solving a cookbook - # collection for a given run list. The user of this method must call - # Chef::MinimalCookbookVersion.load_full_versions_of() after solving - # the cookbook collection to get the full objects. - # === Returns - # Hash - # i.e. - # { - # "cookbook_name" => [ Chef::CookbookVersion ... ] ## the array of CookbookVersions is sorted highest to lowest - # } - # - # There will be a key for every cookbook. If no CookbookVersions - # are available for the specified environment the value will be an - # empty list. - def self.cdb_minimal_filtered_versions(name, couchdb=nil) - version_constraints = cdb_load(name, couchdb).cookbook_versions.inject({}) {|res, (k,v)| res[k] = Chef::VersionConstraint.new(v); res} - - # inject all cookbooks into the hash while filtering out restricted versions, then sort the individual arrays - cookbook_list = Chef::MinimalCookbookVersion.load_all(couchdb) - - filtered_list = cookbook_list.inject({}) do |res, cookbook| - # FIXME: should cookbook.version return a Chef::Version? - version = Chef::Version.new(cookbook.version) - requirement_satisfied = version_constraints.has_key?(cookbook.name) ? version_constraints[cookbook.name].include?(version) : true - # we want a key for every cookbook, even if no versions are available - res[cookbook.name] ||= [] - res[cookbook.name] << cookbook if requirement_satisfied - res - end - - sorted_list = filtered_list.inject({}) do |res, (cookbook_name, versions)| - res[cookbook_name] = versions.sort.reverse - res - end - - sorted_list - end - - def self.cdb_load_filtered_recipe_list(name, couchdb=nil) - cdb_load_filtered_cookbook_versions(name, couchdb).map do |cb_name, cb| - if cb.empty? # no available versions - [] # empty list elided with flatten - else - latest_version = cb.first - latest_version.recipe_filenames_by_name.keys.map do |recipe| - case recipe - when DEFAULT - cb_name - else - "#{cb_name}::#{recipe}" - end - end - end - end.flatten - end - def self.load_filtered_recipe_list(environment) chef_server_rest.get_rest("environments/#{environment}/recipes") end @@ -451,16 +283,5 @@ class Chef end end - def self.create_default_environment(couchdb=nil) - couchdb = couchdb || Chef::CouchDB.new - begin - Chef::Environment.cdb_load('_default', couchdb) - rescue Chef::Exceptions::CouchDBNotFound - env = Chef::Environment.new(couchdb) - env.name '_default' - env.description 'The default Chef environment' - env.cdb_save - end - end end end diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb index c5d213f8b3..87802639d3 100644 --- a/chef/lib/chef/exceptions.rb +++ b/chef/lib/chef/exceptions.rb @@ -54,7 +54,6 @@ class Chef class Group < RuntimeError; end class Link < RuntimeError; end class Mount < RuntimeError; end - class CouchDBNotFound < RuntimeError; end class PrivateKeyMissing < RuntimeError; end class CannotWritePrivateKey < RuntimeError; end class RoleNotFound < RuntimeError; end @@ -112,7 +111,7 @@ class Chef # File operation attempted but no permissions to perform it class InsufficientPermissions < RuntimeError; end - + # Ifconfig failed class Ifconfig < RuntimeError; end diff --git a/chef/lib/chef/index_queue.rb b/chef/lib/chef/index_queue.rb deleted file mode 100644 index b350949aae..0000000000 --- a/chef/lib/chef/index_queue.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright (c) 2009 Daniel DeLeo -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "singleton" -require "bunny" - -require "chef/index_queue/amqp_client" -require "chef/index_queue/indexable" -require "chef/index_queue/consumer" - -class Chef - module IndexQueue - end -end diff --git a/chef/lib/chef/index_queue/amqp_client.rb b/chef/lib/chef/index_queue/amqp_client.rb deleted file mode 100644 index a7d155f4d1..0000000000 --- a/chef/lib/chef/index_queue/amqp_client.rb +++ /dev/null @@ -1,116 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright (c) 2009 Daniel DeLeo -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -class Chef - module IndexQueue - class AmqpClient - VNODES = 1024 - - include Singleton - - def initialize - reset! - end - - def reset! - @amqp_client && amqp_client.connected? && amqp_client.stop - @amqp_client = nil - @exchange = nil - end - - def stop - @amqp_client && @amqp_client.stop - end - - def amqp_client - unless @amqp_client - begin - @amqp_client = Bunny.new(amqp_opts) - Chef::Log.debug "Starting AMQP connection with client settings: #{@amqp_client.inspect}" - @amqp_client.start - @amqp_client.qos(:prefetch_count => 1) - rescue Bunny::ServerDownError => e - Chef::Log.fatal "Could not connect to rabbitmq. Is it running, reachable, and configured correctly?" - raise e - rescue Bunny::ProtocolError => e - Chef::Log.fatal "Connection to rabbitmq refused. Check your rabbitmq configuration and chef's amqp* settings" - raise e - end - end - @amqp_client - end - - def exchange - @exchange ||= amqp_client.exchange("chef-indexer", :durable => true, :type => :fanout) - end - - def disconnected! - Chef::Log.error("Disconnected from the AMQP Broker (RabbitMQ)") - @amqp_client = nil - reset! - end - - def queue_for_object(obj_id) - retries = 0 - vnode_tag = obj_id_to_int(obj_id) % VNODES - begin - yield amqp_client.queue("vnode-#{vnode_tag}", :passive => false, :durable => true, :exclusive => false, :auto_delete => false) - rescue Bunny::ServerDownError, Bunny::ConnectionError, Errno::ECONNRESET - disconnected! - if (retries += 1) < 2 - Chef::Log.info("Attempting to reconnect to the AMQP broker") - retry - else - Chef::Log.fatal("Could not re-connect to the AMQP broker, giving up") - raise - end - end - end - - private - - # Sometimes object ids are "proper" UUIDs, like "64bc00eb-120b-b6a2-ec0e-34fc90d151be" - # and sometimes they omit the dashes, like "64bc00eb120bb6a2ec0e34fc90d151be" - # UUIDTools uses different methods to parse the different styles. - def obj_id_to_int(obj_id) - UUIDTools::UUID.parse(obj_id).to_i - rescue ArgumentError - UUIDTools::UUID.parse_hexdigest(obj_id).to_i - end - - def durable_queue? - !!Chef::Config[:amqp_consumer_id] - end - - def consumer_id - Chef::Config[:amqp_consumer_id] || UUIDTools::UUID.random_create.to_s - end - - def amqp_opts - { :spec => '08', - :host => Chef::Config[:amqp_host], - :port => Chef::Config[:amqp_port], - :user => Chef::Config[:amqp_user], - :pass => Chef::Config[:amqp_pass], - :vhost => Chef::Config[:amqp_vhost]} - end - - end - end -end - diff --git a/chef/lib/chef/index_queue/consumer.rb b/chef/lib/chef/index_queue/consumer.rb deleted file mode 100644 index 8701cffa47..0000000000 --- a/chef/lib/chef/index_queue/consumer.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright (c) 2009 Daniel DeLeo -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -class Chef - module IndexQueue - module Consumer - module ClassMethods - def expose(*methods) - @exposed_methods = Array(@exposed_methods) - @exposed_methods += methods - end - - def exposed_methods - @exposed_methods || [] - end - - def whitelisted?(method_name) - exposed_methods.include?(method_name) - end - end - - def self.included(including_class) - including_class.send(:extend, ClassMethods) - end - - def run - Chef::Log.debug("Starting Index Queue Consumer") - AmqpClient.instance.queue # triggers connection setup - - begin - AmqpClient.instance.queue.subscribe(:ack => true, :timeout => false) do |message| - call_action_for_message(message) - end - rescue Bunny::ConnectionError, Errno::ECONNRESET, Bunny::ServerDownError - AmqpClient.instance.disconnected! - Chef::Log.warn "Connection to rabbitmq lost. attempting to reconnect" - sleep 1 - retry - end - end - alias :start :run - - def call_action_for_message(message) - amqp_payload = Chef::JSONCompat.from_json(message[:payload], :create_additions => false, :max_nesting => false) - action = amqp_payload["action"].to_sym - app_payload = amqp_payload["payload"] - assert_method_whitelisted(action) - send(action, app_payload) - end - - private - - def assert_method_whitelisted(method_name) - unless self.class.whitelisted?(method_name) - raise ArgumentError, "non-exposed method #{method_name} called via index queue" - end - end - - end - end -end diff --git a/chef/lib/chef/index_queue/indexable.rb b/chef/lib/chef/index_queue/indexable.rb deleted file mode 100644 index 73fd08bbb5..0000000000 --- a/chef/lib/chef/index_queue/indexable.rb +++ /dev/null @@ -1,109 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2009 Daniel DeLeo -# Copyright:: Copyright (c) 2010 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -require 'chef/json_compat' - -class Chef - module IndexQueue - module Indexable - - module ClassMethods - - def index_object_type(explicit_type_name=nil) - @index_object_type = explicit_type_name.to_s if explicit_type_name - @index_object_type - end - - # Resets all metadata used for indexing to nil. Used for testing - def reset_index_metadata! - @index_object_type = nil - end - - end - - def self.included(including_class) - including_class.send(:extend, ClassMethods) - end - - attr_accessor :index_id - - def index_object_type - self.class.index_object_type || Mixin::ConvertToClassName.snake_case_basename(self.class.name) - end - - def with_indexer_metadata(indexer_metadata={}) - # changing input param symbol keys to strings, as the keys in hash that goes to solr are expected to be strings [cb] - # Ruby 1.9 hates you, cb [dan] - with_metadata = {} - indexer_metadata.each_key do |key| - with_metadata[key.to_s] = indexer_metadata[key] - end - - with_metadata["type"] ||= self.index_object_type - with_metadata["id"] ||= self.index_id - with_metadata["database"] ||= Chef::Config[:couchdb_database] - with_metadata["item"] ||= self.to_hash - with_metadata["enqueued_at"] ||= Time.now.utc.to_i - - raise ArgumentError, "Type, Id, or Database missing in index operation: #{with_metadata.inspect}" if (with_metadata["id"].nil? or with_metadata["type"].nil?) - with_metadata - end - - def add_to_index(metadata={}) - Chef::Log.debug("Pushing item to index queue for addition: #{self.with_indexer_metadata(metadata)}") - object_with_metadata = with_indexer_metadata(metadata) - obj_id = object_with_metadata["id"] - obj = {:action => :add, :payload => self.with_indexer_metadata(metadata)} - - publish_object(obj_id, obj) - end - - def delete_from_index(metadata={}) - Chef::Log.debug("Pushing item to index queue for deletion: #{self.with_indexer_metadata(metadata)}") - object_with_metadata = with_indexer_metadata(metadata) - obj_id = object_with_metadata["id"] - obj = {:action => :delete, :payload => self.with_indexer_metadata(metadata)} - - publish_object(obj_id, obj) - end - - private - - # Uses the publisher to update the object's queue. If - # Chef::Config[:persistent_queue] is true, the update is wrapped - # in a transaction. - def publish_object(object_id, object) - publisher = AmqpClient.instance - begin - publisher.amqp_client.tx_select if Chef::Config[:persistent_queue] - publisher.queue_for_object(object_id) do |queue| - queue.publish(Chef::JSONCompat.to_json(object), :persistent => Chef::Config[:persistent_queue]) - end - publisher.amqp_client.tx_commit if Chef::Config[:persistent_queue] - rescue - publisher.amqp_client.tx_rollback if Chef::Config[:persistent_queue] - raise - end - - true - end - - end - end -end diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb index 1229e0db28..92a2374bce 100644 --- a/chef/lib/chef/node.rb +++ b/chef/lib/chef/node.rb @@ -28,11 +28,9 @@ require 'chef/mixin/from_file' require 'chef/mixin/deep_merge' require 'chef/dsl/include_attribute' require 'chef/environment' -require 'chef/couchdb' require 'chef/rest' require 'chef/run_list' require 'chef/node/attribute' -require 'chef/index_queue' require 'chef/mash' require 'chef/json_compat' require 'chef/search/query' @@ -44,8 +42,7 @@ class Chef def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key? - attr_accessor :recipe_list, :couchdb, :couchdb_rev, :run_state, :run_list - attr_reader :couchdb_id + attr_accessor :recipe_list, :run_state, :run_list attr_accessor :run_context @@ -54,103 +51,9 @@ class Chef include Chef::Mixin::CheckHelper include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable - - DESIGN_DOCUMENT = { - "version" => 11, - "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 - }, - "status" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "node") { - var to_emit = { "name": doc.name, "chef_environment": doc.chef_environment }; - if (doc["attributes"]["fqdn"]) { - to_emit["fqdn"] = doc["attributes"]["fqdn"]; - } else { - to_emit["fqdn"] = "Undefined"; - } - if (doc["attributes"]["ipaddress"]) { - to_emit["ipaddress"] = doc["attributes"]["ipaddress"]; - } else { - to_emit["ipaddress"] = "Undefined"; - } - if (doc["attributes"]["ohai_time"]) { - to_emit["ohai_time"] = doc["attributes"]["ohai_time"]; - } else { - to_emit["ohai_time"] = "Undefined"; - } - if (doc["attributes"]["uptime"]) { - to_emit["uptime"] = doc["attributes"]["uptime"]; - } else { - to_emit["uptime"] = "Undefined"; - } - if (doc["attributes"]["platform"]) { - to_emit["platform"] = doc["attributes"]["platform"]; - } else { - to_emit["platform"] = "Undefined"; - } - if (doc["attributes"]["platform_version"]) { - to_emit["platform_version"] = doc["attributes"]["platform_version"]; - } else { - to_emit["platform_version"] = "Undefined"; - } - if (doc["run_list"]) { - to_emit["run_list"] = doc["run_list"]; - } else { - to_emit["run_list"] = "Undefined"; - } - emit(doc.name, to_emit); - } - } - EOJS - }, - "by_run_list" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "node") { - if (doc['run_list']) { - for (var i=0; i < doc.run_list.length; i++) { - emit(doc['run_list'][i], doc.name); - } - } - } - } - EOJS - }, - "by_environment" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "node") { - var env = (doc['chef_environment'] == null ? "_default" : doc['chef_environment']); - emit(env, doc.name); - } - } - EOJS - } - }, - } # Create a new Chef::Node object. - def initialize(couchdb=nil) + def initialize @name = nil @chef_environment = '_default' @@ -158,18 +61,9 @@ class Chef @attributes = Chef::Node::Attribute.new({}, {}, {}, {}) - @couchdb_rev = nil - @couchdb_id = nil - @couchdb = couchdb || Chef::CouchDB.new - @run_state = {} end - def couchdb_id=(value) - @couchdb_id = value - @index_id = value - end - # Used by DSL def node self @@ -470,7 +364,6 @@ class Chef #Render correctly for run_list items so malformed json does not result "run_list" => run_list.run_list.map { |item| item.to_s } } - result["_rev"] = couchdb_rev if couchdb_rev result.to_json(*a) end @@ -502,17 +395,9 @@ class Chef else o["recipes"].each { |r| node.recipes << r } end - node.couchdb_rev = o["_rev"] if o.has_key?("_rev") - node.couchdb_id = o["_id"] if o.has_key?("_id") - node.index_id = node.couchdb_id node end - def self.cdb_list_by_environment(environment, inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).get_view("nodes", "by_environment", :include_docs => inflate, :startkey => environment, :endkey => environment) - inflate ? rs["rows"].collect {|r| r["doc"]} : rs["rows"].collect {|r| r["value"]} - end - def self.list_by_environment(environment, inflate=false) if inflate response = Hash.new @@ -523,14 +408,6 @@ class Chef end 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.cdb_list(inflate=false, couchdb=nil) - rs =(couchdb || Chef::CouchDB.new).list("nodes", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - def self.list(inflate=false) if inflate response = Hash.new @@ -543,19 +420,6 @@ class Chef end end - # Load a node by name from CouchDB - def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("node", name) - end - - def self.exists?(nodename, couchdb) - begin - self.cdb_load(nodename, couchdb) - rescue Chef::Exceptions::CouchDBNotFound - nil - end - end - def self.find_or_create(node_name) load(node_name) rescue Net::HTTPServerException => e @@ -576,21 +440,11 @@ class Chef Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}") end - # Remove this node from the CouchDB - def cdb_destroy - couchdb.delete("node", name, couchdb_rev) - end - # Remove this node via the REST API def destroy chef_server_rest.delete_rest("nodes/#{name}") end - # Save this node to the CouchDB - def cdb_save - @couchdb_rev = couchdb.store("node", name, self)["rev"] - end - # Save this node via the REST API def save # Try PUT. If the node doesn't yet exist, PUT will return 404, @@ -614,11 +468,6 @@ class Chef self end - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("nodes", DESIGN_DOCUMENT) - end - def to_s "node[#{name}]" end diff --git a/chef/lib/chef/openid_registration.rb b/chef/lib/chef/openid_registration.rb deleted file mode 100644 index f7bb9595d9..0000000000 --- a/chef/lib/chef/openid_registration.rb +++ /dev/null @@ -1,187 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/config' -require 'chef/mixin/params_validate' -require 'chef/couchdb' -require 'chef/index_queue' -require 'digest/sha1' -require 'chef/json_compat' - -class Chef - class OpenIDRegistration - - attr_accessor :name, :salt, :validated, :password, :couchdb_rev, :admin - - include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable - - 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 - @admin = 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, - 'admin' => @admin, - '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.admin = o["admin"] - 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 - - def self.cdb_list(*args) - list(*args) - 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 OpenIDRegistration from the CouchDB - def destroy - @couchdb.delete("openid_registration", @name, @couchdb_rev) - end - - # Save this OpenIDRegistration 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(couchdb=nil) - couchdb ||= Chef::CouchDB.new - couchdb.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 diff --git a/chef/lib/chef/role.rb b/chef/lib/chef/role.rb index c428472f1f..78bbfadb88 100644 --- a/chef/lib/chef/role.rb +++ b/chef/lib/chef/role.rb @@ -21,9 +21,7 @@ require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' -require 'chef/couchdb' require 'chef/run_list' -require 'chef/index_queue' require 'chef/mash' require 'chef/json_compat' require 'chef/search/query' @@ -33,51 +31,14 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate - include Chef::IndexQueue::Indexable - - DESIGN_DOCUMENT = { - "version" => 6, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "role") { - emit(doc.name, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "role") { - emit(doc.name, doc.name); - } - } - EOJS - } - } - } - - attr_accessor :couchdb_rev, :couchdb - attr_reader :couchdb_id # Create a new Chef::Role object. - def initialize(couchdb=nil) + def initialize @name = '' @description = '' @default_attributes = Mash.new @override_attributes = Mash.new @env_run_lists = {"_default" => Chef::RunList.new} - @couchdb_rev = nil - @couchdb_id = nil - @couchdb = couchdb || Chef::CouchDB.new - end - - def couchdb_id=(value) - @couchdb_id = value - self.index_id = value end def chef_server_rest @@ -177,7 +138,6 @@ class Chef accumulator end } - result["_rev"] = couchdb_rev if couchdb_rev result end @@ -214,20 +174,9 @@ class Chef end role.env_run_lists(env_run_list_hash) - role.couchdb_rev = o["_rev"] if o.has_key?("_rev") - role.index_id = role.couchdb_id - role.couchdb_id = o["_id"] if o.has_key?("_id") role end - # List all the Chef::Role objects in the CouchDB. If inflate is set to true, you will get - # the full list of all Roles, fully inflated. - def self.cdb_list(inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).list("roles", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - # Get the list of all roles from the API. def self.list(inflate=false) if inflate @@ -241,24 +190,11 @@ class Chef end end - # Load a role by name from CouchDB - def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("role", name) - end - # Load a role by name from the API def self.load(name) chef_server_rest.get_rest("roles/#{name}") end - def self.exists?(rolename, couchdb) - begin - self.cdb_load(rolename, couchdb) - rescue Chef::Exceptions::CouchDBNotFound - nil - end - end - def environment(env_name) chef_server_rest.get_rest("roles/#{@name}/environments/#{env_name}") end @@ -267,21 +203,11 @@ class Chef chef_server_rest.get_rest("roles/#{@name}/environments") end - # Remove this role from the CouchDB - def cdb_destroy - couchdb.delete("role", @name, couchdb_rev) - end - # Remove this role via the REST API def destroy chef_server_rest.delete_rest("roles/#{@name}") end - # Save this role to the CouchDB - def cdb_save - self.couchdb_rev = couchdb.store("role", @name, self)["rev"] - end - # Save this role via the REST API def save begin @@ -299,11 +225,6 @@ class Chef self end - # Set up our CouchDB design document - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("roles", DESIGN_DOCUMENT) - end - # As a string def to_s "role[#{@name}]" @@ -328,22 +249,5 @@ class Chef end end - # Sync all the json roles with couchdb from disk - def self.sync_from_disk_to_couchdb - Dir[File.join(Chef::Config[:role_path], "*.json")].each do |role_file| - short_name = File.basename(role_file, ".json") - Chef::Log.warn("Loading #{short_name}") - r = Chef::Role.from_disk(short_name, "json") - begin - couch_role = Chef::Role.cdb_load(short_name) - r.couchdb_rev = couch_role.couchdb_rev - Chef::Log.debug("Replacing role #{short_name} with data from #{role_file}") - rescue Chef::Exceptions::CouchDBNotFound - Chef::Log.debug("Creating role #{short_name} with data from #{role_file}") - end - r.cdb_save - end - end - end end diff --git a/chef/lib/chef/run_list.rb b/chef/lib/chef/run_list.rb index 1e4bdd255a..684c5e19fc 100644 --- a/chef/lib/chef/run_list.rb +++ b/chef/lib/chef/run_list.rb @@ -155,8 +155,6 @@ class Chef RunListExpansionFromDisk.new(environment, @run_list_items) when 'server' RunListExpansionFromAPI.new(environment, @run_list_items, opts[:rest]) - when 'couchdb' - RunListExpansionFromCouchDB.new(environment, @run_list_items, opts[:couchdb]) end end diff --git a/chef/lib/chef/run_list/run_list_expansion.rb b/chef/lib/chef/run_list/run_list_expansion.rb index 690eb3392b..7b8108a2d4 100644 --- a/chef/lib/chef/run_list/run_list_expansion.rb +++ b/chef/lib/chef/run_list/run_list_expansion.rb @@ -21,7 +21,6 @@ require 'chef/mash' require 'chef/mixin/deep_merge' require 'chef/role' -require 'chef/couchdb' require 'chef/rest' class Chef @@ -188,19 +187,5 @@ class Chef end end - # Expand a run list from couchdb. Used in chef-server-api - class RunListExpansionFromCouchDB < RunListExpansion - - def couchdb - source - end - - def fetch_role(name, included_by) - Chef::Role.cdb_load(name, couchdb) - rescue Chef::Exceptions::CouchDBNotFound - role_not_found(name, included_by) - end - - end end end diff --git a/chef/lib/chef/sandbox.rb b/chef/lib/chef/sandbox.rb deleted file mode 100644 index 4d05a1db70..0000000000 --- a/chef/lib/chef/sandbox.rb +++ /dev/null @@ -1,153 +0,0 @@ -# -# Author:: Tim Hinderliter (<tim@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'chef/log' -require 'uuidtools' - -class Chef - class Sandbox - attr_accessor :is_completed, :create_time - alias_method :is_completed?, :is_completed - attr_reader :guid - - alias :name :guid - - attr_accessor :couchdb, :couchdb_id, :couchdb_rev - - # list of checksum ids - attr_accessor :checksums - - DESIGN_DOCUMENT = { - "version" => 1, - "language" => "javascript", - "views" => { - "all" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "sandbox") { - emit(doc.guid, doc); - } - } - EOJS - }, - "all_id" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "sandbox") { - emit(doc.guid, doc.guid); - } - } - EOJS - }, - "all_incomplete" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "sandbox" && !doc.is_completed) { - emit(doc.guid, doc.guid); - } - } - EOJS - }, - "all_completed" => { - "map" => <<-EOJS - function(doc) { - if (doc.chef_type == "sandbox" && doc.is_completed) { - emit(doc.guid, doc.guid); - } - } - EOJS - }, - } - } - - # Creates a new Chef::Sandbox object. - # - # === Returns - # object<Chef::Sandbox>:: Duh. :) - def initialize(guid=nil, couchdb=nil) - @guid = guid || UUIDTools::UUID.random_create.to_s.gsub(/\-/,'').downcase - @is_completed = false - @create_time = Time.now.iso8601 - @checksums = Array.new - end - - def include?(checksum) - @checksums.include?(checksum) - end - - alias :member? :include? - - def to_json(*a) - result = { - :guid => guid, - :name => name, # same as guid, used for id_map - :checksums => checksums, - :create_time => create_time, - :is_completed => is_completed, - :json_class => self.class.name, - :chef_type => 'sandbox' - } - result["_rev"] = @couchdb_rev if @couchdb_rev - result.to_json(*a) - end - - def self.json_create(o) - sandbox = new(o['guid']) - sandbox.checksums = o['checksums'] - sandbox.create_time = o['create_time'] - sandbox.is_completed = o['is_completed'] - if o.has_key?('_rev') - sandbox.couchdb_rev = o["_rev"] - o.delete("_rev") - end - if o.has_key?("_id") - sandbox.couchdb_id = o["_id"] - #sandbox.index_id = sandbox.couchdb_id - o.delete("_id") - end - sandbox - end - - ## - # Couchdb - ## - - def self.create_design_document(couchdb=nil) - (couchdb || Chef::CouchDB.new).create_design_document("sandboxes", DESIGN_DOCUMENT) - end - - def self.cdb_list(inflate=false, couchdb=nil) - rs = (couchdb || Chef::CouchDB.new).list("sandboxes", inflate) - lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } - end - - def self.cdb_load(guid, couchdb=nil) - # Probably want to look for a view here at some point - (couchdb || Chef::CouchDB.new).load("sandbox", guid) - end - - def cdb_destroy - (couchdb || Chef::CouchDB.new).delete("sandbox", guid, @couchdb_rev) - end - - def cdb_save(couchdb=nil) - @couchdb_rev = (couchdb || Chef::CouchDB.new).store("sandbox", guid, self)["rev"] - end - - end -end diff --git a/chef/lib/chef/solr_query.rb b/chef/lib/chef/solr_query.rb deleted file mode 100644 index 3360ecc24d..0000000000 --- a/chef/lib/chef/solr_query.rb +++ /dev/null @@ -1,187 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2009-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/mixin/xml_escape' -require 'chef/log' -require 'chef/config' -require 'chef/couchdb' -require 'chef/solr_query/solr_http_request' -require 'chef/solr_query/query_transform' - -class Chef - class SolrQuery - - ID_KEY = "X_CHEF_id_CHEF_X" - DEFAULT_PARAMS = Mash.new(:start => 0, :rows => 1000, :sort => "#{ID_KEY} asc", :wt => 'json', :indent => 'off').freeze - FILTER_PARAM_MAP = {:database => 'X_CHEF_database_CHEF_X', :type => "X_CHEF_type_CHEF_X", :data_bag => 'data_bag'} - VALID_PARAMS = [:start,:rows,:sort,:q,:type] - BUILTIN_SEARCH_TYPES = ["role","node","client","environment"] - DATA_BAG_ITEM = 'data_bag_item' - - include Chef::Mixin::XMLEscape - - attr_accessor :query - attr_accessor :params - - # Create a new Query object - takes the solr_url and optional - # Chef::CouchDB object to inflate objects into. - def initialize(couchdb = nil) - @filter_query = {} - @params = {} - - if couchdb.nil? - @database = Chef::Config[:couchdb_database] - @couchdb = Chef::CouchDB.new(nil, Chef::Config[:couchdb_database]) - else - unless couchdb.respond_to?(:couchdb_database) - Chef::Log.warn("Passing the database name to Chef::Solr::Query initialization is deprecated. Please pass in the Chef::CouchDB object instead.") - @database = couchdb - @couchdb = Chef::CouchDB.new(nil, couchdb) - else - @database = couchdb.couchdb_database - @couchdb = couchdb - end - end - end - - def self.from_params(params, couchdb=nil) - query = new(couchdb) - query.params = VALID_PARAMS.inject({}) do |p, param_name| - p[param_name] = params[param_name] if params.key?(param_name) - p - end - query.update_filter_query_from_params - query.update_query_from_params - query - end - - def filter_by(filter_query_params) - filter_query_params.each do |key, value| - @filter_query[FILTER_PARAM_MAP[key]] = value - end - end - - def filter_query - @filter_query.map { |param, value| "+#{param}:#{value}" }.join(' ') - end - - def filter_by_type(type) - case type - when *BUILTIN_SEARCH_TYPES - filter_by(:type => type) - else - filter_by(:type => DATA_BAG_ITEM, :data_bag => type) - end - end - - def update_filter_query_from_params - filter_by(:database => @database) - filter_by_type(params.delete(:type)) - end - - def update_query_from_params - original_query = URI.decode(params.delete(:q) || "*:*") - @query = Chef::SolrQuery::QueryTransform.transform(original_query) - end - - def objects - if !object_ids.empty? - @bulk_objects ||= @couchdb.bulk_get(object_ids) - Chef::Log.debug { "Bulk get of objects: #{@bulk_objects.inspect}" } - @bulk_objects - else - [] - end - end - - def object_ids - @object_ids ||= results["response"]["docs"].map { |d| d[ID_KEY] } - end - - def results - @results ||= SolrHTTPRequest.select(self.to_hash) - end - - # Search Solr for objects of a given type, for a given query. - def search - { "rows" => objects, "start" => results["response"]["start"], - "total" => results["response"]["numFound"] } - end - - def to_hash - options = DEFAULT_PARAMS.merge(params) - options[:fq] = filter_query - options[:q] = @query - options - end - - START_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".freeze - START_DELETE_BY_QUERY = "<delete><query>".freeze - END_DELETE_BY_QUERY = "</query></delete>\n".freeze - COMMIT = "<commit/>\n".freeze - - def commit(opts={}) - SolrHTTPRequest.update("#{START_XML}#{COMMIT}") - end - - def delete_database(db) - query_data = xml_escape("X_CHEF_database_CHEF_X:#{db}") - xml = "#{START_XML}#{START_DELETE_BY_QUERY}#{query_data}#{END_DELETE_BY_QUERY}" - SolrHTTPRequest.update(xml) - commit - end - - def rebuild_index(db=Chef::Config[:couchdb_database]) - delete_database(db) - - results = {} - [Chef::ApiClient, Chef::Node, Chef::Role, Chef::Environment].each do |klass| - results[klass.name] = reindex_all(klass) ? "success" : "failed" - end - databags = Chef::DataBag.cdb_list(true) - Chef::Log.info("Reloading #{databags.size.to_s} #{Chef::DataBag} objects into the indexer") - databags.each { |i| i.add_to_index; i.list(true).each { |x| x.add_to_index } } - results[Chef::DataBag.name] = "success" - results - end - - def reindex_all(klass, metadata={}) - begin - items = klass.cdb_list(true) - Chef::Log.info("Reloading #{items.size.to_s} #{klass.name} objects into the indexer") - items.each { |i| i.add_to_index } - rescue Net::HTTPServerException => e - # 404s are okay, there might not be any of that kind of object... - if e.message =~ /Not Found/ - Chef::Log.warn("Could not load #{klass.name} objects from couch for re-indexing (this is ok if you don't have any of these)") - return false - else - raise e - end - rescue Exception => e - Chef::Log.fatal("Chef encountered an error while attempting to load #{klass.name} objects back into the index") - raise e - end - true - end - - - end -end diff --git a/chef/lib/chef/solr_query/lucene.treetop b/chef/lib/chef/solr_query/lucene.treetop deleted file mode 100644 index 99d3e1f709..0000000000 --- a/chef/lib/chef/solr_query/lucene.treetop +++ /dev/null @@ -1,150 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -grammar Lucene - - rule body - (expression / space)* <Body> - end - - rule expression - operation / group / field / field_range / term / string - end - - rule term - keyword valid_letter+ <Term> / !keyword !"?" valid_letter <Term> - end - - rule field - field_name ":" (term/string/group) <Field> - end - - rule field_range - field_name ":" "[" range_value " TO " range_value "]" <InclFieldRange> - / - field_name ":" "{" range_value " TO " range_value "}" <ExclFieldRange> - end - - rule field_name - !keyword valid_letter+ <FieldName> - end - - rule range_value - valid_letter+ <RangeValue> / "*" <RangeValue> - end - - rule group - space? '(' body ')' space? <Group> - end - - rule operation - binary_op / unary_op / fuzzy_op / boost_op - end - - rule unary_op - not_op / required_op / prohibited_op - end - - rule binary_op - (group / field / field_range / term) space? boolean_operator space+ body <BinaryOp> - end - - rule boolean_operator - and_operator / or_operator - end - - rule and_operator - 'AND' <AndOperator> / '&&' <AndOperator> - end - - rule or_operator - 'OR' <OrOperator> / '||' <OrOperator> - end - - rule not_op - not_operator space (group / field / field_range / term / string) <UnaryOp> - / - bang_operator space? (group / field / field_range / term / string) <UnaryOp> - end - - rule not_operator - 'NOT' <NotOperator> - end - - rule bang_operator - '!' <NotOperator> - end - - rule required_op - !valid_letter required_operator (term/string) <UnaryOp> - / - required_operator (term/string) <UnaryOp> - end - - rule required_operator - '+' <RequiredOperator> - end - - rule prohibited_op - !valid_letter prohibited_operator (field/field_range/term/string) <UnaryOp> - end - - rule prohibited_operator - '-' <ProhibitedOperator> - end - - rule boost_op - (term/string) '^' fuzzy_param <BoostOp> - end - - rule fuzzy_op - (term/string) '~' fuzzy_param? (space / !valid_letter) <FuzzyOp> - end - - rule fuzzy_param - [0-9] '.'? [0-9] <FuzzyParam> / [0-9]+ <FuzzyParam> - end - - rule string - '"' term (space term)* '"' <Phrase> - end - - rule keyword - 'AND' / 'OR' / 'NOT' - end - - rule valid_letter - start_letter+ ([a-zA-Z0-9@*?_.-] / '\\' special_char)* - end - - rule start_letter - [a-zA-Z0-9@._*] / '\\' special_char - end - - rule end_letter - [a-zA-Z0-9*?_.] / '\\' special_char - end - - rule special_char - [-+&|!(){}\[\]^"~*?:\\] - end - - rule space - [\s]+ - end -end diff --git a/chef/lib/chef/solr_query/lucene_nodes.rb b/chef/lib/chef/solr_query/lucene_nodes.rb deleted file mode 100644 index c2d7777365..0000000000 --- a/chef/lib/chef/solr_query/lucene_nodes.rb +++ /dev/null @@ -1,285 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'treetop' - -module Lucene - SEP = "__=__" - - class Term < Treetop::Runtime::SyntaxNode - def to_array - "T:#{self.text_value}" - end - - def transform - self.text_value - end - end - - class Field < Treetop::Runtime::SyntaxNode - def to_array - field = self.elements[0].text_value - term = self.elements[1].to_array - "(F:#{field} #{term})" - end - - def transform - field = self.elements[0].text_value - term = self.elements[1] - if term.is_a? Phrase - str = term.transform - # remove quotes - str = str[1 ... (str.length - 1)] - "content:\"#{field}#{SEP}#{str}\"" - else - "content:#{field}#{SEP}#{term.transform}" - end - end - end - - class FieldRange < Treetop::Runtime::SyntaxNode - - def to_array - field = self.elements[0].text_value - range_start = self.elements[1].to_array - range_end = self.elements[2].to_array - "(FR:#{field} #{left}#{range_start}#{right} #{left}#{range_end}#{right})" - end - - def transform - field = self.elements[0].text_value - range_start = self.elements[1].transform - range_end = self.elements[2].transform - # FIXME: handle special cases for missing start/end - if ("*" == range_start && "*" == range_end) - "content:#{field}#{SEP}*" - elsif "*" == range_end - "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}\\ufff0#{right}" - elsif "*" == range_start - "content:#{left}#{field}#{SEP} TO #{field}#{SEP}#{range_end}#{right}" - else - "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}#{range_end}#{right}" - end - end - - end - - class InclFieldRange < FieldRange - def left - "[" - end - def right - "]" - end - end - - class ExclFieldRange < FieldRange - def left - "{" - end - def right - "}" - end - end - - class RangeValue < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - to_array - end - end - - class FieldName < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - to_array - end - end - - - class Body < Treetop::Runtime::SyntaxNode - def to_array - self.elements.map { |x| x.to_array }.join(" ") - end - - def transform - self.elements.map { |x| x.transform }.join(" ") - end - end - - class Group < Treetop::Runtime::SyntaxNode - def to_array - "(" + self.elements[0].to_array + ")" - end - - def transform - "(" + self.elements[0].transform + ")" - end - end - - class BinaryOp < Treetop::Runtime::SyntaxNode - def to_array - op = self.elements[1].to_array - a = self.elements[0].to_array - b = self.elements[2].to_array - "(#{op} #{a} #{b})" - end - - def transform - op = self.elements[1].transform - a = self.elements[0].transform - b = self.elements[2].transform - "#{a} #{op} #{b}" - end - end - - class AndOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:AND" - end - - def transform - "AND" - end - end - - class OrOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:OR" - end - - def transform - "OR" - end - end - - class FuzzyOp < Treetop::Runtime::SyntaxNode - def to_array - a = self.elements[0].to_array - param = self.elements[1] - if param - "(OP:~ #{a} #{param.to_array})" - else - "(OP:~ #{a})" - end - end - - def transform - a = self.elements[0].transform - param = self.elements[1] - if param - "#{a}~#{param.transform}" - else - "#{a}~" - end - end - end - - class BoostOp < Treetop::Runtime::SyntaxNode - def to_array - a = self.elements[0].to_array - param = self.elements[1] - "(OP:^ #{a} #{param.to_array})" - end - - def transform - a = self.elements[0].transform - param = self.elements[1] - "#{a}^#{param.transform}" - end - end - - class FuzzyParam < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - self.text_value - end - end - - class UnaryOp < Treetop::Runtime::SyntaxNode - def to_array - op = self.elements[0].to_array - a = self.elements[1].to_array - "(#{op} #{a})" - end - - def transform - op = self.elements[0].transform - a = self.elements[1].transform - spc = case op - when "+", "-" - "" - else - " " - end - "#{op}#{spc}#{a}" - end - - end - - class NotOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:NOT" - end - - def transform - "NOT" - end - - end - - class RequiredOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:+" - end - - def transform - "+" - end - - end - - class ProhibitedOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:-" - end - - def transform - "-" - end - end - - class Phrase < Treetop::Runtime::SyntaxNode - def to_array - "STR:#{self.text_value}" - end - - def transform - "#{self.text_value}" - end - end -end diff --git a/chef/lib/chef/solr_query/query_transform.rb b/chef/lib/chef/solr_query/query_transform.rb deleted file mode 100644 index 529f9de482..0000000000 --- a/chef/lib/chef/solr_query/query_transform.rb +++ /dev/null @@ -1,65 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -require 'treetop' -require 'chef/solr_query/lucene_nodes' - -class Chef - class Exceptions - class QueryParseError < StandardError - end - end -end - -class Chef - class SolrQuery - class QueryTransform - @@base_path = File.expand_path(File.dirname(__FILE__)) - Treetop.load(File.join(@@base_path, 'lucene.treetop')) - @@parser = LuceneParser.new - - def self.parse(data) - tree = @@parser.parse(data) - msg = "Parse error at offset: #{@@parser.index}\n" - msg += "Reason: #{@@parser.failure_reason}" - raise Chef::Exceptions::QueryParseError, msg if tree.nil? - self.clean_tree(tree) - tree.to_array - end - - def self.transform(data) - return "*:*" if data == "*:*" - tree = @@parser.parse(data) - msg = "Parse error at offset: #{@@parser.index}\n" - msg += "Reason: #{@@parser.failure_reason}" - raise Chef::Exceptions::QueryParseError, msg if tree.nil? - self.clean_tree(tree) - tree.transform - end - - private - - def self.clean_tree(root_node) - return if root_node.elements.nil? - root_node.elements.delete_if do |node| - node.class.name == "Treetop::Runtime::SyntaxNode" - end - root_node.elements.each { |node| self.clean_tree(node) } - end - end - end -end diff --git a/chef/lib/chef/solr_query/solr_http_request.rb b/chef/lib/chef/solr_query/solr_http_request.rb deleted file mode 100644 index e7ce1d5675..0000000000 --- a/chef/lib/chef/solr_query/solr_http_request.rb +++ /dev/null @@ -1,132 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2009-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'net/http' -require 'uri' -require 'chef/json_compat' -require 'chef/config' - -class Chef - class SolrQuery - class SolrHTTPRequest - CLASS_FOR_METHOD = {:GET => Net::HTTP::Get, :POST => Net::HTTP::Post} - - TEXT_XML = {"Content-Type" => "text/xml"} - - def self.solr_url=(solr_url) - @solr_url = solr_url - @http_client = nil - @url_prefix = nil - end - - def self.solr_url - @solr_url || Chef::Config[:solr_url] - end - - def self.http_client - @http_client ||= begin - uri = URI.parse(solr_url) - http_client = Net::HTTP.new(uri.host, uri.port) - http_client.use_ssl = true if uri.port == 443 - http_client - end - end - - def self.url_prefix - @url_prefix ||= begin - uri = URI.parse(solr_url) - if uri.path == "" - "/solr" - else - uri.path.gsub(%r{/$}, '') - end - end - end - - def self.select(params={}) - url = "#{url_prefix}/select?#{url_join(params)}" - Chef::Log.debug("Sending #{url} to Solr") - request = new(:GET, url) - json_response = request.run("Search Query to Solr '#{solr_url}#{url}'") - Chef::JSONCompat.from_json(json_response) - end - - def self.update(doc) - url = "#{url_prefix}/update" - Chef::Log.debug("POSTing document to SOLR:\n#{doc}") - request = new(:POST, url, TEXT_XML) { |req| req.body = doc.to_s } - request.run("POST to Solr '#{solr_url}#{url}', data: #{doc}") - end - - def self.url_join(params_hash={}) - params = params_hash.inject("") do |param_str, params| - param_str << "#{params[0]}=#{escape(params[1])}&" - end - params.chop! # trailing & - params - end - - def self.escape(s) - s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { - '%'+$1.unpack('H2'*$1.size).join('%').upcase - }.tr(' ', '+') - end - - def initialize(method, url, headers=nil) - args = headers ? [url, headers] : url - @request = CLASS_FOR_METHOD[method].new(*args) - yield @request if block_given? - end - - def http_client - self.class.http_client - end - - def solr_url - self.class.solr_url - end - - def run(description="HTTP Request to Solr") - response = http_client.request(@request) - request_failed!(response, description) unless response.kind_of?(Net::HTTPSuccess) - response.body - rescue NoMethodError => e - # http://redmine.ruby-lang.org/issues/show/2708 - # http://redmine.ruby-lang.org/issues/show/2758 - if e.to_s =~ /#{Regexp.escape(%q|undefined method 'closed?' for nil:NilClass|)}/ - Chef::Log.fatal("#{description} failed. Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact #{solr_url}") - Chef::Log.debug("Rescued error in http connect, treating it as Errno::ECONNREFUSED to hide bug in net/http") - Chef::Log.debug(e.backtrace.join("\n")) - raise Chef::Exceptions::SolrConnectionError, "Errno::ECONNREFUSED: Connection refused attempting to contact #{solr_url}" - else - raise - end - end - - def request_failed!(response, description='HTTP call') - Chef::Log.fatal("#{description} failed (#{response.class} #{response.code} #{response.message})") - response.error! - rescue Timeout::Error, Errno::EINVAL, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT => e - Chef::Log.debug(e.backtrace.join("\n")) - raise Chef::Exceptions::SolrConnectionError, "#{e.class.name}: #{e.to_s}" - end - - end - end -end diff --git a/chef/lib/chef/webui_user.rb b/chef/lib/chef/webui_user.rb deleted file mode 100644 index e0dbde4a61..0000000000 --- a/chef/lib/chef/webui_user.rb +++ /dev/null @@ -1,156 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Nuo Yan (<nuo@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/config' -require 'chef/mixin/params_validate' -require 'chef/index_queue' -require 'digest/sha1' -require 'chef/json_compat' - - -class Chef - class WebUIUser - - attr_accessor :name, :validated, :admin, :openid, :couchdb - attr_reader :password, :salt, :couchdb_id, :couchdb_rev - - include Chef::Mixin::ParamsValidate - - # Create a new Chef::WebUIUser object. - def initialize(opts={}) - @name, @salt, @password = opts['name'], opts['salt'], opts['password'] - @openid = opts['openid'] - @admin = false - end - - def name=(n) - @name = n.gsub(/\./, '_') - end - - def admin? - admin - end - - # Set the password for this object. - def set_password(password, confirm_password=password) - raise ArgumentError, "Passwords do not match" unless password == confirm_password - raise ArgumentError, "Password cannot be blank" if (password.nil? || password.length==0) - raise ArgumentError, "Password must be a minimum of 6 characters" if password.length < 6 - generate_salt - @password = encrypt_password(password) - end - - def set_openid(given_openid) - @openid = given_openid - end - - def verify_password(given_password) - encrypt_password(given_password) == @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, - 'openid' => openid, - 'admin' => admin, - 'chef_type' => 'webui_user', - } - result["_id"] = @couchdb_id if @couchdb_id - result["_rev"] = @couchdb_rev if @couchdb_rev - result.to_json(*a) - end - - # Create a Chef::WebUIUser from JSON - def self.json_create(o) - me = new(o) - me.admin = o["admin"] - me - end - - def chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) - end - - def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) - end - - def self.list(inflate=false) - if inflate - response = Hash.new - Chef::Search::Query.new.search(:user) do |n| - response[n.name] = n unless n.nil? - end - response - else - chef_server_rest.get_rest("users") - end - end - - # Load a User by name - def self.load(name) - chef_server_rest.get_rest("users/#{name}") - end - - # Remove this WebUIUser via the REST API - def destroy - chef_server_rest.delete_rest("users/#{@name}") - end - - # Save this WebUIUser via the REST API - def save - begin - chef_server_rest.put_rest("users/#{@name}", self) - rescue Net::HTTPServerException => e - if e.response.code == "404" - chef_server_rest.post_rest("users", self) - else - raise e - end - end - self - end - - # Create the WebUIUser via the REST API - def create - chef_server_rest.post_rest("users", self) - self - 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(password) - Digest::SHA1.hexdigest("--#{salt}--#{password}--") - end - - end -end diff --git a/chef/spec/unit/api_client_spec.rb b/chef/spec/unit/api_client_spec.rb index b9d9cecc01..e01243152e 100644 --- a/chef/spec/unit/api_client_spec.rb +++ b/chef/spec/unit/api_client_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -57,8 +57,8 @@ describe Chef::ApiClient do end it "should return the current admin value" do - @client.admin true - @client.admin.should == true + @client.admin true + @client.admin.should == true end it "should default to false" do @@ -82,7 +82,7 @@ describe Chef::ApiClient do it "should throw an ArgumentError if you feed it something lame" do lambda { @client.public_key Hash.new }.should raise_error(ArgumentError) - end + end end describe "private_key" do @@ -97,27 +97,6 @@ describe Chef::ApiClient do it "should throw an ArgumentError if you feed it something lame" do lambda { @client.private_key Hash.new }.should raise_error(ArgumentError) - end - end - - describe "create_keys" do - before(:each) do - Chef::Certificate.stub!(:gen_keypair).and_return(["cert", "key"]) - end - - it "should create a certificate based on the client name" do - Chef::Certificate.should_receive(:gen_keypair).with(@client.name) - @client.create_keys - end - - it "should set the private key" do - @client.create_keys - @client.private_key.should == "key" - end - - it "should set the public key" do - @client.create_keys - @client.public_key.should == "cert" end end @@ -136,7 +115,7 @@ describe Chef::ApiClient do %w{ name public_key - }.each do |t| + }.each do |t| it "should include '#{t}'" do @serial.should =~ /"#{t}":"#{@client.send(t.to_sym)}"/ end @@ -168,7 +147,7 @@ describe Chef::ApiClient do name public_key admin - }.each do |t| + }.each do |t| it "should match '#{t}'" do @deserial.send(t.to_sym).should == @client.send(t.to_sym) end diff --git a/chef/spec/unit/certificate_spec.rb b/chef/spec/unit/certificate_spec.rb deleted file mode 100644 index 4f0b07519f..0000000000 --- a/chef/spec/unit/certificate_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2009 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -require 'chef/certificate' -require 'ostruct' -require 'tempfile' - -class FakeFile - attr_accessor :data - - def write(arg) - @data = arg - end -end - -describe Chef::Certificate do - describe "generate_signing_ca" do - before(:each) do - Chef::Config[:signing_ca_user] = nil - Chef::Config[:signing_ca_group] = nil - FileUtils.stub!(:mkdir_p).and_return(true) - FileUtils.stub!(:chown).and_return(true) - File.stub!(:open).and_return(true) - File.stub!(:exists?).and_return(false) - @ca_cert = FakeFile.new - @ca_key = FakeFile.new - end - - it "should generate a ca certificate" do - File.should_receive(:open).with(Chef::Config[:signing_ca_cert], "w").and_yield(@ca_cert) - Chef::Certificate.generate_signing_ca - @ca_cert.data.should =~ /BEGIN CERTIFICATE/ - end - - it "should generate an RSA private key" do - File.should_receive(:open).with(Chef::Config[:signing_ca_key], File::WRONLY|File::EXCL|File::CREAT, 0600).and_yield(@ca_key) - FileUtils.should_not_receive(:chown) - Chef::Certificate.generate_signing_ca - @ca_key.data.should =~ /BEGIN RSA PRIVATE KEY/ - end - - it "should generate an RSA private key with user and group" do - Chef::Config[:signing_ca_user] = "funky" - Chef::Config[:signing_ca_group] = "fresh" - File.should_receive(:open).with(Chef::Config[:signing_ca_key], File::WRONLY|File::EXCL|File::CREAT, 0600).and_yield(@ca_key) - FileUtils.should_receive(:chown).with(Chef::Config[:signing_ca_user], Chef::Config[:signing_ca_group], Chef::Config[:signing_ca_key]) - Chef::Certificate.generate_signing_ca - @ca_key.data.should =~ /BEGIN RSA PRIVATE KEY/ - end - end - - describe "generate_keypair" do - it "should return a client certificate" do - public_key, private_key = Chef::Certificate.gen_keypair("oasis") - public_key.to_s.should =~ /(BEGIN RSA PUBLIC KEY|BEGIN PUBLIC KEY)/ - private_key.to_s.should =~ /BEGIN RSA PRIVATE KEY/ - end - end -end diff --git a/chef/spec/unit/checksum_spec.rb b/chef/spec/unit/checksum_spec.rb deleted file mode 100644 index aad559cf3d..0000000000 --- a/chef/spec/unit/checksum_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'chef/checksum' - -describe Chef::Checksum do - - before do - Chef::Log.logger = Logger.new(StringIO.new) - - @now = Time.now - - Time.stub!(:now).and_return(@now) - - @checksum_of_the_file = "3fafecfb15585ede6b840158cbc2f399" - @checksum = Chef::Checksum.new(@checksum_of_the_file) - end - - it "has no original committed file location" do - @checksum.original_committed_file_location.should be_nil - end - - it "has the MD5 checksum of the file it represents" do - @checksum.checksum.should == @checksum_of_the_file - end - - it "stores the time it was created" do - @checksum.create_time.should == @now.iso8601 - end - - it "commits a sandbox file from a given location to the checksum repo location" do - @checksum.storage.should_receive(:commit).with("/tmp/arbitrary_file_location") - @checksum.should_receive(:cdb_save) - @checksum.commit_sandbox_file("/tmp/arbitrary_file_location") - @checksum.original_committed_file_location.should == "/tmp/arbitrary_file_location" - end - - it "reverts committing a sandbox file" do - @checksum.storage.should_receive(:commit).with("/tmp/arbitrary_file_location") - @checksum.should_receive(:cdb_save) - @checksum.commit_sandbox_file("/tmp/arbitrary_file_location") - @checksum.original_committed_file_location.should == "/tmp/arbitrary_file_location" - - @checksum.storage.should_receive(:revert).with("/tmp/arbitrary_file_location") - @checksum.should_receive(:cdb_destroy) - @checksum.revert_sandbox_file_commit - end - - it "raises an error when trying to revert a checksum that was not previously committed" do - lambda {@checksum.revert_sandbox_file_commit}.should raise_error(Chef::Exceptions::IllegalChecksumRevert) - end - - it "deletes the file and its document from couchdb" do - @checksum.should_receive(:cdb_destroy) - @checksum.storage.should_receive(:purge) - @checksum.purge - end - - describe "when converted to json" do - before do - @checksum_as_json = @checksum.to_json - @checksum_as_hash_from_json = Chef::JSONCompat.from_json(@checksum_as_json, :create_additions => false) - end - - it "contains the file's MD5 checksum" do - @checksum_as_hash_from_json["checksum"].should == @checksum_of_the_file - end - - it "contains the creation time" do - @checksum_as_hash_from_json["create_time"].should == @now.iso8601 - end - - it "uses the file's MD5 checksum for its 'name' property" do - @checksum_as_hash_from_json["name"].should == @checksum_of_the_file - end - end - -end diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb index e8a75b7009..9d0c88dad1 100644 --- a/chef/spec/unit/client_spec.rb +++ b/chef/spec/unit/client_spec.rb @@ -41,7 +41,7 @@ shared_examples_for Chef::Client do ohai_data.stub!(:data).and_return(ohai_data) Ohai::System.stub!(:new).and_return(ohai_data) - @node = Chef::Node.new(@hostname) + @node = Chef::Node.new @node.name(@fqdn) @node.chef_environment("_default") @@ -90,7 +90,7 @@ shared_examples_for Chef::Client do Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).exactly(1).and_return(mock_chef_rest_for_client) mock_chef_rest_for_client.should_receive(:register).with(@fqdn, Chef::Config[:client_key]).exactly(1).and_return(true) # Client.register will then turn around create another - + # Chef::REST object, this time with the client key it got from the # previous step. Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node) @@ -139,7 +139,6 @@ shared_examples_for Chef::Client do res.replace(string) end pipe_sim.should_receive(:gets).and_return(res) - Chef::CouchDB.should_receive(:new).and_return(nil) IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim]) IO.should_receive(:select).and_return(true) end @@ -151,7 +150,7 @@ shared_examples_for Chef::Client do block.call end end - + # This is what we're testing. @client.run @@ -160,7 +159,7 @@ shared_examples_for Chef::Client do @node.automatic_attrs[:platform_version].should == "example-platform-1.0" end end - + describe "when notifying other objects of the status of the chef run" do before do Chef::Client.clear_notifications @@ -235,7 +234,7 @@ shared_examples_for Chef::Client do describe "when a run list override is provided" do before do - @node = Chef::Node.new(@hostname) + @node = Chef::Node.new @node.name(@fqdn) @node.chef_environment("_default") @node.automatic_attrs[:platform] = "example-platform" @@ -265,7 +264,7 @@ shared_examples_for Chef::Client do @node.should_receive(:save).and_return(nil) @client.build_node - + @node[:roles].should_not be_nil @node[:roles].should eql(['test_role']) @node[:recipes].should eql(['cookbook1']) diff --git a/chef/spec/unit/cookbook_version_spec.rb b/chef/spec/unit/cookbook_version_spec.rb index 90f411d8b2..85e1db1fae 100644 --- a/chef/spec/unit/cookbook_version_spec.rb +++ b/chef/spec/unit/cookbook_version_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -17,56 +17,10 @@ require 'spec_helper' -describe Chef::MinimalCookbookVersion do - describe "when first created" do - before do - @params = { "id"=>"1a806f1c-b409-4d8e-abab-fa414ff5b96d", - "key"=>"activemq", - "value"=>{"version"=>"0.3.3", "deps"=>{"java"=>">= 0.0.0", "runit"=>">= 0.0.0"}}} - @minimal_cookbook_version = Chef::MinimalCookbookVersion.new(@params) - end - - it "has a name" do - @minimal_cookbook_version.name.should == 'activemq' - end - - it "has a version" do - @minimal_cookbook_version.version.should == '0.3.3' - end - - it "has a list of dependencies" do - @minimal_cookbook_version.deps.should == {"java" => ">= 0.0.0", "runit" => ">= 0.0.0"} - end - - it "has cookbook metadata" do - metadata = @minimal_cookbook_version.metadata - - metadata.name.should == 'activemq' - metadata.dependencies['java'].should == '>= 0.0.0' - metadata.dependencies['runit'].should == '>= 0.0.0' - end - end - - describe "when created from cookbooks with old style version contraints" do - before do - @params = { "id"=>"1a806f1c-b409-4d8e-abab-fa414ff5b96d", - "key"=>"activemq", - "value"=>{"version"=>"0.3.3", "deps"=>{"apt" => ">> 1.0.0"}}} - @minimal_cookbook_version = Chef::MinimalCookbookVersion.new(@params) - end - - it "translates the version constraints" do - metadata = @minimal_cookbook_version.metadata - metadata.dependencies['apt'].should == '> 1.0.0' - end - end -end - describe Chef::CookbookVersion do describe "when first created" do before do - @couchdb_driver = Chef::CouchDB.new - @cookbook_version = Chef::CookbookVersion.new("tatft", @couchdb_driver) + @cookbook_version = Chef::CookbookVersion.new("tatft") end it "has a name" do @@ -114,14 +68,6 @@ describe Chef::CookbookVersion do @cookbook_version.should be_frozen_version end - it "has no couchdb id" do - @cookbook_version.couchdb_id.should be_nil - end - - it "has the couchdb driver it was given on create" do - @cookbook_version.couchdb.should equal(@couchdb_driver) - end - it "is \"ready\"" do # WTF is this? what are the valid states? and why aren't they set with encapsulating methods? # [Dan 15-Jul-2010] @@ -133,19 +79,19 @@ describe Chef::CookbookVersion do end it "creates a manifest hash of its contents" do - expected = {"recipes"=>[], - "definitions"=>[], - "libraries"=>[], - "attributes"=>[], - "files"=>[], - "templates"=>[], - "resources"=>[], - "providers"=>[], - "root_files"=>[], - "cookbook_name"=>"tatft", + expected = {"recipes"=>[], + "definitions"=>[], + "libraries"=>[], + "attributes"=>[], + "files"=>[], + "templates"=>[], + "resources"=>[], + "providers"=>[], + "root_files"=>[], + "cookbook_name"=>"tatft", "metadata"=>Chef::Cookbook::Metadata.new, - "version"=>"0.0.0", - "name"=>"tatft-0.0.0"} + "version"=>"0.0.0", + "name"=>"tatft-0.0.0"} @cookbook_version.manifest.should == expected end end @@ -280,7 +226,7 @@ describe Chef::CookbookVersion do useful_explanation = Regexp.new(Regexp.escape("Cookbook 'tatft' (0.0.0) does not contain")) @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) end - + it "lists suggested places to look" do useful_explanation = Regexp.new(Regexp.escape("files/default/no-such-thing.txt")) @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation) @@ -325,7 +271,7 @@ describe Chef::CookbookVersion do b.version = "1.2.0" a.should == b end - + it "should not allow you to sort cookbooks with different names" do apt = Chef::CookbookVersion.new "apt" @@ -358,54 +304,4 @@ describe Chef::CookbookVersion do end - describe "when deleting in the database" do - before do - @couchdb_driver = Chef::CouchDB.new - @cookbook_version = Chef::CookbookVersion.new("tatft", @couchdb_driver) - @cookbook_version.version = "1.2.3" - @couchdb_rev = "_123456789" - @cookbook_version.couchdb_rev = @couchdb_rev - end - - it "deletes its document from couchdb" do - @couchdb_driver.should_receive(:delete).with("cookbook_version", "tatft-1.2.3", @couchdb_rev) - @cookbook_version.cdb_destroy - end - - it "deletes associated checksum objects when purged" do - checksums = {"12345" => "/tmp/foo", "23456" => "/tmp/bar", "34567" => "/tmp/baz"} - @cookbook_version.stub!(:checksums).and_return(checksums) - - chksum_docs = checksums.map do |md5, path| - cksum_doc = mock("Chef::Checksum for #{md5} at #{path}") - Chef::Checksum.should_receive(:cdb_load).with(md5, @couchdb_driver).and_return(cksum_doc) - cksum_doc.should_receive(:purge) - cksum_doc - end - - @cookbook_version.should_receive(:cdb_destroy) - @cookbook_version.purge - end - - it "successfully purges when associated checksum objects are missing" do - checksums = {"12345" => "/tmp/foo", "23456" => "/tmp/bar", "34567" => "/tmp/baz"} - - chksum_docs = checksums.map do |md5, path| - cksum_doc = mock("Chef::Checksum for #{md5} at #{path}") - Chef::Checksum.should_receive(:cdb_load).with(md5, @couchdb_driver).and_return(cksum_doc) - cksum_doc.should_receive(:purge) - cksum_doc - end - - missing_checksum = {"99999" => "/tmp/qux"} - Chef::Checksum.should_receive(:cdb_load).with("99999", @couchdb_driver).and_raise(Chef::Exceptions::CouchDBNotFound) - - @cookbook_version.stub!(:checksums).and_return(checksums.merge(missing_checksum)) - - @cookbook_version.should_receive(:cdb_destroy) - lambda {@cookbook_version.purge}.should_not raise_error - end - - end - end diff --git a/chef/spec/unit/couchdb_spec.rb b/chef/spec/unit/couchdb_spec.rb deleted file mode 100644 index 480dd61980..0000000000 --- a/chef/spec/unit/couchdb_spec.rb +++ /dev/null @@ -1,274 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -describe Chef::CouchDB do - before(:each) do - Chef::Config[:couchdb_database] = "chef" - @rest = mock("Chef::REST") - @rest.stub!(:run_request).and_return({"couchdb" => "Welcome", "version" =>"0.9.0"}) - @rest.stub!(:url).and_return("http://localhost:5984") - Chef::REST.stub!(:new).and_return(@rest) - @couchdb = Chef::CouchDB.new - end - - describe "new" do - it "should create a new Chef::REST object from the default url" do - old_url = Chef::Config[:couchdb_url] - Chef::Config[:couchdb_url] = "http://monkey" - Chef::REST.should_receive(:new).with("http://monkey", nil, nil) - Chef::CouchDB.new - Chef::Config[:couchdb_url] = old_url - end - - it "should create a new Chef::REST object from a provided url" do - Chef::REST.should_receive(:new).with("http://monkeypants", nil, nil) - Chef::CouchDB.new("http://monkeypants") - end - end - - describe "create_db" do - before(:each) do - @couchdb.stub!(:create_design_document).and_return(true) - end - - it "should get a list of current databases" do - @rest.should_receive(:get_rest).and_return(["chef"]) - @couchdb.create_db - end - - it "should create the chef database if it does not exist" do - @rest.stub!(:get_rest).and_return([]) - @rest.should_receive(:put_rest).with("chef", {}).and_return(true) - @couchdb.create_db - end - - it "should not create the chef database if it does exist" do - @rest.stub!(:get_rest).and_return(["chef"]) - @rest.should_not_receive(:put_rest) - @couchdb.create_db - end - - it "should return 'chef'" do - @rest.should_receive(:get_rest).with("_all_dbs").and_return(%w{chef}) - @couchdb.create_db.should eql("chef") - end - end - - describe "create_design_document" do - before(:each) do - @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 - }, - } - } - @rest.stub!(:get_rest).and_return(@mock_design) - @rest.stub!(:put_rest).and_return(true) - @couchdb.stub!(:create_db).and_return(true) - end - - def do_create_design_document - @couchdb.create_design_document("bob", @mock_data) - end - - it "should fetch the existing design document" do - @rest.should_receive(:get_rest).with("chef/_design/bob") - 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 - @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 - @rest.should_not_receive(:put_rest) - do_create_design_document - end - end - - describe "store" do - before(:each) do - @mock_results = { - "rows" => [ - "id" => 'a0934635-e111-45d9-8223-cb58e1c9434c' - ] - } - @couchdb.stub!(:get_view).with("id_map", "name_to_id", :key => [ "node", "bob" ]).and_return(@mock_results) - end - - it "should put the object into couchdb with a pre-existing GUID" do - item_to_store = {} - item_to_store.should_receive(:add_to_index) - @rest.should_receive(:put_rest).with("chef/#{@mock_results["rows"][0]["id"]}", item_to_store).and_return(true) - @couchdb.store("node", "bob", item_to_store) - end - - it "should put the object into couchdb with a new GUID" do - @mock_results = { "rows" => [] } - item_to_store = {} - item_to_store.should_receive(:add_to_index).with(:database => "chef", :id => "aaaaaaaa-xxxx-xxxx-xxxx-xxxxxxxxxxx", :type => "node") - @couchdb.stub!(:get_view).with("id_map", "name_to_id", :key => [ "node", "bob" ]).and_return(@mock_results) - UUIDTools::UUID.stub!(:random_create).and_return("aaaaaaaa-xxxx-xxxx-xxxx-xxxxxxxxxxx") - @rest.should_receive(:put_rest).with("chef/aaaaaaaa-xxxx-xxxx-xxxx-xxxxxxxxxxx", item_to_store).and_return(true) - @couchdb.store("node", "bob", item_to_store) - end - - end - - describe "when fetching the database status" do - it "gets couchdb's version string'" do - @rest.should_receive(:get_rest).with('/').and_return({"couchdb" => "Welcome","version" => "1.0.1"}) - @couchdb.server_stats.should == {"couchdb" => "Welcome","version" => "1.0.1"} - end - - it "gets database stats" do - db_stats = {"db_name" => "opscode_account","doc_count" => 206,"doc_del_count" => 1,"update_seq" => 208,"purge_seq" => 0, - "compact_running" => false,"disk_size" => 122969,"instance_start_time" => "1298070021394804","disk_format_version" => 5,"committed_update_seq" => 208} - @rest.should_receive(:get_rest).with('/chef').and_return(db_stats) - @couchdb.db_stats.should == db_stats - end - - end - - describe "load" do - before(:each) do - @mock_node = Chef::Node.new() - @mock_node.name("bob") - @couchdb.stub!(:find_by_name).with("node", "bob").and_return(@mock_node) - end - - it "should load the object from couchdb" do - @couchdb.load("node", "bob").should eql(@mock_node) - end - end - - describe "delete" do - before(:each) do - @mock_current = { - "version" => 1, - "_rev" => 1 - } - @rest.stub!(:get_rest).and_return(@mock_current) - @rest.stub!(:delete_rest).and_return(true) - @node = Chef::Node.new() - @node.name("bob") - @node.couchdb_rev = 15 - @couchdb.stub!(:find_by_name).with("node", "bob", true).and_return([ @node, "ax" ]) - end - - def do_delete(rev=nil) - @couchdb.delete("node", "bob", rev) - end - - it "should remove the object from couchdb with a specific revision" do - @node.should_receive(:delete_from_index) - @rest.should_receive(:delete_rest).with("chef/ax?rev=1") - do_delete(1) - end - - it "should remove the object from couchdb based on the couchdb_rev of the current obj" do - @node.should_receive(:delete_from_index) - @rest.should_receive(:delete_rest).with("chef/ax?rev=15") - do_delete - end - end - - describe "list" do - before(:each) do - Chef::Config.stub!(:[]).with(:couchdb_database).and_return("chef") - @mock_response = {"rows" => []} - end - - describe "on couchdb 0.9+" do - before do - Chef::Config.stub!(:[]).with(:couchdb_version).and_return(0.9) - end - - it "should get the view for all objects if inflate is true" do - @rest.should_receive(:get_rest).with("chef/_design/node/_view/all").and_return(@mock_response) - @couchdb.list("node", true) - end - - it "should get the view for just the object id's if inflate is false" do - @rest.should_receive(:get_rest).with("chef/_design/node/_view/all_id").and_return(@mock_response) - @couchdb.list("node", false) - end - end - end - - describe "has_key?" do - it "should return true if the object exists" do - @couchdb.stub!(:find_by_name).with("node", "bob").and_return(true) - @couchdb.has_key?("node", "bob").should eql(true) - end - - it "should return false if the object does not exist" do - @couchdb.stub!(:find_by_name).and_raise(Chef::Exceptions::CouchDBNotFound) - @couchdb.has_key?("node", "bob").should eql(false) - end - end - - describe "get_view" do - it "should construct a call to the view for the proper design document" do - @rest.should_receive(:get_rest).with("chef/_design/nodes/_view/mastodon") - @couchdb.get_view("nodes", "mastodon") - end - - it "should allow arguments to the view" do - @rest.should_receive(:get_rest).with("chef/_design/nodes/_view/mastodon?startkey=%22dont%20stay%22") - @couchdb.get_view("nodes", "mastodon", :startkey => "dont stay") - end - - end - - describe "view_uri" do - it "should output an appropriately formed view URI" do - @couchdb.should_receive(:view_uri).with("nodes", "all").and_return("chef/_design/nodes/_view/all") - @couchdb.view_uri("nodes", "all") - end - end - -end - - - - diff --git a/chef/spec/unit/environment_spec.rb b/chef/spec/unit/environment_spec.rb index 7b0a835e8c..97f0c3395e 100644 --- a/chef/spec/unit/environment_spec.rb +++ b/chef/spec/unit/environment_spec.rb @@ -235,107 +235,6 @@ describe Chef::Environment do end end - describe "when listing the available cookbooks filtered by policy" do - before(:each) do - @environment.name "prod" - @environment.cookbook_versions({ - "apt" => "= 1.0.0", - "apache2" => "= 2.0.0" - }) - Chef::Environment.stub!(:cdb_load).and_return @environment - - @all_cookbooks = [] - @all_cookbooks << begin - cv = Chef::CookbookVersion.new("apt") - cv.version = "1.0.0" - cv.recipe_filenames = ["default.rb", "only-in-1-0.rb"] - cv - end - @all_cookbooks << begin - cv = Chef::CookbookVersion.new("apt") - cv.version = "1.1.0" - cv.recipe_filenames = ["default.rb", "only-in-1-1.rb"] - cv - end - @all_cookbooks << begin - cv = Chef::CookbookVersion.new("apache2") - cv.version = "2.0.0" - cv.recipe_filenames = ["default.rb", "mod_ssl.rb"] - cv - end - @all_cookbooks << begin - cv = Chef::CookbookVersion.new("god") - cv.version = "4.2.0" - cv.recipe_filenames = ["default.rb"] - cv - end - Chef::CookbookVersion.stub!(:cdb_list).and_return @all_cookbooks - end - - it "should load the environment" do - Chef::Environment.should_receive(:cdb_load).with("prod", nil) - Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - end - - it "should handle cookbooks with no available version" do - @environment.cookbook_versions({ - "apt" => "> 999.0.0", - "apache2" => "= 2.0.0" - }) - Chef::Environment.should_receive(:cdb_load).with("prod", nil) - recipes = Chef::Environment.cdb_load_filtered_recipe_list("prod") - # order doesn't matter - recipes.should =~ ["god", "apache2", "apache2::mod_ssl"] - end - - - it "should load all the cookbook versions" do - Chef::CookbookVersion.should_receive(:cdb_list) - Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - recipes = Chef::Environment.cdb_load_filtered_recipe_list("prod") - recipes.should =~ ["apache2", "apache2::mod_ssl", "apt", - "apt::only-in-1-0", "god"] - end - - it "should load all the cookbook versions with no policy" do - @environment.cookbook_versions({}) - Chef::CookbookVersion.should_receive(:cdb_list) - Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - recipes = Chef::Environment.cdb_load_filtered_recipe_list("prod") - recipes.should =~ ["apache2", "apache2::mod_ssl", "apt", - "apt::only-in-1-1", "god"] - end - - it "should restrict the cookbook versions, as specified in the environment" do - res = Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - res["apt"].detect {|cb| cb.version == "1.0.0"}.should_not == nil - res["apache2"].detect {|cb| cb.version == "2.0.0"}.should_not == nil - res["god"].detect {|cb| cb.version == "4.2.0"}.should_not == nil - end - - it "should produce correct results, regardless of the cookbook order in couch" do - # a bug present before the environments feature defaulted to the last CookbookVersion - # object for a cookbook as returned from couchdb when fetching cookbooks for a node - # this is a regression test - @all_cookbooks << begin - cv = Chef::CookbookVersion.new("god") - cv.version = "0.0.1" - cv - end - res = Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - res["apt"].detect {|cb| cb.version == "1.0.0"}.should_not == nil - res["apache2"].detect {|cb| cb.version == "2.0.0"}.should_not == nil - res["god"].detect {|cb| cb.version == "4.2.0"}.should_not == nil - end - - it "should return all versions of a cookbook that meet the version requirement" do - @environment.cookbook "apt", ">= 1.0.0" - res = Chef::Environment.cdb_load_filtered_cookbook_versions("prod") - res["apt"].detect {|cb| cb.version == "1.0.0"}.should_not == nil - res["apt"].detect {|cb| cb.version == "1.1.0"}.should_not == nil - end - end - describe "self.validate_cookbook_versions" do before(:each) do @cookbook_versions = { @@ -376,35 +275,6 @@ describe Chef::Environment do end end - describe "self.create_default_environment" do - it "should check if the '_default' environment exists" do - @couchdb = Chef::CouchDB.new - Chef::CouchDB.stub!(:new).and_return @couchdb - Chef::Environment.should_receive(:cdb_load).with('_default', Chef::CouchDB.new) - Chef::Environment.create_default_environment - end - - it "should not re-create the environment if it exists" do - @couchdb = Chef::CouchDB.new - Chef::CouchDB.stub!(:new).and_return @couchdb - Chef::Environment.should_receive(:cdb_load).with('_default', Chef::CouchDB.new).and_return true - Chef::Environment.should_not_receive(:new) - Chef::Environment.create_default_environment - end - - it "should create the environment if it doesn't exist" do - @env = Chef::Environment.new - @env.stub!(:cdb_save).and_return true - @couchdb = Chef::CouchDB.new - Chef::Environment.stub!(:new).and_return @env - Chef::CouchDB.stub!(:new).and_return @couchdb - - Chef::Environment.should_receive(:cdb_load).with('_default', Chef::CouchDB.new).and_raise(Chef::Exceptions::CouchDBNotFound) - Chef::Environment.should_receive(:new) - Chef::Environment.create_default_environment - end - end - describe "when updating from a parameter hash" do before do @environment = Chef::Environment.new diff --git a/chef/spec/unit/exceptions_spec.rb b/chef/spec/unit/exceptions_spec.rb index fe920fd817..a979d2f6b9 100644 --- a/chef/spec/unit/exceptions_spec.rb +++ b/chef/spec/unit/exceptions_spec.rb @@ -8,9 +8,9 @@ # 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. @@ -40,7 +40,6 @@ describe Chef::Exceptions do Chef::Exceptions::Group => RuntimeError, Chef::Exceptions::Link => RuntimeError, Chef::Exceptions::Mount => RuntimeError, - Chef::Exceptions::CouchDBNotFound => RuntimeError, Chef::Exceptions::PrivateKeyMissing => RuntimeError, Chef::Exceptions::CannotWritePrivateKey => RuntimeError, Chef::Exceptions::RoleNotFound => RuntimeError, diff --git a/chef/spec/unit/index_queue_spec.rb b/chef/spec/unit/index_queue_spec.rb deleted file mode 100644 index 3043585757..0000000000 --- a/chef/spec/unit/index_queue_spec.rb +++ /dev/null @@ -1,391 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@kallistec.com>) -# Copyright:: Copyright (c) 2009 Daniel DeLeo -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -class Chef - class IndexableTestHarness - include Chef::IndexQueue::Indexable - attr_reader :couchdb_id - def couchdb_id=(value) - self.index_id = @couchdb_id = value - end - attr_reader :index_id - def index_id=(value) - @index_id = value - end - - def to_hash - {"ohai_world" => "I am IndexableTestHarness", "object_id" => object_id} - end - - end -end - -class IndexQueueSpecError < RuntimeError ; end - -class FauxQueue - - attr_reader :published_message, :publish_options - - # Note: If publish is not called, this published_message will cause - # JSON parsing to die with "can't convert Symbol into String" - def initialize - @published_message = :epic_fail! - @publish_options = :epic_fail! - end - - def publish(message, options=nil) - @published_message = message - @publish_options = options - end -end - -class IndexConsumerTestHarness - include Chef::IndexQueue::Consumer - - attr_reader :last_indexed_object, :unexposed_attr - - expose :index_this - - def index_this(object_to_index) - @last_indexed_object = object_to_index - end - - def not_exposed(arg) - @unexposed_attr = arg - end -end - -describe Chef::IndexQueue::Indexable do - def a_uuid - /[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}/ - end - - before do - Chef::IndexableTestHarness.reset_index_metadata! - @publisher = Chef::IndexQueue::AmqpClient.instance - @indexable_obj = Chef::IndexableTestHarness.new - @item_as_hash = {"ohai_world" => "I am IndexableTestHarness", "object_id" => @indexable_obj.object_id} - - @now = Time.now - Time.stub!(:now).and_return(@now) - end - - it "downcases the class name for the index_object_type when it's not explicitly set" do - @indexable_obj.index_object_type.should == "indexable_test_harness" - end - - it "uses an explicitly set index_object_type" do - Chef::IndexableTestHarness.index_object_type :a_weird_name - @indexable_obj.index_object_type.should == "a_weird_name" - end - - it "adds 'database', 'type', and 'id' (UUID) keys to the published object" do - with_metadata = @indexable_obj.with_indexer_metadata(:database => "foo", :id=>UUIDTools::UUID.random_create.to_s) - with_metadata.should have(5).keys - with_metadata.keys.should include("type", "id", "item", "database", "enqueued_at") - with_metadata["type"].should == "indexable_test_harness" - with_metadata["database"].should == "foo" - with_metadata["item"].should == @item_as_hash - with_metadata["id"].should match(a_uuid) - with_metadata["enqueued_at"].should == @now.utc.to_i - end - - it "uses the couchdb_id if available" do - expected_uuid = "0000000-1111-2222-3333-444444444444" - @indexable_obj.couchdb_id = expected_uuid - metadata_id = @indexable_obj.with_indexer_metadata["id"] - metadata_id.should == expected_uuid - end - - describe "adds and removes items to and from the index and respects Chef::Config[:persistent_queue]" do - before do - @exchange = mock("Bunny::Exchange") - @amqp_client = mock("Bunny::Client", :start => true, :exchange => @exchange) - @publisher.stub!(:amqp_client).and_return(@amqp_client) - @queue = FauxQueue.new - @publisher.should_receive(:queue_for_object).with("0000000-1111-2222-3333-444444444444").and_yield(@queue) - Chef::Config[:persistent_queue] = false - end - - it "adds items to the index" do - @amqp_client.should_not_receive(:tx_select) - @amqp_client.should_not_receive(:tx_commit) - @amqp_client.should_not_receive(:tx_rollback) - - @indexable_obj.add_to_index(:database => "couchdb@localhost,etc.", :id=>"0000000-1111-2222-3333-444444444444") - - published_message = Chef::JSONCompat.from_json(@queue.published_message) - published_message.should == {"action" => "add", "payload" => {"item" => @item_as_hash, - "type" => "indexable_test_harness", - "database" => "couchdb@localhost,etc.", - "id" => "0000000-1111-2222-3333-444444444444", - "enqueued_at" => @now.utc.to_i}} - @queue.publish_options[:persistent].should == false - end - - it "adds items to the index transactionactionally when Chef::Config[:persistent_queue] == true" do - @amqp_client.should_receive(:tx_select) - @amqp_client.should_receive(:tx_commit) - @amqp_client.should_not_receive(:tx_rollback) - - # set and restore Chef::Config[:persistent_queue] to true - orig_value = Chef::Config[:persistent_queue] - Chef::Config[:persistent_queue] = true - begin - @indexable_obj.add_to_index(:database => "couchdb@localhost,etc.", :id=>"0000000-1111-2222-3333-444444444444") - ensure - Chef::Config[:persistent_queue] = orig_value - end - - published_message = Chef::JSONCompat.from_json(@queue.published_message) - published_message.should == {"action" => "add", "payload" => {"item" => @item_as_hash, - "type" => "indexable_test_harness", - "database" => "couchdb@localhost,etc.", - "id" => "0000000-1111-2222-3333-444444444444", - "enqueued_at" => @now.utc.to_i}} - @queue.publish_options[:persistent].should == true - end - - it "adds items to the index transactionally when Chef::Config[:persistent_queue] == true and rolls it back when there is a failure" do - @amqp_client.should_receive(:tx_select) - @amqp_client.should_receive(:tx_rollback) - @amqp_client.should_not_receive(:tx_commit) - - # cause the publish to fail, and make sure the failure is our own - # by using a specific class - @queue.should_receive(:publish).and_raise(IndexQueueSpecError) - - # set and restore Chef::Config[:persistent_queue] to true - orig_value = Chef::Config[:persistent_queue] - Chef::Config[:persistent_queue] = true - begin - lambda{ - @indexable_obj.add_to_index(:database => "couchdb@localhost,etc.", :id=>"0000000-1111-2222-3333-444444444444") - }.should raise_error(IndexQueueSpecError) - ensure - Chef::Config[:persistent_queue] = orig_value - end - end - - it "removes items from the index" do - @amqp_client.should_not_receive(:tx_select) - @amqp_client.should_not_receive(:tx_commit) - @amqp_client.should_not_receive(:tx_rollback) - - @indexable_obj.delete_from_index(:database => "couchdb2@localhost", :id=>"0000000-1111-2222-3333-444444444444") - published_message = Chef::JSONCompat.from_json(@queue.published_message) - published_message.should == {"action" => "delete", "payload" => { "item" => @item_as_hash, - "type" => "indexable_test_harness", - "database" => "couchdb2@localhost", - "id" => "0000000-1111-2222-3333-444444444444", - "enqueued_at" => @now.utc.to_i}} - @queue.publish_options[:persistent].should == false - end - - it "removes items from the index transactionactionally when Chef::Config[:persistent_queue] == true" do - @amqp_client.should_receive(:tx_select) - @amqp_client.should_receive(:tx_commit) - @amqp_client.should_not_receive(:tx_rollback) - - # set and restore Chef::Config[:persistent_queue] to true - orig_value = Chef::Config[:persistent_queue] - Chef::Config[:persistent_queue] = true - begin - @indexable_obj.delete_from_index(:database => "couchdb2@localhost", :id=>"0000000-1111-2222-3333-444444444444") - ensure - Chef::Config[:persistent_queue] = orig_value - end - - published_message = Chef::JSONCompat.from_json(@queue.published_message) - published_message.should == {"action" => "delete", "payload" => { "item" => @item_as_hash, - "type" => "indexable_test_harness", - "database" => "couchdb2@localhost", - "id" => "0000000-1111-2222-3333-444444444444", - "enqueued_at" => @now.utc.to_i}} - @queue.publish_options[:persistent].should == true - end - - it "remove items from the index transactionally when Chef::Config[:persistent_queue] == true and rolls it back when there is a failure" do - @amqp_client.should_receive(:tx_select) - @amqp_client.should_receive(:tx_rollback) - @amqp_client.should_not_receive(:tx_commit) - - # cause the publish to fail, and make sure the failure is our own - # by using a specific class - @queue.should_receive(:publish).and_raise(IndexQueueSpecError) - - # set and restore Chef::Config[:persistent_queue] to true - orig_value = Chef::Config[:persistent_queue] - Chef::Config[:persistent_queue] = true - begin - lambda{ - @indexable_obj.delete_from_index(:database => "couchdb2@localhost", :id=>"0000000-1111-2222-3333-444444444444") }.should raise_error(IndexQueueSpecError) - ensure - Chef::Config[:persistent_queue] = orig_value - end - end - end - -end - -describe Chef::IndexQueue::Consumer do - before do - @amqp_client = Chef::IndexQueue::AmqpClient.instance - @consumer = IndexConsumerTestHarness.new - end - - it "keeps a whitelist of exposed methods" do - IndexConsumerTestHarness.exposed_methods.should == [:index_this] - IndexConsumerTestHarness.whitelisted?(:index_this).should be_true - IndexConsumerTestHarness.whitelisted?(:not_exposed).should be_false - end - - it "doesn't route non-whitelisted methods" do - payload_json = {"payload" => {"a_placeholder" => "object"}, "action" => "not_exposed"}.to_json - received_message = {:payload => payload_json} - lambda {@consumer.call_action_for_message(received_message)}.should raise_error(ArgumentError) - @consumer.unexposed_attr.should be_nil - end - - it "routes message payloads to the correct method" do - payload_json = {"payload" => {"a_placeholder" => "object"}, "action" => "index_this"}.to_json - received_message = {:payload => payload_json} - @consumer.call_action_for_message(received_message) - @consumer.last_indexed_object.should == {"a_placeholder" => "object"} - - end - - it "subscribes to the queue for the indexer" do - payload_json = {"payload" => {"a_placeholder" => "object"}, "action" => "index_this"}.to_json - message = {:payload => payload_json} - queue = mock("Bunny::Queue") - @amqp_client.stub!(:queue).and_return(queue) - queue.should_receive(:subscribe).with(:timeout => false, :ack => true).and_yield(message) - @consumer.run - @consumer.last_indexed_object.should == {"a_placeholder" => "object"} - end - -end - - -describe Chef::IndexQueue::AmqpClient do - before do - Chef::Config[:amqp_host] = '4.3.2.1' - Chef::Config[:amqp_port] = '1337' - Chef::Config[:amqp_user] = 'teh_rspecz' - Chef::Config[:amqp_pass] = 'access_granted2rspec' - Chef::Config[:amqp_vhost] = '/chef-specz' - Chef::Config[:amqp_consumer_id] = nil - - @publisher = Chef::IndexQueue::AmqpClient.instance - @exchange = mock("Bunny::Exchange") - - @amqp_client = mock("Bunny::Client", :start => true, :exchange => @exchange) - def @amqp_client.connected?; false; end # stubbing predicate methods not working? - Bunny.stub!(:new).and_return(@amqp_client) - - @publisher.reset! - end - - after do - @publisher.disconnected! - end - - it "is a singleton" do - lambda {Chef::IndexQueue::Indexable::AmqpClient.new}.should raise_error - end - - it "creates an amqp client object on demand, starts a connection, and caches it" do - @amqp_client.should_receive(:start).once - @amqp_client.should_receive(:qos).with(:prefetch_count => 1) - ::Bunny.should_receive(:new).once.and_return(@amqp_client) - @publisher.amqp_client.should == @amqp_client - @publisher.amqp_client - end - - it "configures the amqp client with credentials from the config file" do - @publisher.reset! - Bunny.should_receive(:new).with(:spec => '08', :host => '4.3.2.1', :port => '1337', :user => "teh_rspecz", - :pass => "access_granted2rspec", :vhost => '/chef-specz').and_return(@amqp_client) - @amqp_client.should_receive(:qos).with(:prefetch_count => 1) - @publisher.amqp_client.should == @amqp_client - end - - it "creates an amqp exchange on demand and caches it" do - @amqp_client.stub!(:qos) - @publisher.exchange.should == @exchange - @amqp_client.should_not_receive(:exchange) - @publisher.exchange.should == @exchange - end - - describe "publishing" do - - before do - @queue_1 = FauxQueue.new - @queue_2 = FauxQueue.new - - @amqp_client.stub!(:qos) - #@amqp_client.stub!(:queue).and_return(@queue) - @data = {"some_data" => "in_a_hash"} - end - - it "resets the client upon a Bunny::ServerDownError when publishing" do - Bunny.stub!(:new).and_return(@amqp_client) - @amqp_client.should_receive(:queue).with("vnode-68", {:passive=>false, :durable=>true, :exclusive=>false, :auto_delete=>false}).twice.and_return(@queue_1, @queue_2) - - @queue_1.should_receive(:publish).with(@data).and_raise(Bunny::ServerDownError) - @queue_2.should_receive(:publish).with(@data).and_raise(Bunny::ServerDownError) - - @publisher.should_receive(:disconnected!).at_least(3).times - lambda {@publisher.queue_for_object("00000000-1111-2222-3333-444444444444") {|q| q.publish(@data)}}.should raise_error(Bunny::ServerDownError) - end - - it "resets the client upon a Bunny::ConnectionError when publishing" do - Bunny.stub!(:new).and_return(@amqp_client) - @amqp_client.should_receive(:queue).with("vnode-68", {:passive=>false, :durable=>true, :exclusive=>false, :auto_delete=>false}).twice.and_return(@queue_1, @queue_2) - - @queue_1.should_receive(:publish).with(@data).and_raise(Bunny::ConnectionError) - @queue_2.should_receive(:publish).with(@data).and_raise(Bunny::ConnectionError) - - @publisher.should_receive(:disconnected!).at_least(3).times - lambda {@publisher.queue_for_object("00000000-1111-2222-3333-444444444444") {|q| q.publish(@data)}}.should raise_error(Bunny::ConnectionError) - end - - it "resets the client upon a Errno::ECONNRESET when publishing" do - Bunny.stub!(:new).and_return(@amqp_client) - @amqp_client.should_receive(:queue).with("vnode-68", {:passive=>false, :durable=>true, :exclusive=>false, :auto_delete=>false}).twice.and_return(@queue_1, @queue_2) - - @queue_1.should_receive(:publish).with(@data).and_raise(Errno::ECONNRESET) - @queue_2.should_receive(:publish).with(@data).and_raise(Errno::ECONNRESET) - - @publisher.should_receive(:disconnected!).at_least(3).times - lambda {@publisher.queue_for_object("00000000-1111-2222-3333-444444444444") {|q| q.publish(@data)}}.should raise_error(Errno::ECONNRESET) - end - - end - - it "stops bunny and clears subscriptions" do - bunny_client = mock("Bunny::Client") - @publisher.instance_variable_set(:@amqp_client, bunny_client) - bunny_client.should_receive(:stop) - @publisher.stop - end - -end diff --git a/chef/spec/unit/knife/ssh_spec.rb b/chef/spec/unit/knife/ssh_spec.rb index a4853e11cc..6e90a87f01 100644 --- a/chef/spec/unit/knife/ssh_spec.rb +++ b/chef/spec/unit/knife/ssh_spec.rb @@ -36,10 +36,10 @@ describe Chef::Knife::Ssh do @knife = Chef::Knife::Ssh.new @knife.config.clear @knife.config[:attribute] = "fqdn" - @node_foo = Chef::Node.new('foo') + @node_foo = Chef::Node.new @node_foo.automatic_attrs[:fqdn] = "foo.example.org" @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1" - @node_bar = Chef::Node.new('bar') + @node_bar = Chef::Node.new @node_bar.automatic_attrs[:fqdn] = "bar.example.org" @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2" end @@ -64,7 +64,7 @@ describe Chef::Knife::Ssh do @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2']) @knife.configure_session end - + it "returns an array of the attributes specified on the command line even when a config value is set" do @knife.config[:attribute] = "config_file" # this value will be the config file @knife.config[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute diff --git a/chef/spec/unit/lwrp_spec.rb b/chef/spec/unit/lwrp_spec.rb index 76834cf182..da2278e547 100644 --- a/chef/spec/unit/lwrp_spec.rb +++ b/chef/spec/unit/lwrp_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -94,7 +94,7 @@ describe "LWRP" do end it "should have access to the run context and node during class definition" do - node = Chef::Node.new(nil) + node = Chef::Node.new node.normal[:penguin_name] = "jackass" run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new, @events) diff --git a/chef/spec/unit/mixin/params_validate_spec.rb b/chef/spec/unit/mixin/params_validate_spec.rb index dd0366c37c..b79156109b 100644 --- a/chef/spec/unit/mixin/params_validate_spec.rb +++ b/chef/spec/unit/mixin/params_validate_spec.rb @@ -330,10 +330,12 @@ describe Chef::Mixin::ParamsValidate do it "asserts that a value returns false from a predicate method" do lambda do - @vo.validate({:not_blank => "should pass"}, {:not_blank => {:cannot_be => :blank}}) + @vo.validate({:not_blank => "should pass"}, + {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) end.should_not raise_error lambda do - @vo.validate({:not_blank => ""}, {:not_blank => {:cannot_be => :blank}}) + @vo.validate({:not_blank => ""}, + {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}}) end.should raise_error(Chef::Exceptions::ValidationFailed) end diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb index 4577d5098b..b6f63c9651 100644 --- a/chef/spec/unit/node_spec.rb +++ b/chef/spec/unit/node_spec.rb @@ -681,80 +681,4 @@ describe Chef::Node do end end - describe "acting as a CouchDB-backed model" do - before(:each) do - @couchdb = Chef::CouchDB.new - @mock_couch = mock('couch mock') - end - - describe "list" do - before(:each) do - @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.cdb_list.should eql(["avenue"]) - end - - it "should return just the ids if inflate is false" do - Chef::Node.cdb_list(false).should eql(["avenue"]) - end - - it "should return the full objects if inflate is true" do - Chef::Node.cdb_list(true).should eql(["a"]) - end - end - - describe "when loading a given node" do - it "should load a node from couchdb by name" do - @couchdb.should_receive(:load).with("node", "coffee").and_return(true) - Chef::CouchDB.stub!(:new).and_return(@couchdb) - Chef::Node.cdb_load("coffee") - end - end - - describe "when destroying a Node" do - it "should delete this node from couchdb" do - @couchdb.should_receive(:delete).with("node", "bob", 1).and_return(true) - Chef::CouchDB.stub!(:new).and_return(@couchdb) - node = Chef::Node.new - node.name "bob" - node.couchdb_rev = 1 - node.cdb_destroy - end - end - - describe "when saving a Node" do - before(:each) do - @couchdb.stub!(:store).and_return({ "rev" => 33 }) - Chef::CouchDB.stub!(:new).and_return(@couchdb) - @node = Chef::Node.new - @node.name "bob" - @node.couchdb_rev = 1 - end - - it "should save the node to couchdb" do - @couchdb.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 }) - @node.cdb_save - end - - it "should store the new couchdb_rev" do - @node.cdb_save - @node.couchdb_rev.should eql(33) - end - end - - describe "create_design_document" do - it "should create our design document" do - @couchdb.should_receive(:create_design_document).with("nodes", Chef::Node::DESIGN_DOCUMENT) - Chef::CouchDB.stub!(:new).and_return(@couchdb) - Chef::Node.create_design_document - end - end - - end - end diff --git a/chef/spec/unit/openid_registration_spec.rb b/chef/spec/unit/openid_registration_spec.rb deleted file mode 100644 index 70d4964104..0000000000 --- a/chef/spec/unit/openid_registration_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require '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 = Chef::JSONCompat.from_json(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/provider/ohai_spec.rb b/chef/spec/unit/provider/ohai_spec.rb index c86ad288eb..8402c92e97 100644 --- a/chef/spec/unit/provider/ohai_spec.rb +++ b/chef/spec/unit/provider/ohai_spec.rb @@ -34,7 +34,7 @@ describe Chef::Provider::Ohai do :platform => @platform, :platform_version => @platform_version, :data => { - :origdata => "somevalue" + :origdata => "somevalue" }, :data2 => { :origdata => "somevalue", @@ -49,7 +49,7 @@ describe Chef::Provider::Ohai do Chef::Platform.stub!(:find_platform_and_version).and_return({ "platform" => @platform, "platform_version" => @platform_version}) # Fake node with a dummy save - @node = Chef::Node.new(@hostname) + @node = Chef::Node.new @node.name(@fqdn) @node.stub!(:save).and_return(@node) @events = Chef::EventDispatch::Dispatcher.new diff --git a/chef/spec/unit/run_list_spec.rb b/chef/spec/unit/run_list_spec.rb index cc261edbd8..f18f21a82b 100644 --- a/chef/spec/unit/run_list_spec.rb +++ b/chef/spec/unit/run_list_spec.rb @@ -23,13 +23,6 @@ require 'spec_helper' require 'chef/version_class' require 'chef/version_constraint' -# dep_selector/gecode on many platforms is currenly a bowel of hurt -begin -require 'chef/cookbook_version_selector' -rescue LoadError - STDERR.puts "\n*** dep_selector not installed. marking all unit tests 'pending' that have a transitive dependency on dep_selector. ***\n\n" -end - describe Chef::RunList do before(:each) do @run_list = Chef::RunList.new @@ -255,13 +248,6 @@ describe Chef::RunList do end - describe "from couchdb" do - it "should load the role from couchdb" do - Chef::Role.should_receive(:cdb_load).and_return(@role) - @run_list.expand("_default", "couchdb") - end - end - it "should return the list of expanded recipes" do expansion = @run_list.expand("_default") expansion.recipes[0].should == "one" @@ -323,199 +309,4 @@ describe Chef::RunList do end - describe "constrain" do - - pending "=> can't find 'dep_selector' gem...skipping Chef::CookbookVersionSelector related tests" do - - @fake_db = Object.new - - def cookbook_maker(name, version, deps) - book = Chef::CookbookVersion.new(name, @fake_db) - book.version = version - deps.each { |dep_name, vc| book.metadata.depends(dep_name, vc) } - book - end - - def vc_maker(cookbook_name, version_constraint) - vc = Chef::VersionConstraint.new(version_constraint) - { :name => cookbook_name, :version_constraint => vc } - end - - def assert_failure_unsatisfiable_item(run_list, all_cookbooks, constraints, expected_message) - begin - Chef::CookbookVersionSelector.constrain(all_cookbooks, constraints) - fail "Should have raised a Chef::Exceptions::CookbookVersionSelection::UnsatisfiableRunListItem exception" - rescue Chef::Exceptions::CookbookVersionSelection::UnsatisfiableRunListItem => urli - urli.message.should include(expected_message) - end - end - - def assert_failure_invalid_items(run_list, all_cookbooks, constraints, expected_message) - begin - Chef::CookbookVersionSelector.constrain(all_cookbooks, constraints) - fail "Should have raised a Chef::Exceptions::CookbookVersionSelection::InvalidRunListItems exception" - rescue Chef::Exceptions::CookbookVersionSelection::InvalidRunListItems => irli - irli.message.should include(expected_message) - end - end - - before(:each) do - a1 = cookbook_maker("a", "1.0", [["c", "< 4.0"]]) - b1 = cookbook_maker("b", "1.0", [["c", "< 3.0"]]) - - c2 = cookbook_maker("c", "2.0", [["d", "> 1.0"], ["f", nil]]) - c3 = cookbook_maker("c", "3.0", [["d", "> 2.0"], ["e", nil]]) - - d1_1 = cookbook_maker("d", "1.1", []) - d2_1 = cookbook_maker("d", "2.1", []) - e1 = cookbook_maker("e", "1.0", []) - f1 = cookbook_maker("f", "1.0", []) - g1 = cookbook_maker("g", "1.0", [["d", "> 5.0"]]) - - n1_1 = cookbook_maker("n", "1.1", []) - n1_2 = cookbook_maker("n", "1.2", []) - n1_10 = cookbook_maker("n", "1.10", []) - - depends_on_nosuch = cookbook_maker("depends_on_nosuch", "1.0", [["nosuch", nil]]) - - @all_cookbooks = { - "a" => [a1], - "b" => [b1], - "c" => [c2, c3], - "d" => [d1_1, d2_1], - "e" => [e1], - "f" => [f1], - "g" => [g1], - "n" => [n1_1, n1_2, n1_10], - "depends_on_nosuch" => [depends_on_nosuch] - } - - $stderr.reopen DEV_NULL - end - - after do - $stderr.reopen STDERR - end - - it "pulls in transitive dependencies" do - constraints = [vc_maker("a", "~> 1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - %w(a c d e).each { |k| cookbooks.should have_key k } - cookbooks.size.should == 4 - cookbooks["c"].version.should == "3.0.0" - cookbooks["d"].version.should == "2.1.0" - end - - it "should satisfy recipe-specific dependencies" do - depends_on_recipe = cookbook_maker("depends_on_recipe", "1.0", [["f::recipe", "1.0"]]) - @all_cookbooks["depends_on_recipe"] = [depends_on_recipe] - constraints = [vc_maker("depends_on_recipe", "= 1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks["f"].version.should == "1.0.0" - end - - it "properly sorts version triples, treating each term numerically" do - constraints = [vc_maker("n", "> 1.2")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 1 - cookbooks["n"].version.should == "1.10.0" - end - - it "should fail to find a solution when a run list item is constrained to a range that includes no cookbooks" do - constraints = [vc_maker("d", "> 5.0")] - assert_failure_invalid_items(@run_list, @all_cookbooks, constraints, "Run list contains invalid items: no versions match the constraints on cookbook d.") - end - - it "should fail to find a solution when a run list item's dependency is constrained to a range that includes no cookbooks" do - constraints = [vc_maker("g", nil)] - assert_failure_unsatisfiable_item(@run_list, @all_cookbooks, constraints, "Unable to satisfy constraints on cookbook d due to run list item (g >= 0.0.0)") - end - - it "selects 'd 2.1.0' given constraint 'd > 1.2.3'" do - constraints = [vc_maker("d", "> 1.2.3")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 1 - cookbooks["d"].version.should == "2.1.0" - end - - it "selects largest version when constraint allows multiple" do - constraints = [vc_maker("d", "> 1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 1 - cookbooks["d"].version.should == "2.1.0" - end - - it "selects 'd 1.1.0' given constraint 'd ~> 1.0'" do - constraints = [vc_maker("d", "~> 1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 1 - cookbooks["d"].version.should == "1.1.0" - end - - it "raises InvalidRunListItems for an unknown cookbook in the run list" do - constraints = [vc_maker("nosuch", "1.0.0")] - assert_failure_invalid_items(@run_list, @all_cookbooks, constraints, "Run list contains invalid items: no such cookbook nosuch.") - end - - it "raises CookbookVersionConflict for an unknown cookbook in a cookbook's dependencies" do - constraints = [vc_maker("depends_on_nosuch", "1.0.0")] - assert_failure_unsatisfiable_item(@run_list, @all_cookbooks, constraints, "Unable to satisfy constraints on cookbook nosuch, which does not exist, due to run list item (depends_on_nosuch = 1.0.0). Run list items that may result in a constraint on nosuch: [(depends_on_nosuch = 1.0.0) -> (nosuch >= 0.0.0)]") - end - - it "raises UnsatisfiableRunListItem for direct conflict" do - constraints = [vc_maker("d", "= 1.1.0"), vc_maker("d", ">= 2.0")] - assert_failure_unsatisfiable_item(@run_list, @all_cookbooks, constraints, "Unable to satisfy constraints on cookbook d due to run list item (d >= 2.0.0)") - end - - describe "should solve regardless of constraint order" do - - it "raises CookbookVersionConflict a then b" do - # Cookbooks a and b both have a dependency on c, but with - # differing constraints. - constraints = [vc_maker("a", "1.0"), vc_maker("b", "1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 5 - %w(a b c d f).each { |k| cookbooks.should have_key k } - cookbooks["a"].version.should == "1.0.0" - cookbooks["b"].version.should == "1.0.0" - cookbooks["c"].version.should == "2.0.0" - cookbooks["d"].version.should == "2.1.0" - end - - it "resolves b then a" do - # See above comment for a then b. When b is pulled in first, - # we should get a version of c that satifies the constraints - # on the c dependency for both b and a. - constraints = [vc_maker("b", "1.0"), vc_maker("a", "1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 5 - %w(a b c d f).each { |k| cookbooks.should have_key k } - cookbooks["a"].version.should == "1.0.0" - cookbooks["b"].version.should == "1.0.0" - cookbooks["c"].version.should == "2.0.0" - cookbooks["d"].version.should == "2.1.0" - end - - it "resolves a then d" do - constraints = [vc_maker("a", "1.0"), vc_maker("d", "1.1")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 4 - %w(a c d f).each { |k| cookbooks.should have_key k } - cookbooks["a"].version.should == "1.0.0" - cookbooks["c"].version.should == "2.0.0" - cookbooks["d"].version.should == "1.1.0" - end - - it "resolves d then a" do - constraints = [vc_maker("d", "1.1"), vc_maker("a", "1.0")] - cookbooks = Chef::CookbookVersionSelector.constrain(@all_cookbooks, constraints) - cookbooks.size.should == 4 - %w(a c d f).each { |k| cookbooks.should have_key k } - cookbooks["a"].version.should == "1.0.0" - cookbooks["c"].version.should == "2.0.0" - cookbooks["d"].version.should == "1.1.0" - end - end - end - end end diff --git a/chef/spec/unit/solr_query/query_transform_spec.rb b/chef/spec/unit/solr_query/query_transform_spec.rb deleted file mode 100644 index f3fc746746..0000000000 --- a/chef/spec/unit/solr_query/query_transform_spec.rb +++ /dev/null @@ -1,454 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'chef/solr_query/query_transform' - -describe "Chef::SolrQuery::QueryTransform" do - before(:each) do - @parser = Chef::SolrQuery::QueryTransform - @parseError = Chef::Exceptions::QueryParseError - end - - describe "single term queries" do - basic_terms = %w(a ab 123 a1 2b foo_bar baz-baz) - basic_terms << " leading" - basic_terms << "trailing " - basic_terms += %w(XAND ANDX XOR ORX XNOT NOTX) - basic_terms.each do |term| - expect = "T:#{term.strip}" - it "'#{term}' => #{expect}" do - @parser.parse(term).should == expect - end - end - describe "invalid" do - %w(AND OR NOT :).each do |t| - it "'#{t}' => ParseError" do - lambda { @parser.parse(t) }.should raise_error(@parseError) - end - end - end - - describe "wildcards in terms" do - it "allows * as a wildcard" do - @parser.parse("foo*bar").should == "T:foo*bar" - end - - it "allows a single ? as a wildcard" do - @parser.parse("foo?bar").should == "T:foo?bar" - end - - it "allows multiple ? as fixed length wildcards" do - @parser.parse("foo???bar").should == "T:foo???bar" - end - - it "allows a leading wildcard with *" do - # NOTE: This is not valid lucene query syntax. However, our - # index format and query transformation can allow it because - # the transformed query ends up with the '*' not in leading - # position. We decided that allowing it makes sense because - # queries like ec2:* are useful and many users expect this - # behavior to work. - @parser.parse("*foobar").should == "T:*foobar" - end - - it "does not allow a leading wildcard with ?" do - lambda { @parser.parse("?foobar") }.should raise_error(@parseError) - end - - it "does not allow a leading wildcard with ?" do - lambda { @parser.parse("afield:?foobar") }.should raise_error(@parseError) - end - - end - - describe "escaped special characters in terms" do - special_chars = ["!", "(", ")", "{", "}", "[", "]", "^", "\"", - "~", "*", "?", ":", "\\", "&", "|", "+", "-"] - example_fmts = ['foo%sbar', '%sb', 'a%s'] - special_chars.each do |char| - example_fmts.each do |fmt| - input = fmt % ("\\" + char) - expect = "T:#{input}" - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - end - end - - describe "special characters in terms are not allowed" do - # NOTE: '*' is not a valid start letter for a lucene search - # term, however, we can support it because of our index - # structure and query transformation. We decided to keep this - # flexibility because queries like ec2:* are common and useful. - prefix_ok = ["!", "+", "-", "*"] - suffix_ok = ["*", "?", "~", "-"] - # FIXME: ideally, '!' would not be allowed in the middle of a - # term. Currently we parse foo!bar the same as foo !bar. - # Also '+' might be nice to disallow - embed_ok = ["*", "?", ":", "-", "!", "+"] - special_chars = ["!", "(", ")", "{", "}", "[", "]", "^", "\"", - "~", "*", "?", ":", "\\", "&", "|", "+", "-"] - example_fmts = { - :prefix => '%sb', - :middle => 'foo%sbar', - :suffix => 'a%s' - } - special_chars.each do |char| - example_fmts.keys.each do |key| - fmt = example_fmts[key] - if key == :prefix && prefix_ok.include?(char) - :pass - elsif key == :middle && embed_ok.include?(char) - :pass - elsif key == :suffix && suffix_ok.include?(char) - :pass - else - input = fmt % char - it "disallows: '#{input}'" do - lambda { @parser.parse(input) }.should raise_error(@parseError) - end - end - end - end - end - - end - - describe "multiple terms" do - it "should allow multiple terms" do - @parser.parse("a b cdefg").should == "T:a T:b T:cdefg" - end - end - - describe "boolean queries" do - describe "two term basic and/or" do - binary_operators = [['AND', 'AND'], ['&&', 'AND'], ['OR', 'OR'], ['||', 'OR']] - binary_operators.each do |op, op_name| - expect = "(OP:#{op_name} T:t1 T:t2)" - it "should parse 't1 #{op} t2' => #{expect}" do - @parser.parse("t1 #{op} t2").should == expect - end - end - end - - it "should allow a string of terms with ands and ors" do - expect = "(OP:AND T:t1 (OP:OR T:t2 (OP:AND T:t3 T:t4)))" - @parser.parse("t1 AND t2 OR t3 AND t4").should == expect - end - end - - describe "grouping with parens" do - it "should create a single group for (aterm)" do - @parser.parse("(aterm)").should == "(T:aterm)" - end - - describe "and booleans" do - - %w(AND &&).each do |op| - expect = "((OP:AND T:a T:b))" - input = "(a #{op} b)" - it "parses #{input} => #{expect}" do - @parser.parse(input).should == expect - end - end - - %w(OR ||).each do |op| - expect = "((OP:OR T:a T:b))" - input = "(a #{op} b)" - it "parses #{input} => #{expect}" do - @parser.parse(input).should == expect - end - end - - it "should handle a LHS group" do - expect = "(OP:OR ((OP:AND T:a T:b)) T:c)" - @parser.parse("(a && b) OR c").should == expect - @parser.parse("(a && b) || c").should == expect - end - - it "should handle a RHS group" do - expect = "(OP:OR T:c ((OP:AND T:a T:b)))" - @parser.parse("c OR (a && b)").should == expect - @parser.parse("c OR (a AND b)").should == expect - end - - it "should handle both sides as groups" do - expect = "(OP:OR ((OP:AND T:c T:d)) ((OP:AND T:a T:b)))" - @parser.parse("(c AND d) OR (a && b)").should == expect - end - end - end - - describe "NOT queries" do - # input, output - [ - ["a NOT b", "T:a (OP:NOT T:b)"], - ["a ! b", "T:a (OP:NOT T:b)"], - ["a !b", "T:a (OP:NOT T:b)"], - ["a NOT (b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"], - ["a ! (b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"], - ["a !(b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"] - ].each do |input, expected| - it "should parse '#{input}' => #{expected.inspect}" do - @parser.parse(input).should == expected - end - end - - ["NOT", "a NOT", "(NOT)"].each do |d| - it "should raise a ParseError on '#{d}'" do - lambda { @parser.parse(d) }.should raise_error(@parseError) - end - end - end - - describe 'required and prohibited prefixes (+/-)' do - ["+", "-"].each do |kind| - [ - ["#{kind}foo", "(OP:#{kind} T:foo)"], - ["bar #{kind}foo", "T:bar (OP:#{kind} T:foo)"], - ["(#{kind}oneA twoA) b", "((OP:#{kind} T:oneA) T:twoA) T:b"] - ].each do |input, expect| - it "should parse '#{input} => #{expect.inspect}" do - @parser.parse(input).should == expect - end - end - end - - # it 'ignores + embedded in a term' do - # @parser.parse("one+two").should == "T:one+two" - # end - - it 'ignores - embedded in a term' do - @parser.parse("one-two").should == "T:one-two" - end - - it "allows a trailing dash" do - @parser.parse("one-").should == "T:one-" - end - - end - - describe "phrases (strings)" do - phrases = [['"single"', 'STR:"single"'], - ['"two term"', 'STR:"two term"'], - ['"has \"escaped\" quote\"s"', 'STR:"has \"escaped\" quote\"s"'] - ] - phrases.each do |phrase, expect| - it "'#{phrase}' => #{expect}" do - @parser.parse(phrase).should == expect - end - end - - describe "invalid" do - bad = ['""', '":not:a:term"', '"a :bad:'] - bad.each do |t| - it "'#{t}' => ParseError" do - lambda { @parser.parse(t) }.should raise_error(@parseError) - end - end - end - - it "allows phrases to be required with '+'" do - @parser.parse('+"a b c"').should == '(OP:+ STR:"a b c")' - end - - it "allows phrases to be prohibited with '-'" do - @parser.parse('-"a b c"').should == '(OP:- STR:"a b c")' - end - - it "allows phrases to be excluded with NOT" do - @parser.parse('a NOT "b c"').should == 'T:a (OP:NOT STR:"b c")' - end - - end - - describe "fields" do - it "parses a term annotated with a field" do - @parser.parse("afield:aterm").should == "(F:afield T:aterm)" - end - - it "allows underscore in a field name" do - @parser.parse("a_field:aterm").should == "(F:a_field T:aterm)" - end - - it "parses a group annotated with a field" do - @parser.parse("afield:(a b c)").should == "(F:afield (T:a T:b T:c))" - end - - it "parses a phrase annotated with a field" do - @parser.parse('afield:"a b c"').should == '(F:afield STR:"a b c")' - end - - it "allows @ in a term" do - @parser.parse('afield:foo@acme.org').should == '(F:afield T:foo@acme.org)' - end - - describe "and binary operators" do - examples = [ - ['term1 AND afield:term2', "(OP:AND T:term1 (F:afield T:term2))"], - ['afield:term1 AND term2', "(OP:AND (F:afield T:term1) T:term2)"], - ['afield:term1 AND bfield:term2', - "(OP:AND (F:afield T:term1) (F:bfield T:term2))"]] - examples.each do |input, want| - it "'#{input}' => '#{want}'" do - @parser.parse(input).should == want - end - end - end - - describe "and unary operators" do - examples = [ - ['term1 AND NOT afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND ! afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND !afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND -afield:term2', - "(OP:AND T:term1 (OP:- (F:afield T:term2)))"], - ['-afield:[* TO *]', - "(OP:- (FR:afield [*] [*]))"] - ] - examples.each do |input, want| - it "#{input} => #{want}" do - @parser.parse(input).should == want - end - end - end - end - - describe "range queries" do - before(:each) do - @kinds = { - "inclusive" => {:left => "[", :right => "]"}, - "exclusive" => {:left => "{", :right => "}"} - } - end - - def make_expect(kind, field, s, e) - expect_fmt = "(FR:%s %s%s%s %s%s%s)" - left = @kinds[kind][:left] - right = @kinds[kind][:right] - expect_fmt % [field, left, s, right, left, e, right] - end - - def make_query(kind, field, s, e) - query_fmt = "%s:%s%s TO %s%s" - left = @kinds[kind][:left] - right = @kinds[kind][:right] - query_fmt % [field, left, s, e, right] - end - - ["inclusive", "exclusive"].each do |kind| - tests = [["afield", "start", "end"], - ["afield", "start", "*"], - ["afield", "*", "end"], - ["afield", "*", "*"] - ] - tests.each do |field, s, e| - it "parses an #{kind} range query #{s} TO #{e}" do - expect = make_expect(kind, field, s, e) - query = make_query(kind, field, s, e) - @parser.parse(query).should == expect - end - end - end - - describe "and binary operators" do - [["afield:[start TO end] AND term", - "(OP:AND (FR:afield [start] [end]) T:term)"], - ["term OR afield:[start TO end]", - "(OP:OR T:term (FR:afield [start] [end]))"], - ["f1:[s1 TO e1] OR f2:[s2 TO e2]", - "(OP:OR (FR:f1 [s1] [e1]) (FR:f2 [s2] [e2]))"] - ].each do |q, want| - it "parses '#{q}'" do - @parser.parse(q).should == want - end - end - end - - describe "and unary operators" do - [["t1 NOT afield:[start TO end]", - "T:t1 (OP:NOT (FR:afield [start] [end]))"] - ].each do |input, want| - it "#{input} => #{want}" do - @parser.parse(input).should == want - end - end - end - end - - describe "proximity query" do - [ - ['"one two"~10', '(OP:~ STR:"one two" 10)'], - ['word~', '(OP:~ T:word)'], - ['word~0.5', '(OP:~ T:word 0.5)'] - ].each do |input, expect| - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - end - - describe "term boosting" do - [ - ['"one two"^10', '(OP:^ STR:"one two" 10)'], - ['word^0.5', '(OP:^ T:word 0.5)'] - ].each do |input, expect| - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - - it "should fail to parse if no boosting argument is given" do - lambda { @parser.parse("foo^")}.should raise_error(@parseError) - end - end - - describe "examples" do - examples = [['tags:apples*.for.eating.com', "(F:tags T:apples*.for.eating.com)"], - ['ohai_time:[1234.567 TO *]', "(FR:ohai_time [1234.567] [*])"], - ['ohai_time:[* TO 1234.567]', "(FR:ohai_time [*] [1234.567])"], - ['ohai_time:[* TO *]', "(FR:ohai_time [*] [*])"]] - # ['aterm AND afield:aterm', "((OP:AND T:aterm ((F:afield T:aterm))))"], - # ['role:prod AND aterm', "blah"], - # ['role:prod AND xy:true', "blah"]] - examples.each do |input, want| - it "'#{input}' => '#{want}'" do - @parser.parse(input).should == want - end - end - end - - describe "transform queries for solr schema" do - testcase_file = "#{CHEF_SPEC_DATA}/search_queries_to_transform.txt" - lines = File.readlines(testcase_file).map { |line| line.strip } - lines = lines.select { |line| !line.empty? } - testcases = Hash[*(lines)] - testcases.keys.sort.each do |input| - expected = testcases[input] - it "from> #{input}\n to> #{expected}\n" do - @parser.transform(input).should == expected - end - end - end - -end diff --git a/chef/spec/unit/solr_query/solr_http_request_spec.rb b/chef/spec/unit/solr_query/solr_http_request_spec.rb deleted file mode 100644 index c70c347a14..0000000000 --- a/chef/spec/unit/solr_query/solr_http_request_spec.rb +++ /dev/null @@ -1,244 +0,0 @@ -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2011 Opscode, inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -require 'chef/solr_query' -require 'net/http' - -describe Chef::SolrQuery::SolrHTTPRequest do - before do - Chef::Config[:solr_url] = "http://example.com:8983" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@url_prefix, nil) - - @request = Chef::SolrQuery::SolrHTTPRequest.new(:GET, '/solr/select') - end - - it "defaults to using the configured solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://example.com:8983" - end - - it "supports solr_url with a path" do - Chef::Config[:solr_url] = "http://example.com:8983/test" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://example.com:8983/test" - end - - it "updates the Solr URL as you like" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://chunkybacon.org:1234" - end - - it "updates the URL prefix with a path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/something" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/something" - end - - it "removes extra / at the end of solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/extra/" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/extra" - end - - it "creates a Net::HTTP client for the base Solr URL" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - http_client = Chef::SolrQuery::SolrHTTPRequest.http_client - http_client.address.should == "chunkybacon.org" - http_client.port.should == 1234 - end - - it "creates a Net::HTTP client for the base Solr URL ignoring the path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/test" - http_client = Chef::SolrQuery::SolrHTTPRequest.http_client - http_client.address.should == "chunkybacon.org" - http_client.port.should == 1234 - end - - it "defaults url_prefix to /solr if the configured solr_url has no path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/solr" - end - - it "defaults url_prefix to the path from the configured solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/test" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/test" - end - - describe "when configured with the Solr URL" do - before do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when executing a select query" do - before(:each) do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => '{"some": "hash" }' - ) - @solr = Chef::SolrQuery.from_params(:type => 'node', - :q => "hostname:latte") - @params = @solr.to_hash - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when the HTTP call is successful" do - it "should call get to /solr/select with the escaped query" do - txfm_query = "q=content%3Ahostname__%3D__latte" - Net::HTTP::Get.should_receive(:new).with(%r(/solr/select?.+#{txfm_query})) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses Solr's JSON response format" do - Net::HTTP::Get.should_receive(:new).with(%r(wt=json)) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses indent=off to get a compact response" do - Net::HTTP::Get.should_receive(:new).with(%r(indent=off)) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses the filter query to restrict the result set" do - filter_query =@solr.filter_query.gsub('+', '%2B').gsub(':', "%3A").gsub(' ', '+') - Net::HTTP::Get.should_receive(:new).with(/fq=#{Regexp.escape(filter_query)}/) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "returns the evaluated response body" do - res = Chef::SolrQuery::SolrHTTPRequest.select(@params) - res.should == {"some" => "hash" } - end - end - end - - describe "when updating" do - before do - Net::HTTP::Post.stub!(:new).and_return(@http_request) - end - - it "should post to /solr/update" do - @doc = "<xml is the old tldr>" - Net::HTTP::Post.should_receive(:new).with("/solr/update", "Content-Type" => "text/xml").and_return(@http_request) - Chef::SolrQuery::SolrHTTPRequest.update(@doc) - end - - it "should set the body of the request to the stringified doc" do - @http_request.should_receive(:body=).with("foo") - Chef::SolrQuery::SolrHTTPRequest.update(:foo) - end - - it "should send the request to solr" do - @http.should_receive(:request).with(@http_request).and_return(@http_response) - Chef::SolrQuery::SolrHTTPRequest.update(:foo) - end - - end - - describe "when the HTTP call is unsuccessful" do - [Timeout::Error, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL].each do |exception| - it "should rescue, log an error message, and raise a SolrConnectionError encountering exception #{exception}" do - response = mock("Net:HTTPResponse test double", :code => 500, :message => "oops", :class => exception) - @http.should_receive(:request).with(instance_of(Net::HTTP::Get)).and_return(response) - response.should_receive(:error!).and_raise(exception) - Chef::Log.should_receive(:fatal).with("Search Query to Solr failed (#{exception} 500 oops)") - - lambda {@request.run('Search Query to Solr')}.should raise_error(Chef::Exceptions::SolrConnectionError) - end - end - - it "should rescue, log an error message, and raise a SolrConnectionError when encountering exception NoMethodError and net/http closed? bug" do - @no_method_error = NoMethodError.new("undefined method 'closed\?' for nil:NilClass") - @http.should_receive(:request).with(instance_of(Net::HTTP::Get)).and_raise(@no_method_error) - Chef::Log.should_receive(:fatal).with("HTTP Request to Solr failed. Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact http://example.com:8983") - lambda { - @request.run - }.should raise_error(Chef::Exceptions::SolrConnectionError) - end - end - - end - - describe "when configured with the Solr URL with a path" do - before do - Chef::Config[:solr_url] = "http://example.com:8983/test" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@url_prefix, nil) - - @request = Chef::SolrQuery::SolrHTTPRequest.new(:GET, '/solr/select') - - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when executing a select query" do - before(:each) do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => '{"some": "hash" }' - ) - @solr = Chef::SolrQuery.from_params(:type => 'node', - :q => "hostname:latte") - @params = @solr.to_hash - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when the HTTP call is successful" do - it "should call get to /test/select with the escaped query" do - txfm_query = "q=content%3Ahostname__%3D__latte" - Net::HTTP::Get.should_receive(:new).with(%r(/test/select?.+#{txfm_query})) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - end - end - - describe "when updating" do - before do - Net::HTTP::Post.stub!(:new).and_return(@http_request) - end - - it "should post to /test/update" do - @doc = "<xml is the old tldr>" - Net::HTTP::Post.should_receive(:new).with("/test/update", "Content-Type" => "text/xml").and_return(@http_request) - Chef::SolrQuery::SolrHTTPRequest.update(@doc) - end - end - end -end diff --git a/chef/spec/unit/solr_query_spec.rb b/chef/spec/unit/solr_query_spec.rb deleted file mode 100644 index 8b48011713..0000000000 --- a/chef/spec/unit/solr_query_spec.rb +++ /dev/null @@ -1,203 +0,0 @@ -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010, 2011 Opscode, inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -require 'chef/solr_query' -require 'net/http' - -#require 'rspec/mocks' - -describe Chef::SolrQuery do - before do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://example.com:8983" - - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - Net::HTTP::Post.stub!(:new).and_return(@http_request) - Net::HTTP::Get.stub!(:new).and_return(@http_request) - @doc = { "foo" => "bar" } - end - - before(:each) do - @solr = Chef::SolrQuery.new - end - - it "sets filter query params" do - @solr.filter_by(:database => 'chef') - @solr.filter_query.should == "+X_CHEF_database_CHEF_X:chef" - end - - it "filters by type when querying for a builtin type" do - @solr.filter_by_type("node") - @solr.filter_query.should == "+X_CHEF_type_CHEF_X:node" - end - - it "filters by type for data bag items" do - @solr.filter_by_type("users") - @solr.filter_query.split(" ").sort.should == ['+X_CHEF_type_CHEF_X:data_bag_item', '+data_bag:users'] - end - - it "stores the main query" do - @solr.query = "role:prod AND tags:chef-server" - @solr.query.should == "role:prod AND tags:chef-server" - end - - describe "when generating query params for select" do - before(:each) do - @solr = Chef::SolrQuery.from_params(:type => 'node', :q => "hostname:latte") - @params = @solr.to_hash - end - - it "includes the query as q" do - @params[:q].should == "content:hostname__=__latte" - end - - it "sets the response format to json" do - @params[:wt].should == "json" - end - - it "uses indent=off to get a compact response" do - @params[:indent].should == "off" - end - - it "includes the filter query to restrict the result set" do - @params[:fq].should == @solr.filter_query - end - - it "defaults to returning 1000 rows" do - @params[:rows].should == 1000 - end - - it "returns the number of rows requested" do - @solr.params[:rows] = 500 - @solr.to_hash[:rows].should == 500 - end - - it "offsets the row selection if requested" do - @solr.params[:start] = 500 - @solr.to_hash[:start].should == 500 - end - - end - - describe "when querying solr" do - before do - @couchdb = mock("CouchDB Test Double", :couchdb_database => "chunky_bacon") - @couchdb.stub!(:kind_of?).with(Chef::CouchDB).and_return(true) #ugh. - @solr = Chef::SolrQuery.from_params({:type => 'node', :q => "hostname:latte", :start => 10, :rows => 5}, @couchdb) - @docs = [1,2,3,4,5].map { |doc_id| {'X_CHEF_id_CHEF_X' => doc_id} } - @solr_response = {"response" => {"docs" => @docs, "start" => 10, "results" => 123}} - Chef::SolrQuery::SolrHTTPRequest.should_receive(:select).with(@solr.to_hash).and_return(@solr_response) - end - - it "it collects the document ids from the response" do - @solr.object_ids.should == [1,2,3,4,5] - end - - it "does a bulk get of the objects from CouchDB" do - @couchdb.should_receive(:bulk_get).with([1,2,3,4,5]).and_return(%w{obj1 obj2 obj3 obj4 obj5}) - @solr.objects.should == %w{obj1 obj2 obj3 obj4 obj5} - end - - end - - describe "when forcing a Solr commit" do - it "sends valid commit xml to solr" do - Chef::SolrQuery::SolrHTTPRequest.should_receive(:update).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<commit/>\n") - @solr.commit - end - end - - describe "when deleting a database from Solr" do - it "sends a valid delete query to solr and forces a commit" do - Chef::SolrQuery::SolrHTTPRequest.should_receive(:update).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><query>X_CHEF_database_CHEF_X:chef</query></delete>\n") - @solr.should_receive(:commit) - @solr.delete_database("chef") - end - end - - describe "rebuilding the index" do - before do - Chef::Config[:couchdb_database] = "chunky_bacon" - end - - it "deletes the index and commits" do - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all) - Chef::DataBag.stub!(:cdb_list).and_return([]) - @solr.rebuild_index - end - - it "reindexes Chef::ApiClient, Chef::Node, and Chef::Role objects, reporting the results as a hash" do - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.should_receive(:reindex_all).with(Chef::ApiClient).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Environment).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Node).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Role).and_return(true) - Chef::DataBag.stub!(:cdb_list).and_return([]) - - result = @solr.rebuild_index - result["Chef::ApiClient"].should == "success" - result["Chef::Node"].should == "success" - result["Chef::Role"].should == "success" - end - - it "does not reindex Chef::OpenIDRegistration or Chef::WebUIUser objects" do - # hi there. the reason we're specifying this behavior is because these objects - # are not properly indexed in the first place and trying to reindex them - # tickles a bug in our CamelCase to snake_case code. See CHEF-1009. - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all).with(Chef::ApiClient) - @solr.stub!(:reindex_all).with(Chef::Node) - @solr.stub!(:reindex_all).with(Chef::Role) - @solr.should_not_receive(:reindex_all).with(Chef::OpenIDRegistration) - @solr.should_not_receive(:reindex_all).with(Chef::WebUIUser) - Chef::DataBag.stub!(:cdb_list).and_return([]) - - @solr.rebuild_index - end - - it "reindexes databags" do - one_data_item = Chef::DataBagItem.new - one_data_item.raw_data = {"maybe"=>"snakes actually are evil", "id" => "just_sayin"} - two_data_item = Chef::DataBagItem.new - two_data_item.raw_data = {"tone_depth"=>"rumble_fish", "id" => "eff_yes"} - data_bag = Chef::DataBag.new - data_bag.stub!(:list).and_return([one_data_item, two_data_item]) - - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all) - Chef::DataBag.stub!(:cdb_list).and_return([data_bag]) - - data_bag.should_receive(:add_to_index) - one_data_item.should_receive(:add_to_index) - two_data_item.should_receive(:add_to_index) - - @solr.rebuild_index["Chef::DataBag"].should == "success" - end - end -end |