chef-zero 1.4.0.alpha-x86-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +201 -0
- data/README.md +145 -0
- data/Rakefile +11 -0
- data/bin/chef-zero +43 -0
- data/lib/chef_zero.rb +7 -0
- data/lib/chef_zero/cookbook_data.rb +223 -0
- data/lib/chef_zero/data_normalizer.rb +142 -0
- data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -0
- data/lib/chef_zero/data_store/data_error.rb +31 -0
- data/lib/chef_zero/data_store/data_not_found_error.rb +29 -0
- data/lib/chef_zero/data_store/memory_store.rb +167 -0
- data/lib/chef_zero/endpoints/actor_endpoint.rb +68 -0
- data/lib/chef_zero/endpoints/actors_endpoint.rb +32 -0
- data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +25 -0
- data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -0
- data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +110 -0
- data/lib/chef_zero/endpoints/cookbooks_base.rb +65 -0
- data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -0
- data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -0
- data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -0
- data/lib/chef_zero/endpoints/data_bags_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -0
- data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +109 -0
- data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -0
- data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -0
- data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -0
- data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/node_endpoint.rb +17 -0
- data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -0
- data/lib/chef_zero/endpoints/principal_endpoint.rb +30 -0
- data/lib/chef_zero/endpoints/rest_list_endpoint.rb +40 -0
- data/lib/chef_zero/endpoints/rest_object_endpoint.rb +61 -0
- data/lib/chef_zero/endpoints/role_endpoint.rb +16 -0
- data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -0
- data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -0
- data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +51 -0
- data/lib/chef_zero/endpoints/search_endpoint.rb +188 -0
- data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -0
- data/lib/chef_zero/log.rb +7 -0
- data/lib/chef_zero/rest_base.rb +133 -0
- data/lib/chef_zero/rest_error_response.rb +11 -0
- data/lib/chef_zero/rest_request.rb +56 -0
- data/lib/chef_zero/rest_router.rb +44 -0
- data/lib/chef_zero/rspec.rb +107 -0
- data/lib/chef_zero/server.rb +309 -0
- data/lib/chef_zero/solr/query/binary_operator.rb +53 -0
- data/lib/chef_zero/solr/query/phrase.rb +23 -0
- data/lib/chef_zero/solr/query/range_query.rb +34 -0
- data/lib/chef_zero/solr/query/regexpable_query.rb +29 -0
- data/lib/chef_zero/solr/query/subquery.rb +35 -0
- data/lib/chef_zero/solr/query/term.rb +45 -0
- data/lib/chef_zero/solr/query/unary_operator.rb +43 -0
- data/lib/chef_zero/solr/solr_doc.rb +62 -0
- data/lib/chef_zero/solr/solr_parser.rb +193 -0
- data/lib/chef_zero/version.rb +3 -0
- data/spec/run.rb +25 -0
- data/spec/support/pedant.rb +117 -0
- data/spec/support/stickywicket.pem +27 -0
- metadata +204 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'chef_zero/solr/query/regexpable_query'
|
2
|
+
|
3
|
+
module ChefZero
|
4
|
+
module Solr
|
5
|
+
module Query
|
6
|
+
class Phrase < RegexpableQuery
|
7
|
+
def initialize(terms)
|
8
|
+
# Phrase is terms separated by whitespace
|
9
|
+
if terms.size == 0 && terms[0].literal_string
|
10
|
+
literal_string = terms[0].literal_string
|
11
|
+
else
|
12
|
+
literal_string = nil
|
13
|
+
end
|
14
|
+
super(terms.map { |term| term.regexp_string }.join("#{NON_WORD_CHARACTER}+"), literal_string)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Phrase(\"#{@regexp_string}\")"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class RangeQuery
|
5
|
+
def initialize(from, to, from_inclusive, to_inclusive)
|
6
|
+
@from = from
|
7
|
+
@to = to
|
8
|
+
@from_inclusive = from_inclusive
|
9
|
+
@to_inclusive = to_inclusive
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{@from_inclusive ? '[' : '{'}#{@from} TO #{@to}#{@to_inclusive ? '[' : '{'}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(key, value)
|
17
|
+
case @from <=> value
|
18
|
+
when -1
|
19
|
+
return false
|
20
|
+
when 0
|
21
|
+
return false if !@from_inclusive
|
22
|
+
end
|
23
|
+
case @to <=> value
|
24
|
+
when 1
|
25
|
+
return false
|
26
|
+
when 0
|
27
|
+
return false if !@to_inclusive
|
28
|
+
end
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class RegexpableQuery
|
5
|
+
def initialize(regexp_string, literal_string)
|
6
|
+
@regexp_string = regexp_string
|
7
|
+
# Surround the regexp with word boundaries
|
8
|
+
@regexp = Regexp.new("(^|#{NON_WORD_CHARACTER})#{regexp_string}($|#{NON_WORD_CHARACTER})", true)
|
9
|
+
@literal_string = literal_string
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :literal_string
|
13
|
+
attr_reader :regexp_string
|
14
|
+
attr_reader :regexp
|
15
|
+
|
16
|
+
def matches_doc?(doc)
|
17
|
+
value = doc[DEFAULT_FIELD]
|
18
|
+
return value ? matches_values?([value]) : false
|
19
|
+
end
|
20
|
+
def matches_values?(values)
|
21
|
+
values.any? { |value| !@regexp.match(value).nil? }
|
22
|
+
end
|
23
|
+
|
24
|
+
WORD_CHARACTER = "[A-Za-z0-9@._':]"
|
25
|
+
NON_WORD_CHARACTER = "[^A-Za-z0-9@._':]"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class Subquery
|
5
|
+
def initialize(subquery)
|
6
|
+
@subquery = subquery
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
"(#{@subquery})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def literal_string
|
14
|
+
subquery.literal_string
|
15
|
+
end
|
16
|
+
|
17
|
+
def regexp
|
18
|
+
subquery.regexp
|
19
|
+
end
|
20
|
+
|
21
|
+
def regexp_string
|
22
|
+
subquery.regexp_string
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches_doc?(doc)
|
26
|
+
subquery.matches_doc?(doc)
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches_values?(values)
|
30
|
+
subquery.matches_values?(values)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'chef_zero/solr/query/regexpable_query'
|
2
|
+
|
3
|
+
module ChefZero
|
4
|
+
module Solr
|
5
|
+
module Query
|
6
|
+
class Term < RegexpableQuery
|
7
|
+
def initialize(term)
|
8
|
+
# Get rid of escape characters, turn * and ? into .* and . for regex, and
|
9
|
+
# escape everything that needs escaping
|
10
|
+
literal_string = ""
|
11
|
+
regexp_string = ""
|
12
|
+
index = 0
|
13
|
+
while index < term.length
|
14
|
+
if term[index] == '*'
|
15
|
+
regexp_string << "#{WORD_CHARACTER}*"
|
16
|
+
literal_string = nil
|
17
|
+
index += 1
|
18
|
+
elsif term[index] == '?'
|
19
|
+
regexp_string << WORD_CHARACTER
|
20
|
+
literal_string = nil
|
21
|
+
index += 1
|
22
|
+
elsif term[index] == '~'
|
23
|
+
raise "~ unsupported"
|
24
|
+
else
|
25
|
+
if term[index] == '\\'
|
26
|
+
index = index+1
|
27
|
+
if index >= term.length
|
28
|
+
raise "Backslash at end of string '#{term}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
literal_string << term[index] if literal_string
|
32
|
+
regexp_string << Regexp.escape(term[index])
|
33
|
+
index += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
super(regexp_string, literal_string)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"Term(#{regexp_string})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class UnaryOperator
|
5
|
+
def initialize(operator, operand)
|
6
|
+
@operator = operator
|
7
|
+
@operand = operand
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"#{operator} #{operand}"
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :operator
|
15
|
+
attr_reader :operand
|
16
|
+
|
17
|
+
def matches_doc?(doc)
|
18
|
+
case @operator
|
19
|
+
when '-'
|
20
|
+
when 'NOT'
|
21
|
+
!operand.matches_doc?(doc)
|
22
|
+
when '+'
|
23
|
+
# TODO This operator uses relevance to eliminate other, unrelated
|
24
|
+
# expressions. +a OR b means "if it has b but not a, don't return it"
|
25
|
+
raise "+ not supported yet, because it is hard."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches_values?(values)
|
30
|
+
case @operator
|
31
|
+
when '-'
|
32
|
+
when 'NOT'
|
33
|
+
!operand.matches_values?(values)
|
34
|
+
when '+'
|
35
|
+
# TODO This operator uses relevance to eliminate other, unrelated
|
36
|
+
# expressions. +a OR b means "if it has b but not a, don't return it"
|
37
|
+
raise "+ not supported yet, because it is hard."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
# This does what expander does, flattening the json doc into keys and values
|
4
|
+
# so that solr can search them.
|
5
|
+
class SolrDoc
|
6
|
+
def initialize(json, id)
|
7
|
+
@json = json
|
8
|
+
@id = id
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
values = matching_values { |match_key| match_key == key }
|
13
|
+
values[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def matching_values(&block)
|
17
|
+
result = {}
|
18
|
+
key_values(nil, @json) do |key, value|
|
19
|
+
if block.call(key)
|
20
|
+
if result.has_key?(key)
|
21
|
+
result[key] << value.to_s
|
22
|
+
else
|
23
|
+
result[key] = value.to_s.clone
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
# Handle manufactured value(s)
|
28
|
+
if block.call('X_CHEF_id_CHEF_X')
|
29
|
+
if result.has_key?('X_CHEF_id_CHEF_X')
|
30
|
+
result['X_CHEF_id_CHEF_X'] << @id.to_s
|
31
|
+
else
|
32
|
+
result['X_CHEF_id_CHEF_X'] = @id.to_s.clone
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
result.values
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def key_values(key_so_far, value, &block)
|
42
|
+
if value.is_a?(Hash)
|
43
|
+
value.each_pair do |child_key, child_value|
|
44
|
+
block.call(child_key, child_value.to_s)
|
45
|
+
if key_so_far
|
46
|
+
new_key = "#{key_so_far}_#{child_key}"
|
47
|
+
key_values(new_key, child_value, &block)
|
48
|
+
else
|
49
|
+
key_values(child_key, child_value, &block) if child_value.is_a?(Hash) || child_value.is_a?(Array)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
elsif value.is_a?(Array)
|
53
|
+
value.each do |child_value|
|
54
|
+
key_values(key_so_far, child_value, &block)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
block.call(key_so_far || 'text', value.to_s)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'chef_zero/solr/query/binary_operator'
|
2
|
+
require 'chef_zero/solr/query/unary_operator'
|
3
|
+
require 'chef_zero/solr/query/term'
|
4
|
+
require 'chef_zero/solr/query/phrase'
|
5
|
+
require 'chef_zero/solr/query/range_query'
|
6
|
+
require 'chef_zero/solr/query/subquery'
|
7
|
+
|
8
|
+
module ChefZero
|
9
|
+
module Solr
|
10
|
+
class SolrParser
|
11
|
+
def initialize(query_string)
|
12
|
+
@query_string = query_string
|
13
|
+
@index = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
read_expression
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Tokenization
|
22
|
+
#
|
23
|
+
def peek_token
|
24
|
+
@next_token ||= parse_token
|
25
|
+
end
|
26
|
+
|
27
|
+
def next_token
|
28
|
+
result = peek_token
|
29
|
+
@next_token = nil
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_token
|
34
|
+
# Skip whitespace
|
35
|
+
skip_whitespace
|
36
|
+
return nil if eof?
|
37
|
+
|
38
|
+
# Operators
|
39
|
+
operator = peek_operator_token
|
40
|
+
if operator
|
41
|
+
@index+=operator.length
|
42
|
+
operator
|
43
|
+
else
|
44
|
+
# Everything that isn't whitespace or an operator, is part of a term
|
45
|
+
# (characters plus backslashed escaped characters)
|
46
|
+
start_index = @index
|
47
|
+
begin
|
48
|
+
if @query_string[@index] == '\\'
|
49
|
+
@index+=1
|
50
|
+
end
|
51
|
+
@index+=1 if !eof?
|
52
|
+
end until eof? || @query_string[@index] =~ /\s/ || peek_operator_token
|
53
|
+
@query_string[start_index..@index-1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def skip_whitespace
|
58
|
+
if @query_string[@index] =~ /\s/
|
59
|
+
whitespace = /\s+/.match(@query_string, @index)
|
60
|
+
@index += whitespace[0].length
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def peek_operator_token
|
65
|
+
if ['"', '+', '-', '!', '(', ')', '{', '}', '[', ']', '^', ':'].include?(@query_string[@index])
|
66
|
+
return @query_string[@index]
|
67
|
+
else
|
68
|
+
result = @query_string[@index..@index+1]
|
69
|
+
if ['&&', '||'].include?(result)
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def eof?
|
77
|
+
!@next_token && @index >= @query_string.length
|
78
|
+
end
|
79
|
+
|
80
|
+
# Parse tree creation
|
81
|
+
def read_expression
|
82
|
+
result = read_single_expression
|
83
|
+
# Expression is over when we hit a close paren or eof
|
84
|
+
# (peek_token has the side effect of skipping whitespace for us, so we
|
85
|
+
# really know if we're at eof or not)
|
86
|
+
until peek_token == ')' || eof?
|
87
|
+
operator = peek_token
|
88
|
+
if binary_operator?(operator)
|
89
|
+
next_token
|
90
|
+
else
|
91
|
+
# If 2 terms are next to each other, the default operator is OR
|
92
|
+
operator = 'OR'
|
93
|
+
end
|
94
|
+
next_expression = read_single_expression
|
95
|
+
|
96
|
+
# Build the operator, taking precedence into account
|
97
|
+
if result.is_a?(Query::BinaryOperator) &&
|
98
|
+
binary_operator_precedence(operator) > binary_operator_precedence(result.operator)
|
99
|
+
# a+b*c -> a+(b*c)
|
100
|
+
new_right = Query::BinaryOperator.new(result.right, operator, next_expression)
|
101
|
+
result = Query::BinaryOperator.new(result.left, result.operator, new_right)
|
102
|
+
else
|
103
|
+
# a*b+c -> (a*b)+c
|
104
|
+
result = Query::BinaryOperator.new(result, operator, next_expression)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_error(token, str)
|
111
|
+
error = "Error on token '#{token}' at #{@index} of '#{@query_string}': #{str}"
|
112
|
+
raise error
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_single_expression
|
116
|
+
token = next_token
|
117
|
+
# If EOF, we have a problem Houston
|
118
|
+
if !token
|
119
|
+
parse_error(nil, "Expected expression!")
|
120
|
+
|
121
|
+
# If it's an unary operand, build that
|
122
|
+
elsif unary_operator?(token)
|
123
|
+
operand = read_single_expression
|
124
|
+
# TODO We rely on all unary operators having higher precedence than all
|
125
|
+
# binary operators. Check if this is the case.
|
126
|
+
Query::UnaryOperator.new(token, operand)
|
127
|
+
|
128
|
+
# If it's the start of a phrase, read the terms in the phrase
|
129
|
+
elsif token == '"'
|
130
|
+
# Read terms until close "
|
131
|
+
phrase_terms = []
|
132
|
+
until (term = next_token) == '"'
|
133
|
+
phrase_terms << Query::Term.new(term)
|
134
|
+
end
|
135
|
+
Query::Phrase.new(phrase_terms)
|
136
|
+
|
137
|
+
# If it's the start of a range query, build that
|
138
|
+
elsif token == '{' || token == '['
|
139
|
+
left = next_token
|
140
|
+
parse_error(left, "Expected left term in range query") if !left
|
141
|
+
to = next_token
|
142
|
+
parse_error(left, "Expected TO in range query") if to != "TO"
|
143
|
+
right = next_token
|
144
|
+
parse_error(right, "Expected left term in range query") if !right
|
145
|
+
end_range = next_token
|
146
|
+
parse_error(right, "Expected end range '#{expected_end_range}") if !['{', '['].include?(end_range)
|
147
|
+
Query::RangeQuery.new(left, right, token == '[', end_range == ']')
|
148
|
+
|
149
|
+
elsif token == '('
|
150
|
+
subquery = read_expression
|
151
|
+
close_paren = next_token
|
152
|
+
parse_error(close_paren, "Expected ')'") if close_paren != ')'
|
153
|
+
Query::Subquery.new(subquery)
|
154
|
+
|
155
|
+
# If it's the end of a closure, raise an exception
|
156
|
+
elsif ['}',']',')'].include?(token)
|
157
|
+
parse_error(token, "Unexpected end paren")
|
158
|
+
|
159
|
+
# If it's a binary operator, raise an exception
|
160
|
+
elsif binary_operator?(token)
|
161
|
+
parse_error(token, "Unexpected binary operator")
|
162
|
+
|
163
|
+
# Otherwise it's a term.
|
164
|
+
else
|
165
|
+
Query::Term.new(token)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def unary_operator?(token)
|
170
|
+
[ 'NOT', '+', '-' ].include?(token)
|
171
|
+
end
|
172
|
+
|
173
|
+
def binary_operator?(token)
|
174
|
+
[ 'AND', 'OR', '^', ':'].include?(token)
|
175
|
+
end
|
176
|
+
|
177
|
+
def binary_operator_precedence(token)
|
178
|
+
case token
|
179
|
+
when '^'
|
180
|
+
4
|
181
|
+
when ':'
|
182
|
+
3
|
183
|
+
when 'AND'
|
184
|
+
2
|
185
|
+
when 'OR'
|
186
|
+
1
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
DEFAULT_FIELD = 'text'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|