This repository was archived by the owner on May 9, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 131
Expand file tree
/
Copy pathsearch.py
More file actions
133 lines (107 loc) · 4.1 KB
/
search.py
File metadata and controls
133 lines (107 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import six
import collections
import copy
import six.moves.urllib.parse
from chef.api import ChefAPI
from chef.base import ChefQuery, ChefObject
class SearchRow(dict):
"""A single row in a search result."""
def __init__(self, row, api):
super(SearchRow, self).__init__(row)
self.api = api
self._object = None
@property
def object(self):
if self._object is None:
# Decode Chef class name
chef_class = self.get('json_class', '')
if chef_class.startswith('Chef::'):
chef_class = chef_class[6:]
if chef_class == 'ApiClient':
chef_class = 'Client' # Special case since I don't match the Ruby name.
cls = ChefObject.types.get(chef_class.lower())
if not cls:
raise ValueError('Unknown class %s'%chef_class)
self._object = cls.from_search(self, api=self.api)
return self._object
class Search(collections.Sequence):
"""A search of the Chef index.
The only required argument is the index name to search (eg. node, role, etc).
The second, optional argument can be any Solr search query, with the same semantics
as Chef.
Example::
for row in Search('node', 'roles:app'):
print row['roles']
print row.object.name
.. versionadded:: 0.1
You can also do partial searches if you are using chef server >= 12.
Example::
keys = {
'name' : ['name'],
'ip' : ['ipaddress'],
'kernel_name' : ['kernel', 'name']
}
for row in Search('node', 'roles:app', filter_result=keys):
print row['data']['kernel_name']
"""
url = '/search'
def __init__(self, index, q='*:*', rows=1000, start=0, api=None, filter_result=None):
self.name = index
self.api = api or ChefAPI.get_global()
self._args = dict(q=q, rows=rows, start=start)
self.url = self.__class__.url + '/' + self.name + '?' + six.moves.urllib.parse.urlencode(self._args)
self._filter_result = filter_result
@property
def data(self):
if not hasattr(self, '_data'):
if not self._filter_result:
self._data = self.api[self.url]
else:
self._data = self.api.api_request('POST', self.url, data=self._filter_result)
return self._data
@property
def total(self):
return self.data['total']
def query(self, query):
args = copy.copy(self._args)
args['q'] = query
return self.__class__(self.name, api=self.api, **args)
def rows(self, rows):
args = copy.copy(self._args)
args['rows'] = rows
return self.__class__(self.name, api=self.api, **args)
def start(self, start):
args = copy.copy(self._args)
args['start'] = start
return self.__class__(self.name, api=self.api, **args)
def __len__(self):
return len(self.data['rows'])
def __getitem__(self, value):
if isinstance(value, slice):
if value.step is not None and value.step != 1:
raise ValueError('Cannot use a step other than 1')
return self.start(self._args['start']+value.start).rows(value.stop-value.start)
if isinstance(value, six.string_types):
return self[self.index(value)]
row_value = self.data['rows'][value]
# Check for null rows, just in case
if row_value is None:
return None
return SearchRow(row_value, self.api)
def __contains__(self, name):
for row in self:
if row.object.name == name:
return True
return False
def index(self, name):
for i, row in enumerate(self):
if row.object.name == name:
return i
raise ValueError('%s not in search'%name)
def __call__(self, query):
return self.query(query)
@classmethod
def list(cls, api=None):
api = api or ChefAPI.get_global()
names = [name for name, url in six.iteritems(api[cls.url])]
return ChefQuery(cls, names, api)