|
97 | 97 | PATH_JOBS = "search/jobs/" |
98 | 98 | PATH_LOGGER = "server/logger/" |
99 | 99 | PATH_MESSAGES = "messages/" |
| 100 | +PATH_MODULAR_INPUTS = "data/modular-inputs" |
100 | 101 | PATH_ROLES = "authentication/roles/" |
101 | 102 | PATH_SAVED_SEARCHES = "saved/searches/" |
102 | 103 | PATH_STANZA = "configs/conf-%s/%s" # (file, stanza) |
@@ -334,6 +335,7 @@ class Service(Context): |
334 | 335 | """ |
335 | 336 | def __init__(self, **kwargs): |
336 | 337 | Context.__init__(self, **kwargs) |
| 338 | + self._splunk_version = None |
337 | 339 |
|
338 | 340 | @property |
339 | 341 | def apps(self): |
@@ -396,6 +398,14 @@ def messages(self): |
396 | 398 | """Returns a collection of service messages.""" |
397 | 399 | return Collection(self, PATH_MESSAGES, item=Message) |
398 | 400 |
|
| 401 | + @property |
| 402 | + def modular_input_kinds(self): |
| 403 | + """Returns a collection of the modular input kinds on this Splunk instance.""" |
| 404 | + if self.splunk_version[0] >= 5: |
| 405 | + return ReadOnlyCollection(self, PATH_MODULAR_INPUTS, item=Entity) |
| 406 | + else: |
| 407 | + raise IllegalOperationException("Modular inputs are not supported before Splunk version 5.") |
| 408 | + |
399 | 409 | # kwargs: enable_lookups, reload_macros, parse_only, output_mode |
400 | 410 | def parse(self, query, **kwargs): |
401 | 411 | """Parses a search query and returns a semantic map of the search. |
@@ -454,6 +464,12 @@ def settings(self): |
454 | 464 | """Returns configuration settings for the service.""" |
455 | 465 | return Settings(self) |
456 | 466 |
|
| 467 | + @property |
| 468 | + def splunk_version(self): |
| 469 | + if self._splunk_version is None: |
| 470 | + self._splunk_version = tuple([int(p) for p in self.info['version'].split('.')]) |
| 471 | + return self._splunk_version |
| 472 | + |
457 | 473 | @property |
458 | 474 | def users(self): |
459 | 475 | """Returns a collection of users.""" |
@@ -891,47 +907,12 @@ def update(self, **kwargs): |
891 | 907 | self.post(**kwargs) |
892 | 908 | return self |
893 | 909 |
|
894 | | -class Collection(Endpoint): |
895 | | - """A collection of entities in the Splunk instance. |
896 | | -
|
897 | | - Splunk provides a number of different collections of distinct |
898 | | - entity types: applications, saved searches, fired alerts, and a |
899 | | - number of others. Each particular type is available separately |
900 | | - from the Splunk instance, and the entities of that type are |
901 | | - returned in a :class:`Collection`. |
902 | | -
|
903 | | - :class:`Collection`'s interface does not quite match either |
904 | | - ``list`` or ``dict`` in Python, since there are enough semantic |
905 | | - mismatches with either to make its behavior surprising. A unique |
906 | | - element in a :class:`Collection` is defined by a string giving its |
907 | | - name plus a namespace object (though the namespace is optional if |
908 | | - the name is unique).:: |
909 | | -
|
910 | | - import splunklib.client as client |
911 | | - s = client.connect(...) |
912 | | - c = s.saved_searches # c is a Collection |
913 | | - m = c['my_search', client.namespace(owner='boris', app='natasha', sharing='user')] |
914 | | - # Or if there is only one search visible named 'my_search' |
915 | | - m = c['my_search'] |
916 | | -
|
917 | | - Similarly, ``"name" in c`` works as you expect (though you cannot |
918 | | - currently pass a namespace to the ``in`` operator), as does |
919 | | - ``len(c)``. |
920 | | -
|
921 | | - However, as an aggregate, :class:`Collection` behaves more like a |
922 | | - list. If you iterate over a :class:`Collection`, you get an |
923 | | - iterator over the entities, not the names and namespaces:: |
924 | | -
|
925 | | - for entity in c: |
926 | | - assert isinstance(entity, client.Entity) |
927 | | -
|
928 | | - The :meth:`create` and :meth:`delete` methods create and delete |
929 | | - entities in this collection. The access control list and other |
930 | | - metadata of the collection is returned by the :meth:`itemmeta` |
931 | | - method. |
| 910 | +class ReadOnlyCollection(Endpoint): |
| 911 | + """A read-only collection of entities in the Splunk instance. |
932 | 912 |
|
933 | | - :class:`Collection` does no caching. Each call makes at least one |
934 | | - round trip to the server to fetch data. |
| 913 | + See the documentation for :class:`Collection` for most usage. The only |
| 914 | + difference is that it lacks :class:`ReadOnlyCollection` lacks |
| 915 | + :method:`create`, :method:`delete` |
935 | 916 | """ |
936 | 917 | def __init__(self, service, path, item=Entity): |
937 | 918 | Endpoint.__init__(self, service, path) |
@@ -1132,97 +1113,6 @@ def contains(self, name): |
1132 | 1113 | """ |
1133 | 1114 | return name in self |
1134 | 1115 |
|
1135 | | - def create(self, name, **params): |
1136 | | - """Create a new entity in this collection. |
1137 | | -
|
1138 | | - This function makes either one or two roundtrips to the |
1139 | | - server, depending on the type of the entities in this |
1140 | | - collection, plus at most two more if autologin is enabled. |
1141 | | -
|
1142 | | - :param name: The name of the entity to create. |
1143 | | - :type name: string |
1144 | | - :param namespace: A namespace, as created by the :func:`namespace` |
1145 | | - function (optional). If you wish, you can set |
1146 | | - ``owner``, ``app``, and ``sharing`` directly. |
1147 | | - :type namespace: :class:`Record` with keys ``'owner'``, ``'app'``, and |
1148 | | - ``'sharing'`` |
1149 | | - :param params: Additional entity-specific arguments (optional). |
1150 | | - :return: The new entity. |
1151 | | - :rtype: subclass of ``Entity``, chosen by ``self.item`` in ``Collection`` |
1152 | | -
|
1153 | | - **Example**:: |
1154 | | -
|
1155 | | - import splunklib.client as client |
1156 | | - s = client.connect(...) |
1157 | | - applications = s.apps |
1158 | | - new_app = applications.create("my_fake_app") |
1159 | | - """ |
1160 | | - if not isinstance(name, basestring): |
1161 | | - raise InvalidNameException("%s is not a valid name for an entity." % name) |
1162 | | - if 'namespace' in params: |
1163 | | - namespace = params.pop('namespace') |
1164 | | - params['owner'] = namespace.owner |
1165 | | - params['app'] = namespace.app |
1166 | | - params['sharing'] = namespace.sharing |
1167 | | - response = self.post(name=name, **params) |
1168 | | - atom = _load_atom(response, XNAME_ENTRY) |
1169 | | - if atom is None: |
1170 | | - # This endpoint doesn't return the content of the new |
1171 | | - # item. We have to go fetch it ourselves. |
1172 | | - return self[name] |
1173 | | - else: |
1174 | | - entry = atom.entry |
1175 | | - state = _parse_atom_entry(entry) |
1176 | | - entity = self.item( |
1177 | | - self.service, |
1178 | | - self._entity_path(state), |
1179 | | - state=state) |
1180 | | - return entity |
1181 | | - |
1182 | | - def delete(self, name, **params): |
1183 | | - """Delete the entity *name* from the collection. |
1184 | | -
|
1185 | | - :param name: The name of the entity to delete. |
1186 | | - :type name: string |
1187 | | - :rtype: the collection ``self``. |
1188 | | -
|
1189 | | - This method is implemented for consistency with the REST |
1190 | | - interface's DELETE method. |
1191 | | -
|
1192 | | - If there is no entity named *name* on the server, then throws |
1193 | | - a ``KeyError``. This function always makes a roundtrip to the |
1194 | | - server. |
1195 | | -
|
1196 | | - **Example**:: |
1197 | | -
|
1198 | | - import splunklib.client as client |
1199 | | - c = client.connect(...) |
1200 | | - saved_searches = c.saved_searches |
1201 | | - saved_searches.create('my_saved_search', |
1202 | | - 'search * | head 1') |
1203 | | - assert 'my_saved_search' in saved_searches |
1204 | | - saved_searches.delete('my_saved_search') |
1205 | | - assert 'my_saved_search' not in saved_searches |
1206 | | - """ |
1207 | | - # If you update the documentation here, be sure you do so on |
1208 | | - # __delitem__ as well. |
1209 | | - if 'namespace' in params: |
1210 | | - namespace = params.pop('namespace') |
1211 | | - params['owner'] = namespace.owner |
1212 | | - params['app'] = namespace.app |
1213 | | - params['sharing'] = namespace.sharing |
1214 | | - try: |
1215 | | - self.service.delete(_path(self.path, name), **params) |
1216 | | - except HTTPError as he: |
1217 | | - # An HTTPError with status code 404 means that the entity |
1218 | | - # has already been deleted, and we reraise it as a |
1219 | | - # KeyError. |
1220 | | - if he.status == 404: |
1221 | | - raise KeyError("No such entity %s" % name) |
1222 | | - else: |
1223 | | - raise |
1224 | | - return self |
1225 | | - |
1226 | 1116 | def itemmeta(self): |
1227 | 1117 | """Returns metadata for members of the collection. |
1228 | 1118 |
|
@@ -1339,6 +1229,137 @@ def names(self, count=None, **kwargs): |
1339 | 1229 | """ |
1340 | 1230 | return [ent.name for ent in self.iter(count=count, **kwargs)] |
1341 | 1231 |
|
| 1232 | +class Collection(ReadOnlyCollection): |
| 1233 | + """A collection of entities in the Splunk instance. |
| 1234 | +
|
| 1235 | + Splunk provides a number of different collections of distinct |
| 1236 | + entity types: applications, saved searches, fired alerts, and a |
| 1237 | + number of others. Each particular type is available separately |
| 1238 | + from the Splunk instance, and the entities of that type are |
| 1239 | + returned in a :class:`Collection`. |
| 1240 | +
|
| 1241 | + :class:`Collection`'s interface does not quite match either |
| 1242 | + ``list`` or ``dict`` in Python, since there are enough semantic |
| 1243 | + mismatches with either to make its behavior surprising. A unique |
| 1244 | + element in a :class:`Collection` is defined by a string giving its |
| 1245 | + name plus a namespace object (though the namespace is optional if |
| 1246 | + the name is unique).:: |
| 1247 | +
|
| 1248 | + import splunklib.client as client |
| 1249 | + s = client.connect(...) |
| 1250 | + c = s.saved_searches # c is a Collection |
| 1251 | + m = c['my_search', client.namespace(owner='boris', app='natasha', sharing='user')] |
| 1252 | + # Or if there is only one search visible named 'my_search' |
| 1253 | + m = c['my_search'] |
| 1254 | +
|
| 1255 | + Similarly, ``"name" in c`` works as you expect (though you cannot |
| 1256 | + currently pass a namespace to the ``in`` operator), as does |
| 1257 | + ``len(c)``. |
| 1258 | +
|
| 1259 | + However, as an aggregate, :class:`Collection` behaves more like a |
| 1260 | + list. If you iterate over a :class:`Collection`, you get an |
| 1261 | + iterator over the entities, not the names and namespaces:: |
| 1262 | +
|
| 1263 | + for entity in c: |
| 1264 | + assert isinstance(entity, client.Entity) |
| 1265 | +
|
| 1266 | + The :meth:`create` and :meth:`delete` methods create and delete |
| 1267 | + entities in this collection. The access control list and other |
| 1268 | + metadata of the collection is returned by the :meth:`itemmeta` |
| 1269 | + method. |
| 1270 | +
|
| 1271 | + :class:`Collection` does no caching. Each call makes at least one |
| 1272 | + round trip to the server to fetch data. |
| 1273 | + """ |
| 1274 | + def create(self, name, **params): |
| 1275 | + """Create a new entity in this collection. |
| 1276 | +
|
| 1277 | + This function makes either one or two roundtrips to the |
| 1278 | + server, depending on the type of the entities in this |
| 1279 | + collection, plus at most two more if autologin is enabled. |
| 1280 | +
|
| 1281 | + :param name: The name of the entity to create. |
| 1282 | + :type name: string |
| 1283 | + :param namespace: A namespace, as created by the :func:`namespace` |
| 1284 | + function (optional). If you wish, you can set |
| 1285 | + ``owner``, ``app``, and ``sharing`` directly. |
| 1286 | + :type namespace: :class:`Record` with keys ``'owner'``, ``'app'``, and |
| 1287 | + ``'sharing'`` |
| 1288 | + :param params: Additional entity-specific arguments (optional). |
| 1289 | + :return: The new entity. |
| 1290 | + :rtype: subclass of ``Entity``, chosen by ``self.item`` in ``Collection`` |
| 1291 | +
|
| 1292 | + **Example**:: |
| 1293 | +
|
| 1294 | + import splunklib.client as client |
| 1295 | + s = client.connect(...) |
| 1296 | + applications = s.apps |
| 1297 | + new_app = applications.create("my_fake_app") |
| 1298 | + """ |
| 1299 | + if not isinstance(name, basestring): |
| 1300 | + raise InvalidNameException("%s is not a valid name for an entity." % name) |
| 1301 | + if 'namespace' in params: |
| 1302 | + namespace = params.pop('namespace') |
| 1303 | + params['owner'] = namespace.owner |
| 1304 | + params['app'] = namespace.app |
| 1305 | + params['sharing'] = namespace.sharing |
| 1306 | + response = self.post(name=name, **params) |
| 1307 | + atom = _load_atom(response, XNAME_ENTRY) |
| 1308 | + if atom is None: |
| 1309 | + # This endpoint doesn't return the content of the new |
| 1310 | + # item. We have to go fetch it ourselves. |
| 1311 | + return self[name] |
| 1312 | + else: |
| 1313 | + entry = atom.entry |
| 1314 | + state = _parse_atom_entry(entry) |
| 1315 | + entity = self.item( |
| 1316 | + self.service, |
| 1317 | + self._entity_path(state), |
| 1318 | + state=state) |
| 1319 | + return entity |
| 1320 | + |
| 1321 | + def delete(self, name, **params): |
| 1322 | + """Delete the entity *name* from the collection. |
| 1323 | +
|
| 1324 | + :param name: The name of the entity to delete. |
| 1325 | + :type name: string |
| 1326 | + :rtype: the collection ``self``. |
| 1327 | +
|
| 1328 | + This method is implemented for consistency with the REST |
| 1329 | + interface's DELETE method. |
| 1330 | +
|
| 1331 | + If there is no entity named *name* on the server, then throws |
| 1332 | + a ``KeyError``. This function always makes a roundtrip to the |
| 1333 | + server. |
| 1334 | +
|
| 1335 | + **Example**:: |
| 1336 | +
|
| 1337 | + import splunklib.client as client |
| 1338 | + c = client.connect(...) |
| 1339 | + saved_searches = c.saved_searches |
| 1340 | + saved_searches.create('my_saved_search', |
| 1341 | + 'search * | head 1') |
| 1342 | + assert 'my_saved_search' in saved_searches |
| 1343 | + saved_searches.delete('my_saved_search') |
| 1344 | + assert 'my_saved_search' not in saved_searches |
| 1345 | + """ |
| 1346 | + if 'namespace' in params: |
| 1347 | + namespace = params.pop('namespace') |
| 1348 | + params['owner'] = namespace.owner |
| 1349 | + params['app'] = namespace.app |
| 1350 | + params['sharing'] = namespace.sharing |
| 1351 | + try: |
| 1352 | + self.service.delete(_path(self.path, name), **params) |
| 1353 | + except HTTPError as he: |
| 1354 | + # An HTTPError with status code 404 means that the entity |
| 1355 | + # has already been deleted, and we reraise it as a |
| 1356 | + # KeyError. |
| 1357 | + if he.status == 404: |
| 1358 | + raise KeyError("No such entity %s" % name) |
| 1359 | + else: |
| 1360 | + raise |
| 1361 | + return self |
| 1362 | + |
1342 | 1363 | class ConfigurationFile(Collection): |
1343 | 1364 | """This class contains a single configuration, which is a collection of |
1344 | 1365 | stanzas.""" |
@@ -1454,6 +1475,13 @@ def default(self): |
1454 | 1475 | index = self['_audit'] |
1455 | 1476 | return index['defaultDatabase'] |
1456 | 1477 |
|
| 1478 | + def delete(self): |
| 1479 | + if self.splunk_version[0] >= 5: |
| 1480 | + Collection.delete(self.service, self.name) |
| 1481 | + else: |
| 1482 | + raise IllegalOperationException("Deleting indexes via the REST API is " |
| 1483 | + "not supported before Splunk version 5.") |
| 1484 | + |
1457 | 1485 | class Index(Entity): |
1458 | 1486 | """This class is an index class used to access specific operations.""" |
1459 | 1487 | def __init__(self, service, path, **kwargs): |
@@ -1545,14 +1573,14 @@ def disable(self): |
1545 | 1573 | """Disables this index.""" |
1546 | 1574 | # Starting in Ace, we have to do this with specific sharing, |
1547 | 1575 | # unlike most other entities. |
1548 | | - self.post("disable", sharing="system") |
| 1576 | + self.post("disable") |
1549 | 1577 | return self |
1550 | 1578 |
|
1551 | 1579 | def enable(self): |
1552 | 1580 | """Enables this index.""" |
1553 | 1581 | # Starting in Ace, we have to reenable this with a specific |
1554 | 1582 | # sharing unlike most other entities. |
1555 | | - self.post("enable", sharing="system") |
| 1583 | + self.post("enable") |
1556 | 1584 | return self |
1557 | 1585 |
|
1558 | 1586 | def roll_hot_buckets(self): |
|
0 commit comments