Initial Chef repository

This commit is contained in:
Greg Karékinian
2015-07-21 19:45:23 +02:00
parent 7e5401fc71
commit ee4079fa85
1151 changed files with 185163 additions and 0 deletions

2
cookbooks/chef-solo-search/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/Gemfile.lock
/tests/gemfiles/*.lock

View File

@@ -0,0 +1,9 @@
before_install: gem update --system 1.8.25
before_script: chef-solo -v
rvm:
- 1.9.3
- 1.9.2
- 1.8.7
gemfile:
- tests/gemfiles/Gemfile.11
- tests/gemfiles/Gemfile.10

View File

@@ -0,0 +1,31 @@
==========================
chef-solo-search Changelog
==========================
Version 0.5.1, September 19, 2013
---------------------------------
* Added missing metadata.json file
Version 0.5.0, September 19, 2013
---------------------------------
* Allow node data bag path to be configured
* updated install instructions with focus on Chef 11
* Fixed Treetop dependency such that the omnibus installer is working
* Added proper ruby Gemfile
* use rake as runner for the tests
* Fixed Travis CI builds
Version 0.4.0, March 8, 2013
----------------------------
* Added support for 'chef_environment:_default' to queries.
* Special case for lucene "anything range" [* TO *]
* Added support for ruby bundler
* Added support for Berkshelf
Version 0.3.0, July 27, 2011
----------------------------
First release of chef-solo-search as a self-contained project.
Search functionality has been added to the original concept and the search
extension as well as the data bags extension are shipped in libraries/

View File

@@ -0,0 +1,8 @@
source 'https://rubygems.org'
gem 'chef', '>= 10.4'
gem 'treetop'
gem 'rake'
gem 'ruby-wmi'
gem 'win32-service', :platforms => [:mswin, :mingw]

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,23 @@
========================
chef-solo-search Notices
========================
Developed at edelight GmbH (http://www.edelight-group.com/).
Contributors:
* Arjun Singh
* Brian p o'rourke <bpo@somnambulance.net>
* Chris Roberts <chrisroberts.code@gmail.com>
* Greg Karékinian <greg+github@karekinian.com>
* Jeff Wallace <jeff@tjwallace.ca>
* Michael Glass <me@mike.is>
* Miquel Torres <miquel.torres@edelight.de>
* Markus Korn <markus.korn@edelight.de>
* Matt Gleeson <matt@gleeson.org>
* Patrick Debois <Patrick.Debois@jedi.be>
* Patrick Wyatt <pat@codeofhonor.com>
* Paweł Pacana <pawel.pacana@syswise.eu>
* Seth Chisamore <schisamo@opscode.com>
* Teemu Matilainen <teemu.matilainen@iki.fi>
* Tyler Rick

View File

@@ -0,0 +1,148 @@
# chef-solo-search
[![Build Status](https://travis-ci.org/edelight/chef-solo-search.png?branch=master)](https://travis-ci.org/edelight/chef-solo-search)
Chef-solo-search is a cookbook library that adds data bag search powers
to Chef Solo. Data bag support was added to Chef Solo by Chef 0.10.4.
Please see *Supported queries* for a list of query types which are supported.
## Requirements
* ruby >= 1.8
* ruby-chef >= 0.10.4
## Installation
Install this cookbook into your Chef repository using your favorite cookbook
management tool
([Librarian](https://github.com/applicationsonline/librarian-chef),
[Berkshelf](https://github.com/RiotGames/berkshelf), knife...).
In Chef 11, you must either add this to the run list of the nodes where it's used or include it as a dependency in the recipes that use it. [See changes in Chef 11.](http://docs.opscode.com/breaking_changes_chef_11.html#non-recipe-file-evaluation-includes-dependencies)
Now you have to make sure chef-solo knows about data bags, therefore add
data_bag_path "<node_work_path>/data_bags"
to the config file of chef-solo (defaults to /etc/chef/solo.rb).
The same for your roles, add
role_path "<node_work_path>/roles"
## Supported queries
The search methods supports a basic sub-set of the lucene query language.
Sample supported queries are:
### General queries:
search(:users, "*:*")
search(:users)
search(:users, nil)
getting all items in ':users'
search(:users, "username:*")
search(:users, "username:[* TO *]")
getting all items from ':users' which have a 'username' attribute
search(:users, "(NOT username:*)")
search(:users, "(NOT username:[* TO *])")
getting all items from ':users' which don't have a 'username' attribute
### Queries on attributes with string values:
search(:users, "username:speedy")
getting all items from ':users' with username equals 'speedy'
search(:users, "NOT username:speedy")
getting all items from ':users' with username is unequal to 'speedy'
search(:users, "username:spe*")
getting all items which 'username'-value begins with 'spe'
### Queries on attributes with array values:
search(:users, "children:tom")
getting all items which 'children' attribute contains 'tom'
search(:users, "children:t*")
getting all items which have at least one element in 'children'
which starts with 't'
### Queries on attributes with boolean values:
search(:users, "married:true")
### Queries in attributes with integer values:
search(:users, "age:35")
### OR conditions in queries:
search(:users, "age:42 OR age:22")
### AND conditions in queries:
search(:users, "married:true AND age:35")
### NOT condition in queries:
search(:users, "children:tom NOT gender:female")
### More complex queries:
search(:users, "children:tom NOT gender:female AND age:42")
## Supported Objects
The search methods have support for 'roles', 'nodes' and 'databags'.
### Roles
You can use the standard role objects in json form and put them into your role path
{
"name": "monitoring",
"default_attributes": { },
"override_attributes": { },
"json_class": "Chef::Role",
"description": "This is just a monitoring role, no big deal.",
"run_list": [
],
"chef_type": "role"
### Nodes
Nodes are injected through a databag called 'node'. Create a databag called 'node' and put your json files there
You can use the standard node objects in json form.
{
"id": "vagrant",
"name": "vagrant-vm",
"chef_environment": "_default",
"json_class": "Chef::Node",
"automatic": {
"hostname": "vagrant.vm",
"os": "centos"
},
"normal": {
},
"chef_type": "node",
"default": {
},
"override": {
},
"run_list": [
"role[monitoring]"
]
}
### Databags
You can use the standard databag objects in json form
{
"id": "my-ssh",
"hostgroup_name": "all",
"command_line": "$USER1$/check_ssh $HOSTADDRESS$"
}
## Running tests
Running tests is as simple as:
% rake test

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env rake
require "rake/testtask"
Rake::TestTask.new do |t|
t.pattern = "tests/test_*.rb"
t.libs = %w(libraries)
end
desc "Run tests"
task :default => :test

View File

@@ -0,0 +1,74 @@
#
# Copyright 2011, edelight GmbH
#
# Authors:
# Markus Korn <markus.korn@edelight.de>
# Seth Chisamore <schisamo@opscode.com>
#
# 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.
#
if Chef::Config[:solo]
# add currrent dir to load path
$: << File.dirname(__FILE__)
# All chef/solr_query/* classes were removed in Chef 11; Load vendored copy
# that ships with this cookbook
$: << File.expand_path("vendor", File.dirname(__FILE__)) if Chef::VERSION.to_i >= 11
# Ensure the treetop gem is installed and available
begin
require 'treetop'
rescue LoadError
run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
chef_gem = Chef::Resource::ChefGem.new("treetop", run_context)
chef_gem.version('>= 1.4')
chef_gem.run_action(:install)
end
require 'search/overrides'
require 'search/parser'
module Search; class Helper; end; end
# The search and data_bag related methods moved form `Chef::Mixin::Language`
# to `Chef::DSL::DataQuery` in Chef 11.
if Chef::VERSION.to_i >= 11
module Chef::DSL::DataQuery
def self.included(base)
base.send(:include, Search::Overrides)
end
end
Search::Helper.send(:include, Chef::DSL::DataQuery)
else
module Chef::Mixin::Language
def self.included(base)
base.send(:include, Search::Overrides)
end
end
Search::Helper.send(:include, Chef::Mixin::Language)
end
class Chef
class Search
class Query
def initialize(*args)
end
def search(*args, &block)
::Search::Helper.new.search(*args, &block)
end
end
end
end
end

View File

@@ -0,0 +1,100 @@
#
# Copyright 2011, edelight GmbH
#
# Authors:
# Markus Korn <markus.korn@edelight.de>
# Seth Chisamore <schisamo@opscode.com>
#
# 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.
#
module Search
module Overrides
# Overwrite the search method of recipes to operate locally by using
# data found in data_bags.
# Only very basic lucene syntax is supported and also sorting the result
# is not implemented, if this search method does not support a given query
# an exception is raised.
# This search() method returns a block iterator or an Array, depending
# on how this method is called.
def search(obj, query=nil, sort=nil, start=0, rows=1000, &block)
if !sort.nil?
raise "Sorting search results is not supported"
end
_query = Query.parse(query)
if _query.nil?
raise "Query #{query} is not supported"
end
_result = []
case obj
when :node
nodes = search_nodes(_query, start, rows, &block)
_result += nodes
when :role
roles = search_roles(_query, start, rows, &block)
_result += roles
else
bags = search_data_bag(_query, obj, start, rows, &block)
_result += bags
end
if block_given?
pos = 0
while (pos >= start and pos < (start + rows) and pos < _result.size)
yield _result[pos]
pos += 1
end
else
return _result.slice(start, rows)
end
end
def search_nodes(_query, start, rows, &block)
_result = []
node_path = Chef::Config[:nodes_path] || File.join(Chef::Config[:data_bag_path], "node")
Dir.glob(File.join(node_path, "*.json")).map do |f|
# parse and hashify the node
node = Chef::JSONCompat.from_json(IO.read(f))
if _query.match(node.to_hash)
_result << node
end
end
return _result
end
def search_roles(_query, start, rows, &block)
_result = []
Dir.glob(File.join(Chef::Config[:role_path], "*.json")).map do |f|
# parse and hashify the role
role = Chef::JSONCompat.from_json(IO.read(f))
if _query.match(role.to_hash)
_result << role
end
end
return _result
end
def search_data_bag(_query, bag_name, start, rows, &block)
_result = []
data_bag(bag_name.to_s).each do |bag_item_id|
bag_item = data_bag_item(bag_name.to_s, bag_item_id)
if _query.match(bag_item)
_result << bag_item
end
end
return _result
end
end
end

View File

@@ -0,0 +1,222 @@
#
# Copyright 2011, edelight GmbH
#
# Authors:
# Markus Korn <markus.korn@edelight.de>
#
# 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/solr_query/query_transform'
# mock QueryTransform such that we can access the location of the lucene grammar
class Chef
class SolrQuery
class QueryTransform
def self.base_path
class_variable_get(:@@base_path)
end
end
end
end
def build_flat_hash(hsh, prefix="")
result = {}
hsh.each_pair do |key, value|
if value.kind_of?(Hash)
result.merge!(build_flat_hash(value, "#{prefix}#{key}_"))
else
result[prefix+key] = value
end
end
result
end
module Lucene
class Term < Treetop::Runtime::SyntaxNode
# compares a query value and a value, tailing '*'-wildcards are handled correctly.
# Value can either be a string or an array, all other objects are converted
# to a string and than checked.
def match( value )
if value.is_a?(Array)
value.any?{ |x| self.match(x) }
else
File.fnmatch(self.text_value, value.to_s)
end
end
end
class Field < Treetop::Runtime::SyntaxNode
# simple field -> value matches, supporting tailing '*'-wildcards in keys
# as well as in values
def match( item )
keys = self.elements[0].match(item)
if keys.nil?
false
else
keys.any?{ |key| self.elements[1].match(item[key]) }
end
end
end
# we don't support range matches
# range of integers would be easy to implement
# but string ranges are hard
class FiledRange < Treetop::Runtime::SyntaxNode
end
# we handle '[* TO *]' as a special case since it is common in
# cookbooks for matching the existence of keys
class InclFieldRange
def match(item)
field = self.elements[0].text_value
range_start = self.elements[1].transform
range_end = self.elements[2].transform
if range_start == "*" and range_end == "*"
!!item[field]
else
raise "Ranges not really supported yet"
end
end
end
class ExclFieldRange < FieldRange
end
class RangeValue < Treetop::Runtime::SyntaxNode
end
class FieldName < Treetop::Runtime::SyntaxNode
def match( item )
if self.text_value.count("_") > 0
item.merge!(build_flat_hash(item))
end
if self.text_value.end_with?("*")
part = self.text_value.chomp("*")
item.keys.collect{ |key| key.start_with?(part)? key: nil}.compact
else
if item.has_key?(self.text_value)
[self.text_value,]
else
nil
end
end
end
end
class Body < Treetop::Runtime::SyntaxNode
def match( item )
self.elements[0].match( item )
end
end
class Group < Treetop::Runtime::SyntaxNode
def match( item )
self.elements[0].match(item)
end
end
class BinaryOp < Treetop::Runtime::SyntaxNode
def match( item )
self.elements[1].match(
self.elements[0].match(item),
self.elements[2].match(item)
)
end
end
class OrOperator < Treetop::Runtime::SyntaxNode
def match( cond1, cond2 )
cond1 or cond2
end
end
class AndOperator < Treetop::Runtime::SyntaxNode
def match( cond1, cond2 )
cond1 and cond2
end
end
# we don't support fuzzy string matching
class FuzzyOp < Treetop::Runtime::SyntaxNode
end
class BoostOp < Treetop::Runtime::SyntaxNode
end
class FuzzyParam < Treetop::Runtime::SyntaxNode
end
class UnaryOp < Treetop::Runtime::SyntaxNode
def match( item )
self.elements[0].match(
self.elements[1].match(item)
)
end
end
class NotOperator < Treetop::Runtime::SyntaxNode
def match( cond )
not cond
end
end
class RequiredOperator < Treetop::Runtime::SyntaxNode
end
class ProhibitedOperator < Treetop::Runtime::SyntaxNode
end
class Phrase < Treetop::Runtime::SyntaxNode
# a quoted ::Term
def match( value )
self.elements[0].match(value)
end
end
end
class Query
# initialize the parser by using the grammar shipped with chef
@@grammar = File.join(Chef::SolrQuery::QueryTransform.base_path, "lucene.treetop")
Treetop.load(@@grammar)
@@parser = LuceneParser.new
def self.parse(data)
# parse the query into a query tree
if data.nil?
data = "*:*"
end
tree = @@parser.parse(data)
if tree.nil?
msg = "Parse error at offset: #{@@parser.index}\n"
msg += "Reason: #{@@parser.failure_reason}"
raise "Query #{data} is not supported: #{msg}"
end
self.clean_tree(tree)
tree
end
private
def self.clean_tree(root_node)
# remove all SyntaxNode elements from the tree, we don't need them as
# the related ruby class already knowns what to do.
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

View File

@@ -0,0 +1,150 @@
#
# 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

View File

@@ -0,0 +1,285 @@
#
# 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

View File

@@ -0,0 +1,65 @@
#
# 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

View File

@@ -0,0 +1,35 @@
{
"license": "Apache 2.0",
"replacing": {
},
"suggestions": {
},
"long_description": "# chef-solo-search\n\n[![Build Status](https://travis-ci.org/edelight/chef-solo-search.png?branch=master)](https://travis-ci.org/edelight/chef-solo-search)\n\nChef-solo-search is a cookbook library that adds data bag search powers\nto Chef Solo. Data bag support was added to Chef Solo by Chef 0.10.4.\nPlease see *Supported queries* for a list of query types which are supported.\n\n## Requirements\n\n * ruby >= 1.8\n * ruby-chef >= 0.10.4\n\n## Installation\n\nInstall this cookbook into your Chef repository using your favorite cookbook\nmanagement tool\n([Librarian](https://github.com/applicationsonline/librarian-chef),\n[Berkshelf](https://github.com/RiotGames/berkshelf), knife...).\n\nIn Chef 11, you must either add this to the run list of the nodes where it's used or include it as a dependency in the recipes that use it. [See changes in Chef 11.](http://docs.opscode.com/breaking_changes_chef_11.html#non-recipe-file-evaluation-includes-dependencies)\n\nNow you have to make sure chef-solo knows about data bags, therefore add\n\n data_bag_path \"<node_work_path>/data_bags\"\n\nto the config file of chef-solo (defaults to /etc/chef/solo.rb).\n\nThe same for your roles, add\n\n role_path \"<node_work_path>/roles\"\n\n## Supported queries\n\nThe search methods supports a basic sub-set of the lucene query language.\nSample supported queries are:\n\n### General queries:\n\n search(:users, \"*:*\")\n search(:users)\n search(:users, nil)\n getting all items in ':users'\n search(:users, \"username:*\")\n search(:users, \"username:[* TO *]\")\n getting all items from ':users' which have a 'username' attribute\n search(:users, \"(NOT username:*)\")\n search(:users, \"(NOT username:[* TO *])\")\n getting all items from ':users' which don't have a 'username' attribute\n\n### Queries on attributes with string values:\n\n search(:users, \"username:speedy\")\n getting all items from ':users' with username equals 'speedy'\n search(:users, \"NOT username:speedy\")\n getting all items from ':users' with username is unequal to 'speedy'\n search(:users, \"username:spe*\")\n getting all items which 'username'-value begins with 'spe'\n\n### Queries on attributes with array values:\n\n search(:users, \"children:tom\")\n getting all items which 'children' attribute contains 'tom'\n search(:users, \"children:t*\")\n getting all items which have at least one element in 'children'\n which starts with 't'\n\n### Queries on attributes with boolean values:\n\n search(:users, \"married:true\")\n\n### Queries in attributes with integer values:\n\n search(:users, \"age:35\")\n\n### OR conditions in queries:\n\n search(:users, \"age:42 OR age:22\")\n\n### AND conditions in queries:\n\n search(:users, \"married:true AND age:35\")\n\n### NOT condition in queries:\n\n search(:users, \"children:tom NOT gender:female\")\n\n### More complex queries:\n\n search(:users, \"children:tom NOT gender:female AND age:42\")\n\n\n## Supported Objects\nThe search methods have support for 'roles', 'nodes' and 'databags'.\n\n### Roles\nYou can use the standard role objects in json form and put them into your role path\n\n {\n \"name\": \"monitoring\",\n \"default_attributes\": { },\n \"override_attributes\": { },\n \"json_class\": \"Chef::Role\",\n \"description\": \"This is just a monitoring role, no big deal.\",\n \"run_list\": [\n ],\n \"chef_type\": \"role\"\n\n\n### Nodes\nNodes are injected through a databag called 'node'. Create a databag called 'node' and put your json files there\nYou can use the standard node objects in json form.\n\n {\n \"id\": \"vagrant\",\n \"name\": \"vagrant-vm\",\n \"chef_environment\": \"_default\",\n \"json_class\": \"Chef::Node\",\n \"automatic\": {\n \"hostname\": \"vagrant.vm\",\n \"os\": \"centos\"\n },\n \"normal\": {\n },\n \"chef_type\": \"node\",\n \"default\": {\n },\n \"override\": {\n },\n \"run_list\": [\n \"role[monitoring]\"\n ]\n }\n\n### Databags\nYou can use the standard databag objects in json form\n\n {\n \"id\": \"my-ssh\",\n \"hostgroup_name\": \"all\",\n \"command_line\": \"$USER1$/check_ssh $HOSTADDRESS$\"\n }\n\n## Running tests\n\nRunning tests is as simple as:\n\n % rake test\n",
"attributes": {
},
"providing": {
},
"maintainer_email": "markus.korn@edelight.de",
"groupings": {
},
"conflicting": {
},
"description": "Data bag search for Chef Solo",
"name": "chef-solo-search",
"version": "0.5.1",
"dependencies": {
},
"platforms": {
"freebsd": ">= 0.0.0",
"fedora": ">= 0.0.0",
"debian": ">= 0.0.0",
"ubuntu": ">= 0.0.0",
"centos": ">= 0.0.0",
"redhat": ">= 0.0.0"
},
"maintainer": "edelight GmbH",
"recipes": {
},
"recommendations": {
}
}

View File

@@ -0,0 +1,11 @@
name "chef-solo-search"
maintainer "edelight GmbH"
maintainer_email "markus.korn@edelight.de"
license "Apache 2.0"
description "Data bag search for Chef Solo"
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version "0.5.1"
%w{ ubuntu debian redhat centos fedora freebsd}.each do |os|
supports os
end

View File

@@ -0,0 +1,2 @@
# This file is intentionally blank, which allows this
# entire repository to be used as a cookbook

View File

@@ -0,0 +1,10 @@
{
"id": "alpha",
"name": "alpha.example.com",
"json_class": "Chef::Node",
"run_list": ["role[test_server]"],
"chef_environment": "default",
"automatic": {
"hostname": "alpha.example.com"
}
}

View File

@@ -0,0 +1,10 @@
{
"id": "beta",
"name": "beta.example.com",
"json_class": "Chef::Node",
"run_list": ["role[test_server]","role[beta_server]"],
"chef_environment": "default",
"automatic": {
"hostname": "beta.example.com"
}
}

View File

@@ -0,0 +1,7 @@
{
"id": "without_json_class",
"name": "wjc.example.com",
"chef_environment": "default",
"hostname": "wjc.example.com",
"run_list": ["role[test_server]"]
}

View File

@@ -0,0 +1,8 @@
{
"id": "jerry",
"username": "speedy",
"age": 22,
"gender": "male",
"married": true,
"color": "green"
}

View File

@@ -0,0 +1,10 @@
{
"id": "lea",
"username": "lea",
"age": 35,
"gender": "female",
"married": true,
"children": ["tom"],
"tag": "tag::test",
"tags": ["tag::first", "tag::second"]
}

View File

@@ -0,0 +1,13 @@
{
"id": "mike",
"username": "mike the hammer",
"age": 42,
"gender": "male",
"married": true,
"children": ["tom", "jerry"],
"address": {
"street": {
"floor": 1
}
}
}

View File

@@ -0,0 +1,10 @@
{
"id": "tom",
"username": "tom von homme",
"age": 13,
"gender": "male",
"married": false,
"address": {
"street": "wilhelmsstrasse"
}
}

View File

@@ -0,0 +1,4 @@
source "https://rubygems.org"
gem "chef", "~> 10.0"
gem "rake"

View File

@@ -0,0 +1,5 @@
source "https://rubygems.org"
gem "chef", "~> 11.0"
gem "rake"
gem "treetop"

View File

@@ -0,0 +1,45 @@
#
# Copyright 2011, edelight GmbH
#
# Authors:
# Markus Korn <markus.korn@edelight.de>
#
# 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 "test/unit"
require "chef"
# mocking chef such that it thinks it's running as chef-solo and knows about
# the location of the data_bag
Chef::Config[:solo] = true
Chef::Config[:data_bag_path] = "tests/data/data_bags"
def data_bag_item(bag, item)
# wrapper around creating a new Recipe instance and calling data_bag on it
node = Chef::Node.new()
events = Chef::EventDispatch::Dispatcher.new
cookbooks = Chef::CookbookCollection.new()
run_context = Chef::RunContext.new(node, cookbooks, events)
return Chef::Recipe.new("test_cookbook", "test_recipe", run_context).data_bag_item(bag, item)
end
class TestDataBags < Test::Unit::TestCase
def test_data_bag
item = data_bag_item("users", "mike")
assert_equal item["age"], 42
assert_equal item[:age], nil #upstream code for chef-solo does not use mashes
end
end

View File

@@ -0,0 +1,244 @@
#
# Copyright 2011, edelight GmbH
#
# Authors:
# Markus Korn <markus.korn@edelight.de>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require "rubygems"
require "test/unit"
require "chef"
# mocking chef such that it thinks it's running as chef-solo and knows about
# the location of the data_bag
Chef::Config[:solo] = true
Chef::Config[:data_bag_path] = "#{File.dirname(__FILE__)}/data/data_bags"
# load the extension
require File.expand_path('../../libraries/search', __FILE__)
module SearchDbTests
def test_search_all
# try to get data of all users
nodes = search(:users, "*:*")
assert_equal nodes.length, 4
nodes = search(:users)
assert_equal nodes.length, 4
nodes = search(:users, nil)
assert_equal nodes.length, 4
end
def test_search_exact_match
nodes = search(:users, "username:speedy")
assert_equal nodes.length, 1
assert_equal nodes[0]["username"], "speedy"
end
def test_get_all_with_field
nodes = search(:users, "username:*")
assert nodes.length > 0
assert nodes.all?{|x| !x["username"].nil?}
end
def test_get_all_without_field
nodes = search(:users, "(NOT username:*)")
assert nodes.length == 0
nodes = search(:users, "(NOT color:*)")
assert nodes.length == 3
assert nodes.all?{|x| x["color"].nil?}
end
def test_get_all_but_speedy
nodes = search(:users, "NOT username:speedy")
assert nodes.length > 0
assert nodes.all?{|x| x["username"] != "speedy"}
end
def test_array_includes
nodes = search(:users, "children:tom")
assert nodes.length == 2
assert nodes.all?{ |x| x["children"].include?("tom") }
nodes = search(:users, "children:jerry")
assert nodes.length == 1
assert nodes.all?{ |x| x["children"].include?("jerry") }
end
def test_boolean
nodes = search(:users, "married:true")
assert nodes.length == 3
assert nodes.all?{ |x| x["married"] == true }
nodes = search(:users, "married:false")
assert nodes.length == 1
assert nodes[0]["married"] == false
end
def test_integer
nodes = search(:users, "age:35")
assert nodes.length == 1
assert nodes[0]["age"] == 35
end
def test_AND_condition
nodes = search(:users, "married:true AND age:35")
assert nodes.length == 1
assert nodes[0]["username"] == "lea"
end
def test_OR_condition
nodes = search(:users, "age:42 OR age:22")
assert nodes.length == 2
end
def test_NOT_condition
nodes = search(:users, "children:tom AND (NOT gender:female)")
assert nodes.length == 1
nodes = search(:users, "children:tom AND (NOT gender:female) AND age:42")
assert nodes.length == 1
nodes = search(:users, "children:tom AND (NOT gender:female) AND (NOT age:42)")
assert nodes.length == 0
end
def test_any_value
nodes = search(:users, "children:*")
assert nodes.length == 2
end
def test_any_value_lucene_range
nodes = search(:users, "address:[* TO *]")
assert nodes.length == 2
end
def test_general_lucene_range_fails
assert_raises RuntimeError do
nodes = search(:users, "children:[aaa TO zzz]")
end
end
def test_block_usage
# bracket syntax
result = []
search(:users, "*:*") {|x| result << x["id"]}
assert result.length == 4
# do...end syntax
result = []
search(:users) do |x|
result << x["id"]
end
assert result.length == 4
end
def test_check_escaped_chars
nodes = search(:users, 'tag:tag\:\:test')
assert nodes.length == 1
nodes = search(:users, "tag:tag\\:\\:test")
assert nodes.length == 1
nodes = search(:users, 'tags:tag\:\:first')
assert nodes.length == 1
nodes = search(:users, "tags:tag\\:\\:first")
assert nodes.length == 1
nodes = search(:users, 'tags:tag\:\:*')
assert nodes.length == 1
nodes = search(:users, "tags:tag\\:\\:*")
assert nodes.length == 1
end
def test_wildcards
nodes = search(:users, "gender:f??ale")
assert nodes.length == 1
nodes = search(:users, "username:spee?y")
assert nodes.length == 1
nodes = search(:users, "username:spee*")
assert nodes.length == 1
end
def test_empty_field_value
assert_raise(RuntimeError) {
search(:users, "gender:#{nil} AND age:35")
}
assert_raise(RuntimeError) {
search(:users, "gender: AND age:35")
}
assert_raise(RuntimeError) {
search(:users, "gender:\"\" AND age:35")
}
end
def test_OR_group
nodes = search(:users, "id:(mike OR tom)")
assert nodes.length == 2
end
def test_nested_fieldnames
nodes = search(:users, "address_street:wilhelmsstrasse")
assert nodes.length == 1
nodes = search(:users, "address_street_floor:1")
assert nodes.length == 1
end
end
module SearchNodeTests
def test_list_nodes
nodes = search(:node)
assert_equal Chef::Node, nodes.find{ |n| n["hostname"] == "alpha.example.com" }.class
assert_equal Chef::Node, nodes.find{ |n| n["hostname"] == "beta.example.com" }.class
assert_equal Hash, nodes.find{ |n| n["hostname"] == "wjc.example.com" }.class
assert_equal 3, nodes.length
end
def test_search_node_with_wide_filter
nodes = search(:node, "role:test_server AND chef_environment:default")
assert_equal 2, nodes.length
end
def test_search_node_with_narrow_filter
nodes = search(:node, "role:beta_server")
assert_equal 1, nodes.length
end
def test_search_node_with_attr_filter
nodes = search(:node, "hostname:beta.example.com")
assert_equal 1, nodes.length
end
def test_search_node_without_json_class
nodes = search(:node, "chef_environment:default")
assert_equal 3, nodes.length
end
end
class TestImplicitSearchDB < Test::Unit::TestCase
include SearchDbTests
include SearchNodeTests
def search(*args, &block)
# wrapper around creating a new Recipe instance and calling search on it
node = Chef::Node.new()
cookbooks = Chef::CookbookCollection.new()
run_context = Chef::RunContext.new(node, cookbooks, nil)
return Chef::Recipe.new("test_cookbook", "test_recipe", run_context).search(*args, &block)
end
end
class TestExplicitSearchDB < Test::Unit::TestCase
include SearchDbTests
include SearchNodeTests
def search(*args, &block)
Chef::Search::Query.new.search(*args, &block)
end
end