diff --git a/AGENTS b/AGENTS
new file mode 100644
index 0000000..28ae80e
--- /dev/null
+++ b/AGENTS
@@ -0,0 +1,17 @@
+Repository: Tideway (BMC Discovery API client)
+
+Mission
+- Add or adjust API helpers using the top-level REST wrappers in `tideway/main.py` (`Appliance.get/post/patch/put/delete`). Do not call `requests` or `discoRequests` directly from feature modules, except the explicit non-standard schema endpoints (`/about`, swagger/openapi) which are intentionally direct in `main.py`.
+- Preserve parameter handling: set `self.params[...]` before calling a wrapper; the appliance layer resets params after each request.
+- Maintain existing deprecation warnings; when adding aliases, warn with `DeprecationWarning` and point to the preferred helper.
+
+Uploads and special payloads
+- Use `Appliance.post(..., files=..., content_type=...)` for multipart uploads (TKU/knowledge, Kerberos keytabs/ccaches). Do not reintroduce `discoRequests` helpers in new code.
+- When a response needs a specific MIME type (e.g., licensing CSV/raw), pass `response="application/zip"` or similar via the wrapper.
+
+Style and safety
+- Keep code ASCII-only unless the file already uses Unicode. Keep comments minimal and clarifying, not redundant.
+- Do not remove or overwrite user changes; avoid destructive git commands.
+
+Validation
+- Preferred quick check: `python3 -m compileall tideway`.
diff --git a/README.md b/README.md
index 58b5fee..2b7bc52 100644
--- a/README.md
+++ b/README.md
@@ -6,14 +6,14 @@ Simplified Python library for BMC Discovery API Interface that makes use of the
```python
>>> import tideway
>>> tw = tideway.appliance('appliance-hostname','auth-token')
->>> tw.about().url
+>>> tw.api_about.url
'https://appliance-hostname/api/about'
->>> tw.about().status_code
+>>> tw.api_about.status_code
200
->>> tw.about().text
+>>> tw.api_about.text
{
"api_versions": [
- "1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7","1.8","1.9","1.10","1.11","1.12","1.13","1.14"
+ "1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7","1.8","1.9","1.10","1.11","1.12","1.13","1.14","1.15","1.16"
],
"component": "REST API",
"version":"DaaS",
@@ -30,6 +30,8 @@ Tideway removes the extra layer of manually constructing a URL and parameters fo
Documentation can be found at [https://traversys.github.io/Tideway/](https://traversys.github.io/Tideway/).
+- Example notebook: [`notebooks/admin_api_examples.ipynb`](https://github.com/traversys/Tideway/blob/main/notebooks/admin_api_examples.ipynb) (download via `curl -O https://raw.githubusercontent.com/traversys/Tideway/main/notebooks/admin_api_examples.ipynb`)
+
## Installation
- Tideway can be installed via PyPI:
@@ -38,7 +40,7 @@ Documentation can be found at [https://traversys.github.io/Tideway/](https://tra
$ python -m pip install tideway
```
-- Tideway supports BMC Discovery 11.3+, API v1.0-1.14 using Python 3.
+- Tideway supports BMC Discovery 11.3+, API v1.0-1.16 using Python 3.
## Releases
@@ -50,4 +52,5 @@ $ python -m pip install tideway
| 0.1.4 | Search bulk update | Discovery 12.3 (21.3) enforces strict case for "Bearer" header - api calls will not current work. | Now includes headers for non-formatted search. |
| 0.1.5 | Updated to support Discovery 12.3 (API version 1.3) | - Missing 'complete' parameter option on graphNode() function. | - Fixed issue with Bearer capitalisation.
- Search Bulk will now return the full response on failure |
| 0.2.0 | Updated to include Kerberos, Models and Taxonomy endpoints.
Added new high level generic endpoint function calls
Refactored function names/decorators to match API endpoints as close as possible.
Supports Discovery 22.2 (12.5) (API version 1.5) and Outpost API version 1.0 | Project missing tkinter module: https://github.com/traversys/Tideway/issues/15 | Added 'complete' parameter to `get_data_nodes_graph()` (replaces `graphNode()`) |
-| 0.2.1 | Added `complete` flag for graph calls, bug fixes to pagination and default focus.
Can retrieve condition templates without an ID.
Kerberos realm detection fixed and parameters are reset after each request.
Removed unused Tkinter library.
Updated to support API version 1.14 | May not work with all new endpoints. | | Issue: https://github.com/traversys/Tideway/issues/15 |
\ No newline at end of file
+| 0.2.1 | Added `complete` flag for graph calls, bug fixes to pagination and default focus.
Can retrieve condition templates without an ID.
Kerberos realm detection fixed and parameters are reset after each request.
Removed unused Tkinter library.
Updated to support API version 1.14 | May not work with all new endpoints. | | Issue: https://github.com/traversys/Tideway/issues/15 |
+| 0.3.0 | Removed deprecated helper aliases and routed all modules through the top-level REST wrappers.
Documentation refreshed to reflect the lean API surface. | | Deprecated helper functions removed; docs and examples updated. |
diff --git a/docs/endpoints/admin.md b/docs/endpoints/admin.md
index 89de382..b51435c 100644
--- a/docs/endpoints/admin.md
+++ b/docs/endpoints/admin.md
@@ -52,45 +52,34 @@ Example:
{'api_versions': ['1.0', '1.1', '1.2'], 'component': 'REST API', 'product': 'BMC Discovery', 'version': '12.2'}
```
-## baseline()
+## get_admin_licensing
-[Deprecated] See [get_admin_baseline](#get_admin_baseline) for usage.
+Get the latest signed licensing report (plain text by default).
-Syntax: `.baseline()`
-
-## about()
-
-[Deprecated] See [get_admin_about](#get_admin_about) for usage.
-
-Syntax: `.about()`
+Syntax:
-## licensing()
+```
+.get_admin_licensing
+```
-Get the latest signed licensing report.
+## get_admin_licensing_csv
-- CSV option returns raw license data in CSV format as a zip file for offline analysis.
-- RAW option return an encrypted raw license object for import to another appliance.
+Get the latest raw license data in CSV format as a zip file for offline analysis.
Syntax:
```
-.licensing([ _content_type_ ])
+.get_admin_licensing_csv
```
-| Parameters | Type | Required | Default Value | Options |
-| ------------ | ------ | :------: | ------------- | ------- |
-| content_type | String | No | "text/plain" |
+## get_admin_licensing_raw
-Example:
-```python
->>> tw.licensing()
------BEGIN LICENSE REPORT-----
-License report
-==============
+Get the latest license data as an encrypted raw license object for import to another appliance.
+
+Syntax:
-Report start time: 2021-01-18 23:00:00.409987+00:00
-Report end time : 2021-01-21 23:00:00.410085+00:00
-...
+```
+.get_admin_licensing_raw
```
## get_admin_instance
@@ -217,4 +206,4 @@ Example:
{
...
}
-```
\ No newline at end of file
+```
diff --git a/docs/endpoints/appliance.md b/docs/endpoints/appliance.md
index 8371d24..690c0dd 100644
--- a/docs/endpoints/appliance.md
+++ b/docs/endpoints/appliance.md
@@ -355,59 +355,6 @@ Example:
```
-## about()
-
-[Deprecated] See [api_about](#api_about) for usage.
-
-Syntax: `.about()`
-
-## admin()
-
-[Deprecated] See [get_admin_about](#get_admin_about) for usage.
-
-Syntax: `.admin()`
-
-## swagger()
-
-[Deprecated] See [api_swagger](#api_swagger) for usage.
-
-Syntax: `.swagger()`
-
-## baseline()
-
-[Deprecated] See [get_admin_baseline](#get_admin_baseline) for usage.
-
-Syntax: `.baseline()`
-
-## licensing()
-
-Get the latest signed licensing report.
-
-- CSV option returns raw license data in CSV format as a zip file for offline analysis.
-- RAW option return an encrypted raw license object for import to another appliance.
-
-Syntax:
-
-```
-.licensing([ _content_type_ ])
-```
-
-| Parameters | Type | Required | Default Value | Options |
-| ------------ | ------ | :------: | ------------- | ------- |
-| content_type | String | No | "text/plain" |
-
-Example:
-```python
->>> tw.licensing()
------BEGIN LICENSE REPORT-----
-License report
-==============
-
-Report start time: 2021-01-18 23:00:00.409987+00:00
-Report end time : 2021-01-21 23:00:00.410085+00:00
-...
-```
-
## help()
- Get help on specific Discovery API endpoint and function to use. Outputs full list by default.
@@ -433,4 +380,4 @@ Endpoint Function Description
/vault/credentials/{cred_id} updateCredential(cred_id, body) Updates partial resources of a credential. Missing properties are left unchanged.
/vault/credentials/{cred_id} replaceCredential(cred_id, body) Replaces a single credential. All required credential properties must be present.
-```
\ No newline at end of file
+```
diff --git a/docs/endpoints/data.md b/docs/endpoints/data.md
index 0faae0b..7ff9726 100644
--- a/docs/endpoints/data.md
+++ b/docs/endpoints/data.md
@@ -334,7 +334,7 @@ Syntax:
## get_data_partitions
-Graph data represents a set of nodes and relationships that are associated to the given node.
+Get names and IDs of partitions available on the appliance.
Syntax:
@@ -359,36 +359,6 @@ Example:
}
```
-## searchQuery()
-
-[Deprecated] See [search](#search) for usage.
-
-Syntax: `.searchQuery(__json__ [, _offset_ ] [, _results_id_ ] [, _format_ ] [, _limit_ ] [, _delete_ ])`
-
-## nodeLookup()
-
-[Deprecated] See [get_data_nodes](#get_data_nodes) for usage.
-
-Syntax: `.nodeLookup(__node_id__ [, _relationships_ ] [, _traverse_ ] [, _flags_ ])`
-
-## lookupNodeKind()
-
-[Deprecated] See [get_data_kinds](#get_data_kinds) for usage.
-
-Syntax: `.lookupNodeKind(__kind__ [, _offset_ ] [, _results_id_ ] [, _format_ ] [, _limit_ ] [, _delete_ ])`
-
-## graphNode()
-
-[Deprecated] See [get_data_nodes_graph](#get_data_nodes_graph) for usage.
-
-Syntax: `.graphNode(__node_id__ [, _focus_ ] [, _apply_rules_ ]))`
-
-## partitions()
-
-[Deprecated] See [get_data_nodes_graph](#get_data_nodes_graph) for usage.
-
-Syntax: `.partitions()`
-
## post_data_partitions()
Create a Partition.
@@ -507,27 +477,3 @@ Syntax:
| ------------- | ------ | :------: | ------------- | ------- |
| consumer_name | String | Yes | N/A | N/A |
| path | String | No | N/A | N/A |
-
-## best_candidate()
-
-[Deprecated] See [post_data_candidate](#post_data_candidate) for usage.
-
-Syntax: `.post_data_candidate(__JSON__)`
-
-## top_candidates()
-
-[Deprecated] See [post_data_candidates](#post_data_candidates) for usage.
-
-Syntax: `.post_data_candidates(__json__)`
-
-## twImport()
-
-[Deprecated] See [post_data_import](#post_data_import) for usage.
-
-Syntax: `.twImport(__json__)`
-
-## twWrite()
-
-[Deprecated] See [post_data_write](#post_data_write) for usage.
-
-Syntax: `.twWrite(__json__)`
\ No newline at end of file
diff --git a/docs/endpoints/topology.md b/docs/endpoints/topology.md
index f152660..bed678f 100644
--- a/docs/endpoints/topology.md
+++ b/docs/endpoints/topology.md
@@ -65,24 +65,6 @@ Syntax:
| ------------- | ----------- | :------: | ------------- | -------- |
| json | JSON Object | Yes | N/A | N/A |
-## graphNode()
-
-[Deprecated] See [get_data_nodes_graph](#get_data_nodes_graph) for usage.
-
-Syntax: `.graphNode(__node_id__ [, _focus_ ] [, _apply_rules_ ])`
-
-## getNodes()
-
-[Deprecated] See [post_topology_nodes](#post_topology_nodes) for usage.
-
-Syntax: `.getNodes(__json__)`
-
-## getNodeKinds()
-
-[Deprecated] See [post_topology_nodes_kinds](#post_topology_nodes_kinds) for usage.
-
-Syntax: `.getNodeKinds(__json__)`
-
## get_topology_viz_state
Get the current state of the visualization for the authenticated user.
@@ -106,7 +88,6 @@ Syntax:
| ------------- | ----------- | :------: | ------------- | -------- |
| json | JSON Object | Yes | N/A | N/A |
-
## put_topology_viz_state()
Update any or all of the attributes of the current state of the visualization for the authenticated user.
@@ -119,21 +100,3 @@ put_topology_viz_state(__json__)
| Parameters | Type | Required | Default Value | Options |
| ------------- | ----------- | :------: | ------------- | -------- |
| json | JSON Object | Yes | N/A | N/A |
-
-## visualizationState()
-
-[Deprecated] See [get_topology_viz_state](#get_topology_viz_state) for usage.
-
-Syntax: `.visualizationState()`
-
-## updateVizState()
-
-[Deprecated] See [patch_topology_viz_state](#patch_topology_viz_state) for usage.
-
-Syntax: `.updateVizState(__json__)`
-
-## replaceVizState()
-
-[Deprecated] See [put_topology_viz_state](#put_topology_viz_state) for usage.
-
-Syntax: `.replaceVizState(__json__)`
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index f9c19a6..b212ea8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -6,14 +6,14 @@ Simplified Python library for BMC Discovery API Interface that makes use of the
```python
>>> import tideway
>>> tw = tideway.appliance('appliance-hostname','auth-token')
->>> tw.about().url
+>>> tw.api_about.url
'https://appliance-hostname/api/about'
->>> tw.about().status_code
+>>> tw.api_about.status_code
200
->>> tw.about().text
+>>> tw.api_about.text
{
"api_versions": [
- "1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7","1.8","1.9","1.10","1.11","1.12","1.13","1.14"
+ "1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7","1.8","1.9","1.10","1.11","1.12","1.13","1.14","1.15","1.16"
],
"component": "REST API",
"version":"DaaS",
@@ -26,6 +26,8 @@ Tideway follows BMC Discovery's well-structured and documented REST API which ca
Tideway removes the extra layer of manually constructing a URL and parameters for python requests allowing you to query API supported features of Discovery seamlessly and faster than if you were to navigate via the GUI.
+Example notebook: [`notebooks/admin_api_examples.ipynb`](https://github.com/traversys/Tideway/blob/main/notebooks/admin_api_examples.ipynb) (download via `curl -O https://raw.githubusercontent.com/traversys/Tideway/main/notebooks/admin_api_examples.ipynb`)
+
## Installation
- Tideway can be installed via PyPI:
@@ -34,7 +36,7 @@ Tideway removes the extra layer of manually constructing a URL and parameters fo
$ python -m pip install tideway
```
-- Tideway supports BMC Discovery 11.3+, API v1.0-1.14 using Python 3.
+- Tideway supports BMC Discovery 11.3+, API v1.0-1.16 using Python 3.
## Contents
@@ -51,3 +53,4 @@ $ python -m pip install tideway
| 0.1.5 | Updated to support Discovery 12.3 (API version 1.3) | - Missing 'complete' parameter option on graphNode() function. | - Fixed issue with Bearer capitalisation.
- Search Bulk will now return the full response on failure |
| 0.2.0 | Updated to include Kerberos, Models and Taxonomy endpoints.
Added new high level generic endpoint function calls
Refactored function names/decorators to match API endpoints as close as possible.
Supports Discovery 22.2 (12.5) (API version 1.5) and Outpost API version 1.0 | Project missing tkinter module: https://github.com/traversys/Tideway/issues/15 | Added 'complete' parameter to `get_data_nodes_graph()` (replaces `graphNode()`) |
| 0.2.1 | Added `complete` flag for graph calls, bug fixes to pagination and default focus.
Can retrieve condition templates without an ID.
Kerberos realm detection fixed and parameters are reset after each request.
Removed unused Tkinter library.
Updated to support API version 1.14 | May not work with all new endpoints. | | Issue: https://github.com/traversys/Tideway/issues/15 |
+| 0.3.0 | Removed deprecated helper aliases and routed all modules through the top-level REST wrappers.
Documentation refreshed to reflect the lean API surface. | | Deprecated helper functions removed; docs and examples updated. |
diff --git a/docs/quickstart/initiation.md b/docs/quickstart/initiation.md
index 3079ac6..c24ad6e 100644
--- a/docs/quickstart/initiation.md
+++ b/docs/quickstart/initiation.md
@@ -32,5 +32,5 @@ Upon initiation the following parameters can be used:
| - | - | - | - | -
| target | Required | String | | The Hostname, FQDN or IP Address of the Discovery instance.
| token | Required | String | | The authentication token of the API user. It is not necessary to include the "bearer" pre-text.
-| api_version | | String | "1.5" | This should be the supported version of the API. Discovery 22.2 supports API versions up to 1.5 (outpost 1.0).
-| ssl_verify | | Boolean | False | Choose whether to query the API using a valid SSL certificate. If you are using self-signed HTTPS then you should leave this with the default value.
\ No newline at end of file
+| api_version | | String | "1.16" | This should be the supported version of the API. Discovery 25.x supports API versions up to 1.16 (outpost 1.0).
+| ssl_verify | | Boolean | False | Choose whether to query the API using a valid SSL certificate. If you are using self-signed HTTPS then you should leave this with the default value.
diff --git a/docs/quickstart/responses.md b/docs/quickstart/responses.md
index e2e5d09..8abdec8 100644
--- a/docs/quickstart/responses.md
+++ b/docs/quickstart/responses.md
@@ -9,67 +9,67 @@ sort: 2
```python
>>> tw = tideway.appliance('appliance-hostname','auth-token',api_version='1.2')
->>> response = tw.about()
+>>> response = tw.api_about
```
## .headers
```python
->>> tw.about().headers
+>>> tw.api_about.headers
{'Date': 'Sun, 06 Jun 2021 18:43:31 GMT', 'Server': 'waitress', 'X-Content-Type-Options': 'nosniff', 'Content-Length': '160', 'Content-Type': 'application/json', 'Content-security-policy': "default-src https: 'self'; style-src https: 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; img-src 'self' data:; base-uri 'none'; object-src 'none'; connect-src https: 'self'; frame-ancestors 'self';", 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive'}
```
## .encoding
```python
->>> tw.about().encoding
+>>> tw.api_about.encoding
None
```
## .elapsed
```python
->>> tw.about().elapsed
+>>> tw.api_about.elapsed
0:00:00.028861
```
## .content
```python
->>> tw.about().content
+>>> tw.api_about.content
b'{\n "api_versions": [\n "1.0",\n "1.1",\n "1.2"\n ],\n "component": "REST API",\n "product": "BMC Discovery",\n "version": "12.2"\n}\n'
```
## .cookies
```python
->>> tw.about().cookies
+>>> tw.api_about.cookies
```
## .history
```python
->>> tw.about().history
+>>> tw.api_about.history
[]
```
## .is_permanent_redirect
```python
->>> tw.about().is_permanent_redirect
+>>> tw.api_about.is_permanent_redirect
False
```
## .is_redirect
```python
->>> tw.about().is_redirect
+>>> tw.api_about.is_redirect
False
```
## .iter_content()
```python
->>> tw.about().iter_content()
+>>> tw.api_about.iter_content()
```
## .json()
```python
->>> tw.about().json()
+>>> tw.api_about.json()
{'api_versions': ['1.0', '1.1', '1.2'], 'component': 'REST API', 'product': 'BMC Discovery', 'version': '12.2'}
```
## .url
```python
->>> tw.about().url
+>>> tw.api_about.url
https://appliance-hostname/api/about
```
## .text
```python
->>> tw.about().text
+>>> tw.api_about.text
{
"api_versions": [
"1.0",
@@ -83,31 +83,31 @@ https://appliance-hostname/api/about
```
## .status_code
```python
->>> tw.about().status_code
+>>> tw.api_about.status_code
200
```
## .request
```python
->>> tw.about().request
+>>> tw.api_about.request
```
## .reason
```python
->>> tw.about().reason
+>>> tw.api_about.reason
OK
```
## .raise_for_status()
```python
->>> tw.about().raise_for_status()
+>>> tw.api_about.raise_for_status()
None
```
## .ok
```python
->>> tw.about().ok
+>>> tw.api_about.ok
True
```
## links
```python
->>> tw.about().links
+>>> tw.api_about.links
{}
```
\ No newline at end of file
diff --git a/notebooks/admin_api.ipynb b/notebooks/admin_api.ipynb
new file mode 100644
index 0000000..7894b46
--- /dev/null
+++ b/notebooks/admin_api.ipynb
@@ -0,0 +1,225 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Admin API Examples\n",
+ "\n",
+ "This notebook shows how to initialise a Tideway appliance client and call common Admin endpoints using the top-level REST wrappers. Replace the placeholder values before running."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Ensure `tideway` is installed in your environment (e.g. `pip install -e .` from the repo root).\n",
+ "- Provide your appliance hostname and API token below. Do **not** commit tokens."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "735d498b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = '' # e.g. 'discovery.example.com'\n",
+ "TOKEN = '' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a113ac85",
+ "metadata": {},
+ "source": [
+ "## Admin calls\n",
+ "Examples of common admin endpoints."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c15278d6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Appliance baseline summary (appliance only - won't work on Helix)\n",
+ "admin_baseline = tw.get('/admin/baseline')\n",
+ "show_response(admin_baseline, '/admin/baseline')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "eb9be7cf",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Appliance details\n",
+ "admin_about = tw.get('/admin/about')\n",
+ "show_response(admin_about, '/admin/about')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cc1acb67",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Licensing endpoints with explicit response types\n",
+ "try:\n",
+ " admin_licensing_text = tw.get('/admin/licensing', response='text/plain')\n",
+ " admin_licensing_json = tw.get('/admin/licensing/json', response='application/json')\n",
+ " admin_licensing_csv = tw.get('/admin/licensing/csv', response='application/zip')\n",
+ " admin_licensing_raw = tw.get('/admin/licensing/raw', response='application/zip')\n",
+ "except TypeError:\n",
+ " admin_licensing_text = tw.get('/admin/licensing')\n",
+ " admin_licensing_json = tw.get('/admin/licensing/json')\n",
+ " admin_licensing_csv = tw.get('/admin/licensing/csv')\n",
+ " admin_licensing_raw = tw.get('/admin/licensing/raw')\n",
+ "\n",
+ "# Render plain text with real line breaks when available\n",
+ "if admin_licensing_text.ok:\n",
+ " print(admin_licensing_text.text)\n",
+ "else:\n",
+ " show_response(admin_licensing_text, '/admin/licensing')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0c38dd77",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Fetch API schema (swagger/openapi)\n",
+ "api_swagger = tw.api_swagger\n",
+ "api_swagger.status_code, api_swagger.ok\n",
+ "\n",
+ "if api_swagger.ok:\n",
+ " schema = api_swagger.json()\n",
+ " list(schema.keys())[:10]\n",
+ "else:\n",
+ " api_swagger.text\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e3c71ce7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Parsed schema via helper\n",
+ "parsed_schema = tw.api_schema()\n",
+ "list(parsed_schema.keys())[:10]\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a3080dc2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Inspect available API paths\n",
+ "paths = tw.api_paths()\n",
+ "list(paths.keys())[:10]\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4d097b99",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Endpoint help (prints to stdout)\n",
+ "tw.api_help\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2546f0ed",
+ "metadata": {},
+ "source": [
+ "## Direct admin GET calls\n",
+ "Raw examples using the generic `get` wrapper without helper methods. Adjust `response` headers as needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1c05acea",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Direct admin endpoints\n",
+ "admin_instance = tw.get('/admin/instance')\n",
+ "admin_cluster = tw.get('/admin/cluster')\n",
+ "admin_organizations = tw.get('/admin/organizations')\n",
+ "admin_preferences = tw.get('/admin/preferences')\n",
+ "admin_builtin_reports = tw.get('/admin/builtin_reports')\n",
+ "admin_custom_reports = tw.get('/admin/custom_reports')\n",
+ "admin_smtp = tw.get('/admin/smtp')\n",
+ "\n",
+ "# Collect all responses with context\n",
+ "admin_results = {\n",
+ " '/admin/instance': show_response(admin_instance, '/admin/instance'),\n",
+ " '/admin/cluster': show_response(admin_cluster, '/admin/cluster'),\n",
+ " '/admin/organizations': show_response(admin_organizations, '/admin/organizations'),\n",
+ " '/admin/preferences': show_response(admin_preferences, '/admin/preferences'),\n",
+ " '/admin/builtin_reports': show_response(admin_builtin_reports, '/admin/builtin_reports'),\n",
+ " '/admin/custom_reports': show_response(admin_custom_reports, '/admin/custom_reports'),\n",
+ " '/admin/smtp': show_response(admin_smtp, '/admin/smtp'),\n",
+ "}\n",
+ "admin_results\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/admin_consolidation.ipynb b/notebooks/admin_consolidation.ipynb
new file mode 100644
index 0000000..08bb925
--- /dev/null
+++ b/notebooks/admin_consolidation.ipynb
@@ -0,0 +1,137 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Admin & Consolidation API Examples\n",
+ "\n",
+ "Using raw `get`/`post` calls (no helper functions) to hit admin and consolidation endpoints. Adjust paths/bodies to match your appliance and the published API (see BMC docs: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Install tideway (e.g. `pip install -e .` from repo root).\n",
+ "- Set `TARGET` and `TOKEN` below. Do **not** commit secrets."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ " \n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Admin examples (raw GET/POST)\n",
+ "Edit the paths as needed; these use `tw.get`/`tw.post` directly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Example admin POST (edit body/endpoint per docs)\n",
+ "sample_preferences_body = {\n",
+ " # Fill in fields per API schema, e.g. 'timezone': 'UTC'\n",
+ "}\n",
+ "if sample_preferences_body:\n",
+ " admin_preferences_post = tw.post('/admin/preferences', sample_preferences_body)\n",
+ " show_response(admin_preferences_post, '/admin/preferences (POST)')\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Consolidation examples (adjust endpoints per docs)\n",
+ "These are raw calls; update paths/bodies to match the consolidation endpoints you are using."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Typical consolidation GETs (edit as needed)\n",
+ "consolidation_status = tw.get('/admin/consolidation/status')\n",
+ "consolidation_targets = tw.get('/admin/consolidation/targets')\n",
+ "consolidation_results = {\n",
+ " '/admin/consolidation/status': show_response(consolidation_status, '/admin/consolidation/status'),\n",
+ " '/admin/consolidation/targets': show_response(consolidation_targets, '/admin/consolidation/targets'),\n",
+ "}\n",
+ "consolidation_results\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Example consolidation POST (edit endpoint/body per docs)\n",
+ "consolidation_body = {\n",
+ " # e.g. 'target': 'consolidation-host', 'action': 'push'\n",
+ "}\n",
+ "if consolidation_body:\n",
+ " consolidation_post = tw.post('/admin/consolidation/actions', consolidation_body)\n",
+ " show_response(consolidation_post, '/admin/consolidation/actions')\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/credentials_api.ipynb b/notebooks/credentials_api.ipynb
new file mode 100644
index 0000000..6d5cd4b
--- /dev/null
+++ b/notebooks/credentials_api.ipynb
@@ -0,0 +1,180 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Credentials API Examples\n",
+ "\n",
+ "Raw `get`/`post`/`patch`/`delete` calls for vault credentials. Adjust endpoints/bodies per your appliance (see BMC docs)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Install tideway (e.g. `pip install -e .` from repo root).\n",
+ "- Set `TARGET` and `TOKEN` below. Do **not** commit secrets."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Credential type endpoints (GET)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# List credential types\n",
+ "cred_types = tw.get('/vault/credential_types')\n",
+ "show_response(cred_types, '/vault/credential_types')\n",
+ "\n",
+ "# Get specific credential type (edit name)\n",
+ "cred_type_name = 'SNMPv2' # example\n",
+ "cred_type = tw.get(f\"/vault/credential_types/{cred_type_name}\")\n",
+ "show_response(cred_type, f\"/vault/credential_types/{cred_type_name}\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Credential endpoints (GET/POST/PATCH/DELETE)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# List credentials\n",
+ "creds = tw.get('/vault/credentials')\n",
+ "show_response(creds, '/vault/credentials')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "937b16bd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "# Get a credential by id\n",
+ "if cred_id:\n",
+ " cred = tw.get(f\"/vault/credentials/{cred_id}\")\n",
+ " show_response(cred, f\"/vault/credentials/{cred_id}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "719c9493",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "# Create a credential (edit body)\n",
+ "new_cred_body = {\n",
+ " # 'name': 'example', 'type': 'SNMPv2', 'attributes': {...}\n",
+ "}\n",
+ "if new_cred_body:\n",
+ " created = tw.post('/vault/credentials', new_cred_body)\n",
+ " show_response(created, '/vault/credentials (POST)')\n",
+ " cred_id = created.json().get('id') if created.ok else None\n",
+ "else:\n",
+ " cred_id = None\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8436d9f9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "# Patch a credential (edit body)\n",
+ "patch_body = {\n",
+ " # 'attributes': {'community': 'public'}\n",
+ "}\n",
+ "if cred_id and patch_body:\n",
+ " patched = tw.patch(f\"/vault/credentials/{cred_id}\", patch_body)\n",
+ " show_response(patched, f\"/vault/credentials/{cred_id} (PATCH)\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "69333ed9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "# Delete a credential\n",
+ "if cred_id:\n",
+ " deleted = tw.delete(f\"/vault/credentials/{cred_id}\")\n",
+ " show_response(deleted, f\"/vault/credentials/{cred_id} (DELETE)\")\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/data_api.ipynb b/notebooks/data_api.ipynb
new file mode 100644
index 0000000..2fec02a
--- /dev/null
+++ b/notebooks/data_api.ipynb
@@ -0,0 +1,369 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Data API Examples\n",
+ "\n",
+ "Data search, node lookups, kinds, and partitions via `tideway.data`. Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Install tideway (e.g. `pip install -e .` from repo root).\n",
+ "- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n",
+ "- Adjust queries/endpoints to match your appliance.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d0c6fdc6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "from urllib.parse import quote\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "data = tw.data()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "78471131",
+ "metadata": {},
+ "source": [
+ "## Data search endpoints\n",
+ "- Use TPL search syntax for `/data/search`.\n",
+ "- `format` supports `object`, `tree`, or default tabular rows.\n",
+ "- BMC doc reference above for query capabilities.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ac07b485",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# GET /data/search with query string\n",
+ "search_query = \"search Host show name, os_type, ip_addr\" # edit query per docs\n",
+ "search_query_encoded = quote(search_query)\n",
+ "\n",
+ "helper_search = data.search(search_query, format=\"object\", limit=50, bulk=False)\n",
+ "direct_search = data.get(f\"/data/search?query={search_query_encoded}&format=object&limit=50\")\n",
+ "\n",
+ "search_calls = {\n",
+ " \"helper\": show_response(helper_search, \"/data/search?format=object\"),\n",
+ " \"direct_get\": show_response(direct_search, \"/data/search (GET)\"),\n",
+ "}\n",
+ "search_calls\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4b91e755",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Bulk search to iterate through paginated results\n",
+ "bulk_results = data.search_bulk(search_query, format=\"object\", limit=200, record_limit=200)\n",
+ "\n",
+ "if hasattr(bulk_results, \"ok\"):\n",
+ " bulk_preview = show_response(bulk_results, \"/data/search bulk\")\n",
+ "else:\n",
+ " bulk_preview = bulk_results[:5] # preview first rows/objects\n",
+ "\n",
+ "direct_first_page = data.get(f\"/data/search?query={search_query_encoded}&format=object&limit=200\")\n",
+ "\n",
+ "bulk_calls = {\n",
+ " \"helper_bulk_preview\": bulk_preview,\n",
+ " \"direct_first_page\": show_response(direct_first_page, \"/data/search (GET, first page)\"),\n",
+ "}\n",
+ "bulk_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0efaa56a",
+ "metadata": {},
+ "source": [
+ "## POST /data/search (long queries or alternate formats)\n",
+ "Use POST when the search string is long or when sending JSON payloads.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "109f3ae1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "long_query_body = {\n",
+ " \"query\": \"search SoftwareInstance where type matches \\\"Database\\\" show name, version, host.name\"\n",
+ "}\n",
+ "\n",
+ "helper_long_query = data.search(long_query_body, format=\"tree\", limit=50, bulk=False)\n",
+ "direct_long_query = data.post('/data/search', long_query_body)\n",
+ "\n",
+ "long_query_calls = {\n",
+ " \"/data/search (helper, tree)\": show_response(helper_long_query, \"/data/search (POST, tree)\"),\n",
+ " \"/data/search (direct POST)\": show_response(direct_long_query, \"/data/search (POST)\"),\n",
+ "}\n",
+ "long_query_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7b7eac0d",
+ "metadata": {},
+ "source": [
+ "## Condition endpoints\n",
+ "- Discover available condition parameters/templates first.\n",
+ "- Fill `condition_body` with a condition and attributes per the API schema.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "43ce63d4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "condition_params = data.get_data_condition_params()\n",
+ "condition_templates = data.get_data_condition_templates\n",
+ "\n",
+ "direct_condition_params = data.get('/data/condition/params')\n",
+ "direct_condition_templates = data.get('/data/condition/templates')\n",
+ "\n",
+ "condition_meta = {\n",
+ " 'helpers': {\n",
+ " '/data/condition/params': show_response(condition_params, '/data/condition/params'),\n",
+ " '/data/condition/templates': show_response(condition_templates, '/data/condition/templates'),\n",
+ " },\n",
+ " 'direct': {\n",
+ " '/data/condition/params (GET)': show_response(direct_condition_params, '/data/condition/params'),\n",
+ " '/data/condition/templates (GET)': show_response(direct_condition_templates, '/data/condition/templates'),\n",
+ " }\n",
+ "}\n",
+ "condition_meta\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ded2f1e4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "condition_body = {\n",
+ " # Example shape - adjust to your condition\n",
+ " # \"condition\": {\"attribute\": \"Host:os\", \"operator\": \"=\", \"value\": \"Linux\"},\n",
+ " # \"attributes\": [\"name\", \"os\", \"serial_number\"],\n",
+ " # \"parameters\": {},\n",
+ "}\n",
+ "\n",
+ "if condition_body:\n",
+ " condition_results_helper = data.post_data_condition(condition_body)\n",
+ " condition_results_direct = data.post('/data/condition', condition_body)\n",
+ " condition_results = {\n",
+ " 'helper': show_response(condition_results_helper, \"/data/condition (helper)\"),\n",
+ " 'direct': show_response(condition_results_direct, \"/data/condition (direct POST)\"),\n",
+ " }\n",
+ "else:\n",
+ " condition_results = \"Set condition_body to post /data/condition\"\n",
+ "condition_results\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d7c84536",
+ "metadata": {},
+ "source": [
+ "## Node lookups and graphs\n",
+ "Set `node_id` to inspect node state and graph relationships.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "40acb372",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "node_id = '' # e.g. '1234567890abcdef'\n",
+ "\n",
+ "if node_id:\n",
+ " node_details_helper = data.get_data_nodes(node_id, relationships=True)\n",
+ " node_graph_helper = data.get_data_nodes_graph(node_id, focus=\"software-connected\", complete=False)\n",
+ "\n",
+ " node_details_direct = data.get(f\"/data/nodes/{node_id}?relationships=true\")\n",
+ " node_graph_direct = data.get(f\"/data/nodes/{node_id}/graph?focus=software-connected&complete=false\")\n",
+ "\n",
+ " node_info = {\n",
+ " f\"/data/nodes/{node_id} (helper)\": show_response(node_details_helper, f\"/data/nodes/{node_id}?relationships=true\"),\n",
+ " f\"/data/nodes/{node_id}/graph (helper)\": show_response(node_graph_helper, f\"/data/nodes/{node_id}/graph\"),\n",
+ " f\"/data/nodes/{node_id} (direct)\": show_response(node_details_direct, f\"/data/nodes/{node_id}?relationships=true\"),\n",
+ " f\"/data/nodes/{node_id}/graph (direct)\": show_response(node_graph_direct, f\"/data/nodes/{node_id}/graph\"),\n",
+ " }\n",
+ "else:\n",
+ " node_info = \"Set node_id to inspect node details.\"\n",
+ "node_info\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e8262bc",
+ "metadata": {},
+ "source": [
+ "## Node kinds and attribute values\n",
+ "Use these to inspect specific node kinds and enumerate attribute values.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6770c398",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "kind = 'Host' # edit node kind per docs\n",
+ "attribute = 'os' # attribute to enumerate values for\n",
+ "\n",
+ "kind_resp_helper = data.get_data_kinds(kind, format=\"object\", limit=25)\n",
+ "kind_values_resp_helper = data.get_data_kinds_values(kind, attribute)\n",
+ "\n",
+ "kind_resp_direct = data.get(f\"/data/kinds/{kind}?format=object&limit=25\")\n",
+ "kind_values_resp_direct = data.get(f\"/data/kinds/{kind}/values/{attribute}\")\n",
+ "\n",
+ "kind_calls = {\n",
+ " f\"/data/kinds/{kind} (helper)\": show_response(kind_resp_helper, f\"/data/kinds/{kind}?format=object\"),\n",
+ " f\"/data/kinds/{kind} (direct)\": show_response(kind_resp_direct, f\"/data/kinds/{kind}?format=object&limit=25\"),\n",
+ " f\"/data/kinds/{kind}/values/{attribute} (helper)\": show_response(kind_values_resp_helper, f\"/data/kinds/{kind}/values/{attribute}\"),\n",
+ " f\"/data/kinds/{kind}/values/{attribute} (direct)\": show_response(kind_values_resp_direct, f\"/data/kinds/{kind}/values/{attribute}\"),\n",
+ "}\n",
+ "kind_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "53aa2f36",
+ "metadata": {},
+ "source": [
+ "## Partitions, candidates, and external consumers\n",
+ "GET calls are safe to run; POST bodies are placeholders until filled.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "eb528ce2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "partitions = data.get_data_partitions\n",
+ "external_consumers = data.get_data_external_consumers\n",
+ "\n",
+ "direct_partitions = data.get('/data/partitions')\n",
+ "direct_external_consumers = data.get('/data/external_consumers')\n",
+ "\n",
+ "data_admin_calls = {\n",
+ " '/data/partitions (helper)': show_response(partitions, '/data/partitions'),\n",
+ " '/data/partitions (direct)': show_response(direct_partitions, '/data/partitions'),\n",
+ " '/data/external_consumers (helper)': show_response(external_consumers, '/data/external_consumers'),\n",
+ " '/data/external_consumers (direct)': show_response(direct_external_consumers, '/data/external_consumers'),\n",
+ "}\n",
+ "data_admin_calls\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "35542e1e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "partition_body = {\n",
+ " # 'name': 'example_partition',\n",
+ " # 'label': 'Example partition description',\n",
+ "}\n",
+ "candidate_body = {\n",
+ " # 'ip': '10.0.0.1',\n",
+ " # 'dns_name': 'host.example.com',\n",
+ "}\n",
+ "external_consumer_body = {\n",
+ " # 'name': 'consumer_id',\n",
+ " # 'endpoint': 'https://example.com/ingest',\n",
+ "}\n",
+ "\n",
+ "post_responses = {}\n",
+ "if partition_body:\n",
+ " created_partition_helper = data.post_data_partitions(partition_body)\n",
+ " created_partition_direct = data.post('/data/partitions', partition_body)\n",
+ " post_responses['/data/partitions (helper POST)'] = show_response(created_partition_helper, '/data/partitions (POST)')\n",
+ " post_responses['/data/partitions (direct POST)'] = show_response(created_partition_direct, '/data/partitions (POST)')\n",
+ "\n",
+ "if candidate_body:\n",
+ " best_candidate_helper = data.post_data_candidate(candidate_body)\n",
+ " best_candidate_direct = data.post('/data/candidate', candidate_body)\n",
+ " post_responses['/data/candidate (helper POST)'] = show_response(best_candidate_helper, '/data/candidate')\n",
+ " post_responses['/data/candidate (direct POST)'] = show_response(best_candidate_direct, '/data/candidate')\n",
+ "\n",
+ "if external_consumer_body:\n",
+ " created_consumer_helper = data.post_data_external_consumer(external_consumer_body)\n",
+ " created_consumer_direct = data.post('/data/external_consumers', external_consumer_body)\n",
+ " post_responses['/data/external_consumers (helper POST)'] = show_response(created_consumer_helper, '/data/external_consumers')\n",
+ " post_responses['/data/external_consumers (direct POST)'] = show_response(created_consumer_direct, '/data/external_consumers')\n",
+ "\n",
+ "post_responses or \"Set request bodies to run POST examples.\"\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/discovery_api.ipynb b/notebooks/discovery_api.ipynb
new file mode 100644
index 0000000..61a33fc
--- /dev/null
+++ b/notebooks/discovery_api.ipynb
@@ -0,0 +1,380 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Discovery API Examples\n",
+ "\n",
+ "Helper methods paired with direct `get`/`post`/`patch` calls for discovery endpoints. Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Install tideway (e.g. `pip install -e .` from repo root).\n",
+ "- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n",
+ "- Adjust request bodies to match your appliance and the published API.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "discovery = tw.discovery()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Discovery status\n",
+ "Helper vs direct calls for the discovery state.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "status_helper = discovery.get_discovery\n",
+ "status_direct = discovery.get('/discovery')\n",
+ "\n",
+ "status_calls = {\n",
+ " '/discovery (helper)': show_response(status_helper, '/discovery'),\n",
+ " '/discovery (direct GET)': show_response(status_direct, '/discovery'),\n",
+ "}\n",
+ "status_calls\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "discovery_patch_body = {\n",
+ " # 'state': 'start', # or 'stop'\n",
+ " # 'reason': 'Maintenance complete',\n",
+ "}\n",
+ "\n",
+ "if discovery_patch_body:\n",
+ " discovery_patch_helper = discovery.patch_discovery(discovery_patch_body)\n",
+ " discovery_patch_direct = discovery.patch('/discovery', discovery_patch_body)\n",
+ " discovery_patch_calls = {\n",
+ " '/discovery (helper PATCH)': show_response(discovery_patch_helper, '/discovery'),\n",
+ " '/discovery (direct PATCH)': show_response(discovery_patch_direct, '/discovery'),\n",
+ " }\n",
+ "else:\n",
+ " discovery_patch_calls = 'Set discovery_patch_body to start/stop discovery'\n",
+ "discovery_patch_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Provider and cloud metadata\n",
+ "Metadata helpers vs direct calls.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "provider_meta_helper = discovery.get_discovery_api_provider_metadata\n",
+ "provider_meta_direct = discovery.get('/discovery/api_provider_metadata')\n",
+ "\n",
+ "cloud_meta_helper = discovery.get_discovery_api_cloud_metadata\n",
+ "cloud_meta_direct = discovery.get('/discovery/cloud_metadata')\n",
+ "\n",
+ "metadata_calls = {\n",
+ " '/discovery/api_provider_metadata (helper)': show_response(provider_meta_helper, '/discovery/api_provider_metadata'),\n",
+ " '/discovery/api_provider_metadata (direct)': show_response(provider_meta_direct, '/discovery/api_provider_metadata'),\n",
+ " '/discovery/cloud_metadata (helper)': show_response(cloud_meta_helper, '/discovery/cloud_metadata'),\n",
+ " '/discovery/cloud_metadata (direct)': show_response(cloud_meta_direct, '/discovery/cloud_metadata'),\n",
+ "}\n",
+ "metadata_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Excludes\n",
+ "List and manage excludes via helpers and direct calls.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "excludes_list_helper = discovery.get_discovery_excludes\n",
+ "excludes_list_direct = discovery.get('/discovery/excludes')\n",
+ "\n",
+ "exclude_calls = {\n",
+ " '/discovery/excludes (helper)': show_response(excludes_list_helper, '/discovery/excludes'),\n",
+ " '/discovery/excludes (direct)': show_response(excludes_list_direct, '/discovery/excludes'),\n",
+ "}\n",
+ "\n",
+ "exclude_body = {\n",
+ " # 'value': '10.0.0.0/24',\n",
+ " # 'reason': 'Example exclude',\n",
+ "}\n",
+ "exclude_id = '' # set to an existing exclude id for patch/delete\n",
+ "\n",
+ "if exclude_body:\n",
+ " exclude_post_helper = discovery.post_discovery_exclude(exclude_body)\n",
+ " exclude_post_direct = discovery.post('/discovery/excludes', exclude_body)\n",
+ " exclude_calls['/discovery/excludes (helper POST)'] = show_response(exclude_post_helper, '/discovery/excludes')\n",
+ " exclude_calls['/discovery/excludes (direct POST)'] = show_response(exclude_post_direct, '/discovery/excludes')\n",
+ "\n",
+ "if exclude_id and exclude_body:\n",
+ " exclude_patch_helper = discovery.patch_discovery_exclude(exclude_id, exclude_body)\n",
+ " exclude_patch_direct = discovery.patch(f\"/discovery/excludes/{exclude_id}\", exclude_body)\n",
+ " exclude_calls[f\"/discovery/excludes/{exclude_id} (helper PATCH)\"] = show_response(exclude_patch_helper, f\"/discovery/excludes/{exclude_id}\")\n",
+ " exclude_calls[f\"/discovery/excludes/{exclude_id} (direct PATCH)\"] = show_response(exclude_patch_direct, f\"/discovery/excludes/{exclude_id}\")\n",
+ "\n",
+ "if exclude_id:\n",
+ " exclude_delete_helper = discovery.delete_discovery_exclude(exclude_id)\n",
+ " exclude_delete_direct = discovery.delete(f\"/discovery/excludes/{exclude_id}\")\n",
+ " exclude_calls[f\"/discovery/excludes/{exclude_id} (helper DELETE)\"] = show_response(exclude_delete_helper, f\"/discovery/excludes/{exclude_id}\")\n",
+ " exclude_calls[f\"/discovery/excludes/{exclude_id} (direct DELETE)\"] = show_response(exclude_delete_direct, f\"/discovery/excludes/{exclude_id}\")\n",
+ "\n",
+ "exclude_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Discovery runs\n",
+ "List runs, create or update run state via helper vs direct calls.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "runs_helper = discovery.get_discovery_runs\n",
+ "runs_direct = discovery.get('/discovery/runs')\n",
+ "\n",
+ "run_body = {\n",
+ " # 'type': 'snapshot',\n",
+ " # 'label': 'Example run',\n",
+ " # 'endpoints': ['10.0.0.0/24'],\n",
+ "}\n",
+ "run_patch_body = {\n",
+ " # 'state': 'cancelled',\n",
+ "}\n",
+ "run_id = '' # set to an existing run id for patching\n",
+ "\n",
+ "run_calls = {\n",
+ " '/discovery/runs (helper)': show_response(runs_helper, '/discovery/runs'),\n",
+ " '/discovery/runs (direct)': show_response(runs_direct, '/discovery/runs'),\n",
+ "}\n",
+ "\n",
+ "if run_body:\n",
+ " run_post_helper = discovery.post_discovery_run(run_body)\n",
+ " run_post_direct = discovery.post('/discovery/runs', run_body)\n",
+ " run_calls['/discovery/runs (helper POST)'] = show_response(run_post_helper, '/discovery/runs')\n",
+ " run_calls['/discovery/runs (direct POST)'] = show_response(run_post_direct, '/discovery/runs')\n",
+ "\n",
+ "if run_id and run_patch_body:\n",
+ " run_patch_helper = discovery.patch_discovery_run(run_id, run_patch_body)\n",
+ " run_patch_direct = discovery.patch(f\"/discovery/runs/{run_id}\", run_patch_body)\n",
+ " run_calls[f\"/discovery/runs/{run_id} (helper PATCH)\"] = show_response(run_patch_helper, f\"/discovery/runs/{run_id}\")\n",
+ " run_calls[f\"/discovery/runs/{run_id} (direct PATCH)\"] = show_response(run_patch_direct, f\"/discovery/runs/{run_id}\")\n",
+ "\n",
+ "run_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Run results and inferred devices\n",
+ "Requires a `run_id`; choose result type or inferred kind per docs.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "run_id_for_results = '' # e.g. '1234567890abcdef'\n",
+ "result_type = 'Success'\n",
+ "inferred_kind = '' # e.g. 'Host'\n",
+ "\n",
+ "if run_id_for_results:\n",
+ " run_results_helper = discovery.get_discovery_run_results(run_id_for_results, result_type)\n",
+ " run_results_direct = discovery.get(f\"/discovery/runs/{run_id_for_results}/results/{result_type}\")\n",
+ "\n",
+ " inferred_helper = discovery.get_discovery_run_inferred(run_id_for_results, inferred_kind)\n",
+ " inferred_direct = discovery.get(f\"/discovery/runs/{run_id_for_results}/inferred\" + (f\"/{inferred_kind}\" if inferred_kind else ''))\n",
+ "\n",
+ " results_calls = {\n",
+ " f\"/discovery/runs/{run_id_for_results}/results/{result_type} (helper)\": show_response(run_results_helper, f\"/discovery/runs/{run_id_for_results}/results/{result_type}\"),\n",
+ " f\"/discovery/runs/{run_id_for_results}/results/{result_type} (direct)\": show_response(run_results_direct, f\"/discovery/runs/{run_id_for_results}/results/{result_type}\"),\n",
+ " f\"/discovery/runs/{run_id_for_results}/inferred (helper)\": show_response(inferred_helper, f\"/discovery/runs/{run_id_for_results}/inferred\"),\n",
+ " f\"/discovery/runs/{run_id_for_results}/inferred (direct)\": show_response(inferred_direct, f\"/discovery/runs/{run_id_for_results}/inferred\"),\n",
+ " }\n",
+ "else:\n",
+ " results_calls = 'Set run_id_for_results to inspect results and inferred devices.'\n",
+ "results_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Scheduled runs\n",
+ "List, create, update, or delete scheduled runs (helper vs direct).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "schedules_helper = discovery.get_discovery_run_schedules\n",
+ "schedules_direct = discovery.get('/discovery/runs/scheduled')\n",
+ "\n",
+ "schedule_body = {\n",
+ " # 'label': 'Weekly scan',\n",
+ " # 'cron': '0 2 * * 1',\n",
+ " # 'endpoints': ['10.0.0.0/24'],\n",
+ "}\n",
+ "schedule_patch_body = {\n",
+ " # 'label': 'Updated label',\n",
+ "}\n",
+ "schedule_id = '' # set to a scheduled run id for patch/delete\n",
+ "\n",
+ "schedule_calls = {\n",
+ " '/discovery/runs/scheduled (helper)': show_response(schedules_helper, '/discovery/runs/scheduled'),\n",
+ " '/discovery/runs/scheduled (direct)': show_response(schedules_direct, '/discovery/runs/scheduled'),\n",
+ "}\n",
+ "\n",
+ "if schedule_body:\n",
+ " schedule_post_helper = discovery.post_discovery_run_schedule(schedule_body)\n",
+ " schedule_post_direct = discovery.post('/discovery/runs/scheduled', schedule_body)\n",
+ " schedule_calls['/discovery/runs/scheduled (helper POST)'] = show_response(schedule_post_helper, '/discovery/runs/scheduled')\n",
+ " schedule_calls['/discovery/runs/scheduled (direct POST)'] = show_response(schedule_post_direct, '/discovery/runs/scheduled')\n",
+ "\n",
+ "if schedule_id and schedule_patch_body:\n",
+ " schedule_patch_helper = discovery.patch_discovery_run_schedule(schedule_id, schedule_patch_body)\n",
+ " schedule_patch_direct = discovery.patch(f\"/discovery/runs/scheduled/{schedule_id}\", schedule_patch_body)\n",
+ " schedule_calls[f\"/discovery/runs/scheduled/{schedule_id} (helper PATCH)\"] = show_response(schedule_patch_helper, f\"/discovery/runs/scheduled/{schedule_id}\")\n",
+ " schedule_calls[f\"/discovery/runs/scheduled/{schedule_id} (direct PATCH)\"] = show_response(schedule_patch_direct, f\"/discovery/runs/scheduled/{schedule_id}\")\n",
+ "\n",
+ "if schedule_id:\n",
+ " schedule_delete_helper = discovery.delete_discovery_run_schedule(schedule_id)\n",
+ " schedule_delete_direct = discovery.delete(f\"/discovery/runs/scheduled/{schedule_id}\")\n",
+ " schedule_calls[f\"/discovery/runs/scheduled/{schedule_id} (helper DELETE)\"] = show_response(schedule_delete_helper, f\"/discovery/runs/scheduled/{schedule_id}\")\n",
+ " schedule_calls[f\"/discovery/runs/scheduled/{schedule_id} (direct DELETE)\"] = show_response(schedule_delete_direct, f\"/discovery/runs/scheduled/{schedule_id}\")\n",
+ "\n",
+ "schedule_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Outposts\n",
+ "List, create, or delete Outposts via helpers vs direct calls.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "outposts_helper = discovery.get_discovery_outposts\n",
+ "outposts_direct = discovery.get('/discovery/outposts')\n",
+ "\n",
+ "outpost_body = {\n",
+ " # 'name': 'example-outpost',\n",
+ " # 'address': 'outpost.example.com',\n",
+ "}\n",
+ "outpost_id = '' # set to an existing outpost id for delete\n",
+ "\n",
+ "outpost_calls = {\n",
+ " '/discovery/outposts (helper)': show_response(outposts_helper, '/discovery/outposts'),\n",
+ " '/discovery/outposts (direct)': show_response(outposts_direct, '/discovery/outposts'),\n",
+ "}\n",
+ "\n",
+ "if outpost_body:\n",
+ " outpost_post_helper = discovery.post_discovery_outpost(outpost_body)\n",
+ " outpost_post_direct = discovery.post('/discovery/outposts', outpost_body)\n",
+ " outpost_calls['/discovery/outposts (helper POST)'] = show_response(outpost_post_helper, '/discovery/outposts')\n",
+ " outpost_calls['/discovery/outposts (direct POST)'] = show_response(outpost_post_direct, '/discovery/outposts')\n",
+ "\n",
+ "if outpost_id:\n",
+ " outpost_delete_helper = discovery.delete_discovery_outpost(outpost_id)\n",
+ " outpost_delete_direct = discovery.delete(f\"/discovery/outposts/{outpost_id}\")\n",
+ " outpost_calls[f\"/discovery/outposts/{outpost_id} (helper DELETE)\"] = show_response(outpost_delete_helper, f\"/discovery/outposts/{outpost_id}\")\n",
+ " outpost_calls[f\"/discovery/outposts/{outpost_id} (direct DELETE)\"] = show_response(outpost_delete_direct, f\"/discovery/outposts/{outpost_id}\")\n",
+ "\n",
+ "outpost_calls\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/events_knowledge_api.ipynb b/notebooks/events_knowledge_api.ipynb
new file mode 100644
index 0000000..583d2d8
--- /dev/null
+++ b/notebooks/events_knowledge_api.ipynb
@@ -0,0 +1,205 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Events & Knowledge API Examples\n",
+ "\n",
+ "Helper methods paired with direct `get`/`post` calls for events and knowledge endpoints. Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "- Install tideway (e.g. `pip install -e .` from repo root).\n",
+ "- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n",
+ "- Adjust request bodies and file paths to match your appliance.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tideway\n",
+ "from pathlib import Path\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "events = tw.events()\n",
+ "knowledge = tw.knowledge()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Events\n",
+ "Send events using helper vs direct POST.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "event_body = {\n",
+ " # 'status': 'warning',\n",
+ " # 'message': 'Example event from notebook',\n",
+ " # 'source': 'notebook',\n",
+ "}\n",
+ "\n",
+ "if event_body:\n",
+ " event_helper = events.post_events(event_body)\n",
+ " event_direct = events.post('/events', event_body)\n",
+ " event_calls = {\n",
+ " '/events (helper POST)': show_response(event_helper, '/events'),\n",
+ " '/events (direct POST)': show_response(event_direct, '/events'),\n",
+ " }\n",
+ "else:\n",
+ " event_calls = 'Set event_body to post /events.'\n",
+ "event_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Knowledge state\n",
+ "Helpers vs direct GET for knowledge info and upload status.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "knowledge_helper = knowledge.get_knowledge_property\n",
+ "knowledge_direct = knowledge.get('/knowledge')\n",
+ "\n",
+ "knowledge_status_helper = knowledge.get_knowledge_status_property\n",
+ "knowledge_status_direct = knowledge.get('/knowledge/status')\n",
+ "\n",
+ "knowledge_calls = {\n",
+ " '/knowledge (helper)': show_response(knowledge_helper, '/knowledge'),\n",
+ " '/knowledge (direct GET)': show_response(knowledge_direct, '/knowledge'),\n",
+ " '/knowledge/status (helper)': show_response(knowledge_status_helper, '/knowledge/status'),\n",
+ " '/knowledge/status (direct GET)': show_response(knowledge_status_direct, '/knowledge/status'),\n",
+ "}\n",
+ "knowledge_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Upload knowledge (TKU/pattern module)\n",
+ "Provide a filename and local path to upload; set `activate`/`allow_restart` flags per your process.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "upload_filename = 'tku.zip' # name shown in the endpoint URL\n",
+ "upload_path = '' # local path to the TKU file\n",
+ "activate = True\n",
+ "allow_restart = False\n",
+ "\n",
+ "if upload_path and Path(upload_path).exists():\n",
+ " upload_helper = knowledge.post_knowledge(upload_filename, upload_path, activate=activate, allow_restart=allow_restart)\n",
+ " # Direct call mirrors the helper: set params then send multipart file\n",
+ " knowledge.params['activate'] = activate\n",
+ " knowledge.params['allow_restart'] = allow_restart\n",
+ " with open(upload_path, 'rb') as upload_file:\n",
+ " upload_direct = knowledge.post(f\"/knowledge/{upload_filename}\", files={'file': upload_file}, response='text/html')\n",
+ " upload_calls = {\n",
+ " f\"/knowledge/{upload_filename} (helper POST)\": show_response(upload_helper, f\"/knowledge/{upload_filename}\"),\n",
+ " f\"/knowledge/{upload_filename} (direct POST)\": show_response(upload_direct, f\"/knowledge/{upload_filename}\"),\n",
+ " }\n",
+ "else:\n",
+ " upload_calls = 'Set upload_path to a TKU file to post /knowledge/{filename}.'\n",
+ "upload_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Trigger patterns\n",
+ "List knowledge trigger patterns via helper vs direct GET.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lookup_data_sources = None # e.g. True to include lookup data sources\n",
+ "\n",
+ "# Helper allows passing lookup_data_sources param\n",
+ "trigger_patterns_helper = knowledge.get_knowledge_trigger_patterns(lookup_data_sources=lookup_data_sources)\n",
+ "\n",
+ "# Direct GET (set params manually if you need lookup_data_sources)\n",
+ "if lookup_data_sources is not None:\n",
+ " knowledge.params['lookup_data_sources'] = lookup_data_sources\n",
+ "trigger_patterns_direct = knowledge.get('/knowledge/trigger_patterns')\n",
+ "\n",
+ "trigger_calls = {\n",
+ " '/knowledge/trigger_patterns (helper)': show_response(trigger_patterns_helper, '/knowledge/trigger_patterns'),\n",
+ " '/knowledge/trigger_patterns (direct)': show_response(trigger_patterns_direct, '/knowledge/trigger_patterns'),\n",
+ "}\n",
+ "trigger_calls\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/models_api.ipynb b/notebooks/models_api.ipynb
new file mode 100644
index 0000000..9a4b21a
--- /dev/null
+++ b/notebooks/models_api.ipynb
@@ -0,0 +1,276 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Models API Examples\n\nHelper methods paired with direct calls for model CRUD, topology, and node queries. Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n- Install tideway (e.g. `pip install -e .` from repo root).\n- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n- Adjust request bodies/ids per your appliance models."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "models = tw.models()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## List or filter models\nHelper vs direct GET with optional filters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "model_filters = {\n",
+ " # 'name': 'Example',\n",
+ " # 'type': 'business_service',\n",
+ " # 'kind': 'custom',\n",
+ "}\n",
+ "\n",
+ "models_helper = models.get_model(**model_filters)\n",
+ "models_direct = models.get('/models')\n",
+ "\n",
+ "model_list_calls = {\n",
+ " '/models (helper)': show_response(models_helper, '/models'),\n",
+ " '/models (direct GET)': show_response(models_direct, '/models'),\n",
+ "}\n",
+ "model_list_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Create, update, delete models\nProvide bodies/ids to exercise helper vs direct calls."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "model_body = {\n",
+ " # 'name': 'Example Model',\n",
+ " # 'type': 'business_service',\n",
+ " # 'definition': {},\n",
+ "}\n",
+ "model_key = '' # set to a model key for patch/delete\n",
+ "model_patch_body = {\n",
+ " # 'favorite': True,\n",
+ "}\n",
+ "\n",
+ "model_calls = {}\n",
+ "\n",
+ "if model_body:\n",
+ " create_helper = models.post_model(model_body)\n",
+ " create_direct = models.post('/models', model_body)\n",
+ " model_calls['/models (helper POST)'] = show_response(create_helper, '/models')\n",
+ " model_calls['/models (direct POST)'] = show_response(create_direct, '/models')\n",
+ "\n",
+ "if model_key and model_patch_body:\n",
+ " patch_helper = models.patch_model(model_key, model_patch_body)\n",
+ " patch_direct = models.patch(f\"/models/{model_key}\", model_patch_body)\n",
+ " model_calls[f\"/models/{model_key} (helper PATCH)\"] = show_response(patch_helper, f\"/models/{model_key}\")\n",
+ " model_calls[f\"/models/{model_key} (direct PATCH)\"] = show_response(patch_direct, f\"/models/{model_key}\")\n",
+ "\n",
+ "if model_key:\n",
+ " delete_helper = models.delete_model(model_key)\n",
+ " delete_direct = models.delete(f\"/models/{model_key}\")\n",
+ " model_calls[f\"/models/{model_key} (helper DELETE)\"] = show_response(delete_helper, f\"/models/{model_key}\")\n",
+ " model_calls[f\"/models/{model_key} (direct DELETE)\"] = show_response(delete_direct, f\"/models/{model_key}\")\n",
+ "\n",
+ "model_calls or 'Set model_body/model_key to run create/update/delete.'\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Bulk/multi model operations\nUse `/models/multi` for multi-action payloads."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "multi_body = {\n",
+ " # 'create': [ ... ],\n",
+ " # 'update': [ ... ],\n",
+ "}\n",
+ "\n",
+ "if multi_body:\n",
+ " multi_helper = models.post_model_multi(multi_body)\n",
+ " multi_direct = models.post('/models/multi', multi_body)\n",
+ " multi_calls = {\n",
+ " '/models/multi (helper POST)': show_response(multi_helper, '/models/multi'),\n",
+ " '/models/multi (direct POST)': show_response(multi_direct, '/models/multi'),\n",
+ " }\n",
+ "else:\n",
+ " multi_calls = 'Set multi_body to post /models/multi.'\n",
+ "multi_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Model details by key\nHelper vs direct for model definition and topology/node info."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "model_key_lookup = '' # e.g. 'Model-1234'\n",
+ "attributes = None # e.g. 'name,kind' for topology\n",
+ "node_kind = None # e.g. 'Host'\n",
+ "\n",
+ "if model_key_lookup:\n",
+ " model_def_helper = models.get_model_key(model_key_lookup)\n",
+ " model_def_direct = models.get(f\"/models/{model_key_lookup}\")\n",
+ "\n",
+ " if attributes:\n",
+ " models.params['attributes'] = attributes\n",
+ " topo_helper = models.get_model_topology(model_key_lookup, attributes=attributes)\n",
+ " topo_direct = models.get(f\"/models/{model_key_lookup}/topology\")\n",
+ "\n",
+ " nodecount_helper = models.get_model_nodecount(model_key_lookup)\n",
+ " nodecount_direct = models.get(f\"/models/{model_key_lookup}/nodecount\")\n",
+ "\n",
+ " nodes_helper = models.get_model_nodes(model_key_lookup, format='object', limit=25, kind=node_kind)\n",
+ " nodes_direct = models.get(f\"/models/{model_key_lookup}/nodes\" + (f\"/{node_kind}\" if node_kind else ''))\n",
+ "\n",
+ " model_detail_calls = {\n",
+ " f\"/models/{model_key_lookup} (helper)\": show_response(model_def_helper, f\"/models/{model_key_lookup}\"),\n",
+ " f\"/models/{model_key_lookup} (direct)\": show_response(model_def_direct, f\"/models/{model_key_lookup}\"),\n",
+ " f\"/models/{model_key_lookup}/topology (helper)\": show_response(topo_helper, f\"/models/{model_key_lookup}/topology\"),\n",
+ " f\"/models/{model_key_lookup}/topology (direct)\": show_response(topo_direct, f\"/models/{model_key_lookup}/topology\"),\n",
+ " f\"/models/{model_key_lookup}/nodecount (helper)\": show_response(nodecount_helper, f\"/models/{model_key_lookup}/nodecount\"),\n",
+ " f\"/models/{model_key_lookup}/nodecount (direct)\": show_response(nodecount_direct, f\"/models/{model_key_lookup}/nodecount\"),\n",
+ " f\"/models/{model_key_lookup}/nodes (helper)\": show_response(nodes_helper, f\"/models/{model_key_lookup}/nodes\"),\n",
+ " f\"/models/{model_key_lookup}/nodes (direct)\": show_response(nodes_direct, f\"/models/{model_key_lookup}/nodes\"),\n",
+ " }\n",
+ "else:\n",
+ " model_detail_calls = 'Set model_key_lookup to inspect a model definition/topology/nodes.'\n",
+ "model_detail_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Model lookup by node id\nHelper vs direct for models tied to a node id."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "node_id = '' # e.g. '1234567890abcdef'\n",
+ "expand_related = None # e.g. True\n",
+ "node_kind = None # optional kind filter for nodes\n",
+ "\n",
+ "if node_id:\n",
+ " model_by_node_helper = models.get_model_by_node_id(node_id, expand_related=expand_related)\n",
+ " model_by_node_direct = models.get(f\"/models/by_node_id/{node_id}\")\n",
+ "\n",
+ " if expand_related:\n",
+ " models.params['expand_related'] = expand_related\n",
+ " topology_helper = models.get_topology_by_node_id(node_id)\n",
+ " topology_direct = models.get(f\"/models/by_node_id/{node_id}/topology\")\n",
+ "\n",
+ " nodecount_helper = models.get_nodecount_by_node_id(node_id)\n",
+ " nodecount_direct = models.get(f\"/models/by_node_id/{node_id}/nodecount\")\n",
+ "\n",
+ " nodes_helper = models.get_nodes_by_node_id(node_id, format='object', limit=25, kind=node_kind)\n",
+ " nodes_direct = models.get(f\"/models/by_node_id/{node_id}/nodes\" + (f\"/{node_kind}\" if node_kind else ''))\n",
+ "\n",
+ " model_by_node_calls = {\n",
+ " f\"/models/by_node_id/{node_id} (helper)\": show_response(model_by_node_helper, f\"/models/by_node_id/{node_id}\"),\n",
+ " f\"/models/by_node_id/{node_id} (direct)\": show_response(model_by_node_direct, f\"/models/by_node_id/{node_id}\"),\n",
+ " f\"/models/by_node_id/{node_id}/topology (helper)\": show_response(topology_helper, f\"/models/by_node_id/{node_id}/topology\"),\n",
+ " f\"/models/by_node_id/{node_id}/topology (direct)\": show_response(topology_direct, f\"/models/by_node_id/{node_id}/topology\"),\n",
+ " f\"/models/by_node_id/{node_id}/nodecount (helper)\": show_response(nodecount_helper, f\"/models/by_node_id/{node_id}/nodecount\"),\n",
+ " f\"/models/by_node_id/{node_id}/nodecount (direct)\": show_response(nodecount_direct, f\"/models/by_node_id/{node_id}/nodecount\"),\n",
+ " f\"/models/by_node_id/{node_id}/nodes (helper)\": show_response(nodes_helper, f\"/models/by_node_id/{node_id}/nodes\"),\n",
+ " f\"/models/by_node_id/{node_id}/nodes (direct)\": show_response(nodes_direct, f\"/models/by_node_id/{node_id}/nodes\"),\n",
+ " }\n",
+ "else:\n",
+ " model_by_node_calls = 'Set node_id to inspect models tied to a node id.'\n",
+ "model_by_node_calls\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/taxonomy_api.ipynb b/notebooks/taxonomy_api.ipynb
new file mode 100644
index 0000000..ad44334
--- /dev/null
+++ b/notebooks/taxonomy_api.ipynb
@@ -0,0 +1,184 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Taxonomy API Examples\n\nHelper methods paired with direct calls for taxonomy endpoints (sections, locales, node kinds, relationship kinds, field lists). Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n- Install tideway (e.g. `pip install -e .` from repo root).\n- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n- Adjust filters (section/locale/kind) as needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "taxonomy = tw.taxonomy()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sections and locales\nList taxonomy sections/locales via helper vs direct GET."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "sections_helper = taxonomy.get_taxonomy_sections\n",
+ "sections_direct = taxonomy.get('/taxonomy/sections')\n",
+ "\n",
+ "locales_helper = taxonomy.get_taxonomy_locales\n",
+ "locales_direct = taxonomy.get('/taxonomy/locales')\n",
+ "\n",
+ "meta_calls = {\n",
+ " '/taxonomy/sections (helper)': show_response(sections_helper, '/taxonomy/sections'),\n",
+ " '/taxonomy/sections (direct)': show_response(sections_direct, '/taxonomy/sections'),\n",
+ " '/taxonomy/locales (helper)': show_response(locales_helper, '/taxonomy/locales'),\n",
+ " '/taxonomy/locales (direct)': show_response(locales_direct, '/taxonomy/locales'),\n",
+ "}\n",
+ "meta_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Node kinds\nFetch node kinds (optionally filtered) and specific kind or field lists."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "node_format = None # e.g. 'object'\n",
+ "node_section = None # e.g. 'foundation'\n",
+ "node_locale = None # e.g. 'en_US'\n",
+ "node_kind = None # e.g. 'Host'\n",
+ "fieldlists = False\n",
+ "fieldlist_name = None # e.g. 'default'\n",
+ "\n",
+ "nodekinds_helper = taxonomy.get_taxonomy_nodekind(format=node_format, section=node_section, locale=node_locale)\n",
+ "nodekinds_direct = taxonomy.get('/taxonomy/nodekinds')\n",
+ "\n",
+ "if node_kind:\n",
+ " kind_helper = taxonomy.get_taxonomy_nodekind(kind=node_kind, locale=node_locale, fieldlists=fieldlists)\n",
+ " kind_direct = taxonomy.get(f\"/taxonomy/nodekinds/{node_kind}\" + ('/fieldlists' if fieldlists else ''))\n",
+ "else:\n",
+ " kind_helper = kind_direct = 'Set node_kind to fetch a specific node kind or fieldlists.'\n",
+ "\n",
+ "if node_kind and fieldlist_name:\n",
+ " fieldlist_helper = taxonomy.get_taxonomy_nodekind_fieldlist(node_kind, fieldlist_name)\n",
+ " fieldlist_direct = taxonomy.get(f\"/taxonomy/nodekinds/{node_kind}/fieldlists/{fieldlist_name}\")\n",
+ "else:\n",
+ " fieldlist_helper = fieldlist_direct = 'Set node_kind and fieldlist_name to fetch a field list.'\n",
+ "\n",
+ "node_calls = {\n",
+ " '/taxonomy/nodekinds (helper)': show_response(nodekinds_helper, '/taxonomy/nodekinds'),\n",
+ " '/taxonomy/nodekinds (direct)': show_response(nodekinds_direct, '/taxonomy/nodekinds'),\n",
+ " 'specific_kind_helper': show_response(kind_helper, f\"/taxonomy/nodekinds/{node_kind}\" if isinstance(kind_helper, object) else 'set node_kind'),\n",
+ " 'specific_kind_direct': show_response(kind_direct, f\"/taxonomy/nodekinds/{node_kind}\" if isinstance(kind_direct, object) else 'set node_kind'),\n",
+ " 'fieldlist_helper': show_response(fieldlist_helper, f\"/taxonomy/nodekinds/{node_kind}/fieldlists/{fieldlist_name}\" if isinstance(fieldlist_helper, object) else 'set node_kind/fieldlist_name'),\n",
+ " 'fieldlist_direct': show_response(fieldlist_direct, f\"/taxonomy/nodekinds/{node_kind}/fieldlists/{fieldlist_name}\" if isinstance(fieldlist_direct, object) else 'set node_kind/fieldlist_name'),\n",
+ "}\n",
+ "node_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Relationship kinds\nFetch relationship kinds (optionally filtered) and specific kind details."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "rel_format = None # e.g. 'object'\n",
+ "rel_locale = None # e.g. 'en_US'\n",
+ "rel_kind = None # e.g. 'Containment'\n",
+ "\n",
+ "relkinds_helper = taxonomy.get_taxonomy_relkind(format=rel_format, locale=rel_locale)\n",
+ "relkinds_direct = taxonomy.get('/taxonomy/relkinds')\n",
+ "\n",
+ "if rel_kind:\n",
+ " relkind_helper = taxonomy.get_taxonomy_relkind(kind=rel_kind, locale=rel_locale)\n",
+ " relkind_direct = taxonomy.get(f\"/taxonomy/relkinds/{rel_kind}\")\n",
+ "else:\n",
+ " relkind_helper = relkind_direct = 'Set rel_kind to fetch a specific relationship kind.'\n",
+ "\n",
+ "rel_calls = {\n",
+ " '/taxonomy/relkinds (helper)': show_response(relkinds_helper, '/taxonomy/relkinds'),\n",
+ " '/taxonomy/relkinds (direct)': show_response(relkinds_direct, '/taxonomy/relkinds'),\n",
+ " 'relkind_helper': show_response(relkind_helper, f\"/taxonomy/relkinds/{rel_kind}\" if isinstance(relkind_helper, object) else 'set rel_kind'),\n",
+ " 'relkind_direct': show_response(relkind_direct, f\"/taxonomy/relkinds/{rel_kind}\" if isinstance(relkind_direct, object) else 'set rel_kind'),\n",
+ "}\n",
+ "rel_calls\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/topology_api.ipynb b/notebooks/topology_api.ipynb
new file mode 100644
index 0000000..c885b62
--- /dev/null
+++ b/notebooks/topology_api.ipynb
@@ -0,0 +1,215 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Tideway Topology API Examples\n\nHelper methods paired with direct calls for topology endpoints: graphs, node lookups by criteria, node kinds, and visualization state. Reference: https://docs.bmc.com/xwiki/bin/view/IT-Operations-Management/Discovery/BMC-Helix-Discovery/DAAS/Integrating/Using-the-REST-APIs/Endpoints-in-the-REST-API/."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Setup\n- Install tideway (e.g. `pip install -e .` from repo root).\n- Set `TARGET` and `TOKEN` below. Do **not** commit secrets.\n- Adjust ids, filters, and bodies per your environment."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "import tideway\n",
+ "\n",
+ "# Configuration\n",
+ "TARGET = 'appliance-hostname-or-ip' # e.g. 'discovery.example.com'\n",
+ "TOKEN = 'your-api-token' # keep secrets out of source control\n",
+ "API_VERSION = '1.16' # latest supported API\n",
+ "SSL_VERIFY = False # set to True when using valid certs\n",
+ "\n",
+ "tw = tideway.appliance(TARGET, TOKEN, api_version=API_VERSION, ssl_verify=SSL_VERIFY)\n",
+ "topology = tw.topology()\n",
+ "\n",
+ "def show_response(resp, label):\n",
+ " if resp.ok:\n",
+ " try:\n",
+ " return resp.json()\n",
+ " except Exception:\n",
+ " return resp.text\n",
+ " try:\n",
+ " body = resp.json()\n",
+ " except Exception:\n",
+ " body = resp.text\n",
+ " return {'endpoint': label, 'status': resp.status_code, 'body': body}\n",
+ "\n",
+ "tw.api_about.json() # quick sanity check\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Node graph by node id\nHelper vs direct GET for `/data/nodes/{id}/graph` with focus/apply_rules/complete flags."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "node_id = '' # e.g. '1234567890abcdef'\n",
+ "focus = 'software-connected'\n",
+ "apply_rules = True\n",
+ "complete = False\n",
+ "\n",
+ "if node_id:\n",
+ " graph_helper = topology.get_data_nodes_graph(node_id, focus=focus, apply_rules=apply_rules, complete=complete)\n",
+ " graph_direct = topology.get(\n",
+ " f\"/data/nodes/{node_id}/graph?focus={focus}&apply_rules={str(apply_rules).lower()}&complete={str(complete).lower()}\"\n",
+ " )\n",
+ " graph_calls = {\n",
+ " f\"/data/nodes/{node_id}/graph (helper)\": show_response(graph_helper, f\"/data/nodes/{node_id}/graph\"),\n",
+ " f\"/data/nodes/{node_id}/graph (direct)\": show_response(graph_direct, f\"/data/nodes/{node_id}/graph\"),\n",
+ " }\n",
+ "else:\n",
+ " graph_calls = 'Set node_id to fetch a topology graph.'\n",
+ "graph_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Topology nodes (criteria search)\nPost criteria to `/topology/nodes` using helper vs direct."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "topology_nodes_body = {\n",
+ " # 'criteria': {'kind': 'Host', 'ip_address': '10.0.0.1'},\n",
+ " # 'attributes': ['name', 'kind'],\n",
+ "}\n",
+ "\n",
+ "if topology_nodes_body:\n",
+ " topo_nodes_helper = topology.post_topology_nodes(topology_nodes_body)\n",
+ " topo_nodes_direct = topology.post('/topology/nodes', topology_nodes_body)\n",
+ " topo_nodes_calls = {\n",
+ " '/topology/nodes (helper POST)': show_response(topo_nodes_helper, '/topology/nodes'),\n",
+ " '/topology/nodes (direct POST)': show_response(topo_nodes_direct, '/topology/nodes'),\n",
+ " }\n",
+ "else:\n",
+ " topo_nodes_calls = 'Set topology_nodes_body to post /topology/nodes.'\n",
+ "topo_nodes_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Topology nodes by kinds\nPost kind filters to `/topology/nodes/kinds` using helper vs direct."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "topology_kinds_body = {\n",
+ " # 'kinds': ['Host', 'SoftwareInstance'],\n",
+ " # 'attributes': ['name', 'kind'],\n",
+ "}\n",
+ "\n",
+ "if topology_kinds_body:\n",
+ " topo_kinds_helper = topology.post_topology_nodes_kinds(topology_kinds_body)\n",
+ " topo_kinds_direct = topology.post('/topology/nodes/kinds', topology_kinds_body)\n",
+ " topo_kinds_calls = {\n",
+ " '/topology/nodes/kinds (helper POST)': show_response(topo_kinds_helper, '/topology/nodes/kinds'),\n",
+ " '/topology/nodes/kinds (direct POST)': show_response(topo_kinds_direct, '/topology/nodes/kinds'),\n",
+ " }\n",
+ "else:\n",
+ " topo_kinds_calls = 'Set topology_kinds_body to post /topology/nodes/kinds.'\n",
+ "topo_kinds_calls\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Visualization state\nGet or update visualization state via helper vs direct calls."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "# GET visualization state\n",
+ "viz_helper = topology.get_topology_viz_state()\n",
+ "viz_direct = topology.get('/topology/visualization_state')\n",
+ "\n",
+ "viz_calls = {\n",
+ " '/topology/visualization_state (helper)': show_response(viz_helper, '/topology/visualization_state'),\n",
+ " '/topology/visualization_state (direct)': show_response(viz_direct, '/topology/visualization_state'),\n",
+ "}\n",
+ "\n",
+ "viz_patch_body = {\n",
+ " # 'layout': {},\n",
+ " # 'filters': {},\n",
+ "}\n",
+ "\n",
+ "viz_put_body = {\n",
+ " # 'layout': {},\n",
+ " # 'filters': {},\n",
+ "}\n",
+ "\n",
+ "if viz_patch_body:\n",
+ " viz_patch_helper = topology.patch_topology_viz_state(viz_patch_body)\n",
+ " viz_patch_direct = topology.patch('/topology/visualization_state', viz_patch_body)\n",
+ " viz_calls['/topology/visualization_state (helper PATCH)'] = show_response(viz_patch_helper, '/topology/visualization_state')\n",
+ " viz_calls['/topology/visualization_state (direct PATCH)'] = show_response(viz_patch_direct, '/topology/visualization_state')\n",
+ "\n",
+ "if viz_put_body:\n",
+ " viz_put_helper = topology.put_topology_viz_state(viz_put_body)\n",
+ " viz_put_direct = topology.put('/topology/visualization_state', viz_put_body)\n",
+ " viz_calls['/topology/visualization_state (helper PUT)'] = show_response(viz_put_helper, '/topology/visualization_state')\n",
+ " viz_calls['/topology/visualization_state (direct PUT)'] = show_response(viz_put_direct, '/topology/visualization_state')\n",
+ "\n",
+ "viz_calls\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/setup.py b/setup.py
index 3c4014d..e1fe902 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setuptools.setup(
name="tideway",
- version="0.2.1",
+ version="0.3.0",
author="Wes Moskal-Fitzpatrick",
author_email="wes@traversys.io",
description="Library for BMC Discovery API Interface.",
@@ -15,6 +15,7 @@
packages=setuptools.find_packages(exclude=["tests*"]),
install_requires=[
"requests",
+ "tabulate",
],
classifiers=[
"Programming Language :: Python :: 3",
diff --git a/tideway/admin.py b/tideway/admin.py
index 89d0132..46f2253 100644
--- a/tideway/admin.py
+++ b/tideway/admin.py
@@ -2,72 +2,53 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Admin(appliance):
'''Manage the BMC Discovery appliance.'''
- def baseline(self):
+ def get_admin_baseline(self):
'''Get a summary of the appliance status, and details of which baseline checks have passed or failed.'''
- response = dr.discoRequest(self, "/admin/baseline")
- return response
- get_admin_baseline = property(baseline)
+ return self.get("/admin/baseline")
- def admin(self):
+ def get_admin_about(self):
'''Get information about the appliance, like its version and versions of the installed packages.'''
- response = dr.discoRequest(self, "/admin/about")
- return response
- get_admin_about = property(admin)
+ return self.get("/admin/about")
- def licensing(self,content_type="text/plain"):
+ def get_admin_licensing(self, content_type="text/plain"):
'''Get the latest signed licensing report.'''
if content_type == "csv":
- response = dr.discoRequest(self, "/admin/licensing/csv",response="application/zip")
+ response = self.get("/admin/licensing/csv", response="application/zip")
elif content_type == "raw":
- response = dr.discoRequest(self, "/admin/licensing/raw",response="application/zip")
+ response = self.get("/admin/licensing/raw", response="application/zip")
else:
- response = dr.discoRequest(self, "/admin/licensing",response=content_type)
+ response = self.get("/admin/licensing", response=content_type)
return response
def instance(self):
'''Get details about the appliance instance.'''
- response = dr.discoRequest(self, "/admin/instance")
- return response
- get_admin_instance = property(instance)
+ return self.get("/admin/instance")
def cluster(self):
'''Get cluster configuration and status.'''
- response = dr.discoRequest(self, "/admin/cluster")
- return response
- get_admin_cluster = property(cluster)
+ return self.get("/admin/cluster")
def organizations(self):
'''Get configured organizations.'''
- response = dr.discoRequest(self, "/admin/organizations")
- return response
- get_admin_organizations = property(organizations)
+ return self.get("/admin/organizations")
def preferences(self):
'''Get global appliance preferences.'''
- response = dr.discoRequest(self, "/admin/preferences")
- return response
- get_admin_preferences = property(preferences)
+ return self.get("/admin/preferences")
def builtin_reports(self):
'''Get built-in report definitions.'''
- response = dr.discoRequest(self, "/admin/builtin_reports")
- return response
- get_admin_builtin_reports = property(builtin_reports)
+ return self.get("/admin/builtin_reports")
def custom_reports(self):
'''Get custom report definitions.'''
- response = dr.discoRequest(self, "/admin/custom_reports")
- return response
- get_admin_custom_reports = property(custom_reports)
+ return self.get("/admin/custom_reports")
def smtp(self):
'''Get SMTP configuration.'''
- response = dr.discoRequest(self, "/admin/smtp")
- return response
- get_admin_smtp = property(smtp)
+ return self.get("/admin/smtp")
diff --git a/tideway/credentials.py b/tideway/credentials.py
index 84301f6..13f51da 100644
--- a/tideway/credentials.py
+++ b/tideway/credentials.py
@@ -2,7 +2,6 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Credentials(appliance):
@@ -12,80 +11,68 @@ def get_vault_credential_type(self, group=None, category=None):
'''Altnernate API call for /vault/credential_types.'''
self.params['group'] = group
self.params['category'] = category
- req = dr.discoRequest(self, "/vault/credential_types")
- return req
+ return self.get("/vault/credential_types")
get_vault_credential_types = property(get_vault_credential_type)
def listCredentialTypes(self, group=None, category=None):
'''Get a list of all credential types and filter by group and/or category.'''
self.params['group'] = group
self.params['category'] = category
- response = dr.discoRequest(self, "/vault/credential_types")
- return response
+ return self.get("/vault/credential_types")
def get_vault_credential_type_name(self, cred_type_name):
'''Altnernate API call for /vault/credential_types/cred_type_name.'''
- req = dr.discoRequest(self, "/vault/credential_types/{}".format(cred_type_name))
- return req
+ return self.get("/vault/credential_types/{}".format(cred_type_name))
def credentialType(self, cred_type_name):
'''Get the properties of a specific credential type.'''
- response = dr.discoRequest(self, "/vault/credential_types/{}".format(cred_type_name))
- return response
+ return self.get("/vault/credential_types/{}".format(cred_type_name))
def get_vault_credential(self, cred_id=None):
'''Altnernate API call for /vault/credentials.'''
if cred_id:
- req = dr.discoRequest(self, "/vault/credentials/{}".format(cred_id))
+ req = self.get("/vault/credentials/{}".format(cred_id))
else:
- req = dr.discoRequest(self, "/vault/credentials")
+ req = self.get("/vault/credentials")
return req
get_vault_credentials = property(get_vault_credential)
def listCredentials(self, cred_id=None):
'''Get a list of all credentials.'''
if cred_id:
- response = dr.discoRequest(self, "/vault/credentials/{}".format(cred_id))
+ response = self.get("/vault/credentials/{}".format(cred_id))
else:
- response = dr.discoRequest(self, "/vault/credentials")
+ response = self.get("/vault/credentials")
return response
def post_vault_credential(self, body):
'''Altnernate API call for /vault/credentials.'''
- req = dr.discoPost(self, "/vault/credentials", body)
- return req
+ return self.post("/vault/credentials", body)
def newCredential(self, body):
'''Create a new credential.'''
- response = dr.discoPost(self, "/vault/credentials", body)
- return response
+ return self.post("/vault/credentials", body)
def delete_vault_credential(self, cred_id):
'''Altnernate API call for /vault/credentials.'''
- req = dr.discoDelete(self, "/vault/credentials/{}".format(cred_id))
- return req
+ return self.delete("/vault/credentials/{}".format(cred_id))
def deleteCredential(self, cred_id):
'''Delete a credential.'''
- response = dr.discoDelete(self, "/vault/credentials/{}".format(cred_id))
- return response
+ return self.delete("/vault/credentials/{}".format(cred_id))
def patch_vault_credential(self, cred_id, body):
'''Altnernate API call for /vault/credentials.'''
- req = dr.discoPatch(self, "/vault/credentials/{}".format(cred_id), body)
- return req
+ return self.patch("/vault/credentials/{}".format(cred_id), body)
def updateCredential(self, cred_id, body):
'''Updates partial resources of a credential. Missing properties are left unchanged.'''
- response = dr.discoPatch(self, "/vault/credentials/{}".format(cred_id), body)
- return response
+ return self.patch("/vault/credentials/{}".format(cred_id), body)
def put_vault_credential(self, cred_id, body):
'''Altnernate API call for /vault/credentials.'''
- req = dr.discoPut(self, "/vault/credentials/{}".format(cred_id), body)
- return req
+ return self.put("/vault/credentials/{}".format(cred_id), body)
def replaceCredential(self, cred_id, body):
'''Replaces a single credential. All required credential properties must be present. Optional properties that are missing will be reset to their defaults.'''
- response = dr.discoPut(self, "/vault/credentials/{}".format(cred_id), body)
- return response
+ return self.put("/vault/credentials/{}".format(cred_id), body)
diff --git a/tideway/data.py b/tideway/data.py
index b86e507..8ecf72d 100644
--- a/tideway/data.py
+++ b/tideway/data.py
@@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
import tideway
-import warnings
import json
-
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Data(appliance):
@@ -44,10 +41,10 @@ def _search_once(self, query, offset=None, results_id=None, format=None, limit=1
try:
body = query
_ = query["query"]
- response = dr.discoPost(self, "/data/search", body)
+ response = self.post("/data/search", body)
except Exception:
self.params['query'] = query
- response = dr.discoRequest(self, "/data/search")
+ response = self.get("/data/search")
return response
def _search_all(self, query, format=None, limit=100, delete=False, record_limit=None, call_limit=None):
@@ -97,14 +94,6 @@ def search(self, query, offset=None, results_id=None, format=None, limit=100, de
return response
return self._search_all(query, format, limit, delete, record_limit, call_limit)
- def searchQuery(self, body, offset=None, results_id=None, format=None, limit = 100, delete = False):
- '''An alternative to GET /data/search, for search queries which are too long for urls.'''
- warnings.warn(
- "searchQuery() is deprecated; use search() instead.",
- DeprecationWarning,
- )
- return Data.search(self, body, offset, results_id, format, limit, delete)
-
def search_bulk(self, query, format=None, limit=100, delete=False, record_limit=None, call_limit=None):
'''Performs a bulk search, looping through paginated results.'''
return self._search_all(query, format, limit, delete, record_limit, call_limit)
@@ -116,100 +105,52 @@ def post_data_condition(self, body, offset=None, results_id=None, format=None, l
self.params['format'] = format
self.params['delete'] = delete
self.params['limit'] = limit
- response = dr.discoPost(self, "/data/condition", body)
+ response = self.post("/data/condition", body)
return response
def post_data_condition_param_values(self, body):
'''Get possible parameter values for a condition'''
- response = dr.discoPost(self, "/data/condition/param_values", body)
+ response = self.post("/data/condition/param_values", body)
return response
def get_data_condition_template(self, template_id=None):
'''Get a template or a list of all templates.'''
if template_id:
- req = dr.discoRequest(self, "/data/condition/templates/{}".format(template_id))
+ req = self.get("/data/condition/templates/{}".format(template_id))
else:
- req = dr.discoRequest(self, "/data/condition/templates")
+ req = self.get("/data/condition/templates")
return req
get_data_condition_templates = property(get_data_condition_template)
def post_data_candidate(self, body):
'''Alternate API call for POST /data/candidate.'''
- response = dr.discoPost(self, "/data/candidate", body)
+ response = self.post("/data/candidate", body)
return response
- def best_candidate(self, body):
- '''
- The node object of the best candidate based on the provided parameters.
- '''
- warnings.warn(
- "best_candidate() is deprecated; use post_data_candidate() instead.",
- DeprecationWarning,
- )
- return self.post_data_candidate(body)
-
def post_data_candidates(self, body):
'''Alternate API call for POST /data/candidates.'''
- response = dr.discoPost(self, "/data/candidates", body)
+ response = self.post("/data/candidates", body)
return response
- def top_candidates(self, body):
- '''
- Enter parameters to identify a device, the response is a list of
- candidate nodes ordered by descending score.
- '''
- warnings.warn(
- "top_candidates() is deprecated; use post_data_candidates() instead.",
- DeprecationWarning,
- )
- return self.post_data_candidates(body)
-
def get_data_nodes(self, node_id, relationships=False, traverse=None, flags=None, attributes=None):
'''Alternate API call for /data/nodes/node_id'''
self.params['traverse'] = traverse
self.params['flags'] = flags
self.params['attributes'] = attributes
if relationships:
- response = dr.discoRequest(self, "/data/nodes/{}?relationships=true".format(node_id))
+ response = self.get("/data/nodes/{}?relationships=true".format(node_id))
else:
- response = dr.discoRequest(self, "/data/nodes/{}".format(node_id))
+ response = self.get("/data/nodes/{}".format(node_id))
return response
- def nodeLookup(self, node_id, relationships=False, traverse=None, flags=None, attributes=None):
- '''Get the state of a node with specified id.'''
- warnings.warn(
- "nodeLookup() is deprecated; use get_data_nodes() instead.",
- DeprecationWarning,
- )
- return self.get_data_nodes(
- node_id,
- relationships=relationships,
- traverse=traverse,
- flags=flags,
- attributes=attributes,
- )
-
def get_data_nodes_graph(self, node_id, focus="software-connected", apply_rules=True, complete=False):
'''Alternate API call for /data/nodes/node_id/graph'''
self.params['focus'] = focus
self.params['apply_rules'] = apply_rules
self.params['complete'] = complete
- response = dr.discoRequest(self, "/data/nodes/{}/graph".format(node_id))
+ response = self.get("/data/nodes/{}/graph".format(node_id))
return response
- def graphNode(self, node_id, focus="software-connected", apply_rules=True):
- '''Graph data represents a set of nodes and relationships that are associated to the given node.'''
- warnings.warn(
- "graphNode() is deprecated; use get_data_nodes_graph() instead.",
- DeprecationWarning,
- )
- return self.get_data_nodes_graph(
- node_id,
- focus=focus,
- apply_rules=apply_rules,
- complete=False,
- )
-
def get_data_kinds(self, kind, offset=None, results_id=None, format=None, limit = 100, delete = False):
'''Alternate API call for /data/kinds.'''
self.params['offset'] = offset
@@ -217,73 +158,38 @@ def get_data_kinds(self, kind, offset=None, results_id=None, format=None, limit
self.params['format'] = format
self.params['limit'] = limit
self.params['delete'] = delete
- response = dr.discoRequest(self, "/data/kinds/{}".format(kind))
+ response = self.get("/data/kinds/{}".format(kind))
return response
- def lookupNodeKind(self, kind, offset=None, results_id=None, format=None, limit = 100, delete = False):
- '''Finds all nodes of a specified node kind.'''
- warnings.warn(
- "lookupNodeKind() is deprecated; use get_data_kinds() instead.",
- DeprecationWarning,
- )
- return self.get_data_kinds(
- kind,
- offset=offset,
- results_id=results_id,
- format=format,
- limit=limit,
- delete=delete,
- )
-
def partitions(self):
'''Get names and ids of partitions.'''
- response = dr.discoRequest(self, "/data/partitions")
+ response = self.get("/data/partitions")
return response
get_data_partitions = property(partitions)
def post_data_partitions(self, body):
'''Create a partition.'''
- response = dr.discoPost(self, "/data/partitions", body)
+ response = self.post("/data/partitions", body)
return response
def post_data_import(self, body):
'''Alternate API call for /data/import.'''
- response = dr.discoPost(self, "/data/import", body)
+ response = self.post("/data/import", body)
return response
- def twImport(self, body):
- '''
- Imports data. Returns the import UUID.
- '''
- warnings.warn(
- "twImport() is deprecated; use post_data_import() instead.",
- DeprecationWarning,
- )
- return self.post_data_import(body)
-
def post_data_write(self, body):
'''Alternate API call for /data/write.'''
- response = dr.discoPost(self, "/data/write", body)
+ response = self.post("/data/write", body)
return response
- def twWrite(self, body):
- '''
- Perform arbitrary write operations.
- '''
- warnings.warn(
- "twWrite() is deprecated; use post_data_write() instead.",
- DeprecationWarning,
- )
- return self.post_data_write(body)
-
def get_data_condition_params(self):
'''Retrieve the list of available condition parameters.'''
- response = dr.discoRequest(self, "/data/condition/params")
+ response = self.get("/data/condition/params")
return response
def post_data_import_graph(self, body):
'''Import graph data and return the import UUID.'''
- response = dr.discoPost(self, "/data/import/graph", body)
+ response = self.post("/data/import/graph", body)
return response
def get_data_external_consumer(self, consumer_name=None, path=None):
@@ -293,7 +199,7 @@ def get_data_external_consumer(self, consumer_name=None, path=None):
endpoint += f"/{consumer_name}"
if path:
endpoint += f"/{path}"
- response = dr.discoRequest(self, endpoint)
+ response = self.get( endpoint)
return response
get_data_external_consumers = property(get_data_external_consumer)
@@ -304,7 +210,7 @@ def post_data_external_consumer(self, body, consumer_name=None, path=None):
endpoint += f"/{consumer_name}"
if path:
endpoint += f"/{path}"
- response = dr.discoPost(self, endpoint, body)
+ response = self.post( endpoint, body)
return response
def patch_data_external_consumer(self, consumer_name, body, path=None):
@@ -312,7 +218,7 @@ def patch_data_external_consumer(self, consumer_name, body, path=None):
endpoint = f"/data/external_consumers/{consumer_name}"
if path:
endpoint += f"/{path}"
- response = dr.discoPatch(self, endpoint, body)
+ response = self.patch( endpoint, body)
return response
def delete_data_external_consumer(self, consumer_name, path=None):
@@ -320,7 +226,7 @@ def delete_data_external_consumer(self, consumer_name, path=None):
endpoint = f"/data/external_consumers/{consumer_name}"
if path:
endpoint += f"/{path}"
- response = dr.discoDelete(self, endpoint)
+ response = self.delete( endpoint)
return response
def get_data_kinds_values(self, kind, attribute, offset=None, results_id=None, format=None, limit=100, delete=False):
@@ -331,5 +237,5 @@ def get_data_kinds_values(self, kind, attribute, offset=None, results_id=None, f
self.params['limit'] = limit
self.params['delete'] = delete
endpoint = f"/data/kinds/{kind}/values/{attribute}"
- response = dr.discoRequest(self, endpoint)
+ response = self.get( endpoint)
return response
diff --git a/tideway/discoRequests.py b/tideway/discoRequests.py
index a92543a..8f5df1c 100644
--- a/tideway/discoRequests.py
+++ b/tideway/discoRequests.py
@@ -9,47 +9,59 @@ def url_and_headers(target,token,api_endpoint,response):
return url, headers
def discoRequest(appliance, api_endpoint, response="application/json"):
+ """Issue a GET request."""
url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
req = requests.get(url, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
appliance.reset_params()
return req
-def discoPost(appliance, api_endpoint, jsoncode, response="application/json"):
+def discoPost(appliance, api_endpoint, jsoncode=None, response="application/json", files=None, data=None, content_type=None):
+ """Issue a POST request with optional JSON, form data, or files."""
url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
- req = requests.post(url, json=jsoncode, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
+ if content_type:
+ heads['Content-type'] = content_type
+ req = requests.post(
+ url,
+ json=jsoncode if files is None else None,
+ files=files,
+ data=data,
+ headers=heads,
+ params=appliance.params.copy(),
+ verify=appliance.verify,
+ )
appliance.reset_params()
return req
def filePost(appliance, api_endpoint, file, response="text/html"):
- url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
+ """Backward compatible helper for file uploads."""
with open(file, 'rb') as f:
files = {"file": f}
- req = requests.post(url, files=files, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
- appliance.reset_params()
+ req = discoPost(appliance, api_endpoint, files=files, response=response)
return req
def keytabPost(appliance, api_endpoint, file, username, response="application/json", content_type="multipart/form-data"):
- url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
- heads['Content-type'] = content_type
+ """Backward compatible helper for Kerberos uploads."""
with open(file, 'rb') as f:
form_data = {"keytab": f, "username": username}
- req = requests.post(url, files=form_data, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
- appliance.reset_params()
+ req = discoPost(appliance, api_endpoint, files=form_data, response=response, content_type=content_type)
return req
def discoPatch(appliance, api_endpoint, jsoncode, response="application/json"):
+ """Issue a PATCH request."""
url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
req = requests.patch(url, json=jsoncode, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
appliance.reset_params()
return req
def discoPut(appliance, api_endpoint, jsoncode, response="application/json"):
+ """Issue a PUT request."""
url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
req = requests.put(url, json=jsoncode, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
appliance.reset_params()
return req
def discoDelete(appliance, api_endpoint, response="application/json"):
+ """Issue a DELETE request."""
url, heads = url_and_headers(appliance.url, appliance.token, api_endpoint, response)
req = requests.delete(url, headers=heads, params=appliance.params.copy(), verify=appliance.verify)
appliance.reset_params()
diff --git a/tideway/discovery.py b/tideway/discovery.py
index dc0b6e1..bf7479a 100644
--- a/tideway/discovery.py
+++ b/tideway/discovery.py
@@ -2,7 +2,6 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Discovery(appliance):
@@ -10,20 +9,20 @@ class Discovery(appliance):
def getDiscoveryStatus(self):
'''Get the current status of the discovery process. JSON Output.'''
- response = dr.discoRequest(self, "/discovery")
+ response = self.get("/discovery")
return response
get_discovery = property(getDiscoveryStatus)
def patch_discovery(self, body):
'''Alternate API call for PATCH /discovery.'''
- response = dr.discoPatch(self, "/discovery", body)
+ response = self.patch("/discovery", body)
return response
def setDiscoveryStatus(self, body):
'''
Set the Discovery status using JSON format.
'''
- response = dr.discoPatch(self, "/discovery", body)
+ response = self.patch("/discovery", body)
return response.ok
def getApiProviderMetadata(self):
@@ -33,7 +32,7 @@ def getApiProviderMetadata(self):
/discovery/runs and /vault/credentials endpoints. Support for new
API providers is available in TKU knowledge updates.
'''
- response = dr.discoRequest(self, "/discovery/api_provider_metadata")
+ response = self.get("/discovery/api_provider_metadata")
return response
get_discovery_api_provider_metadata = property(getApiProviderMetadata)
@@ -42,71 +41,71 @@ def getDiscoveryCloudMetaData(self):
Get metadata for the cloud providers currently supported by BMC
Discovery.
'''
- response = dr.discoRequest(self, "/discovery/cloud_metadata")
+ response = self.get("/discovery/cloud_metadata")
return response
get_discovery_api_cloud_metadata = property(getDiscoveryCloudMetaData)
def get_discovery_exclude(self, exclude_id=None):
'''Get a list of all excludes or specific.'''
if exclude_id:
- req = dr.discoRequest(self, "/discovery/excludes/{}".format(exclude_id))
+ req = self.get("/discovery/excludes/{}".format(exclude_id))
else:
- req = dr.discoRequest(self, "/discovery/excludes")
+ req = self.get("/discovery/excludes")
return req
get_discovery_excludes = property(get_discovery_exclude)
def post_discovery_exclude(self, body):
'''Create an exclude.'''
- response = dr.discoPost(self, "/discovery/excludes", body)
+ response = self.post("/discovery/excludes", body)
return response
def delete_discovery_exclude(self, exclude_id):
'''Delete an exclude.'''
- response = dr.discoDelete(self, "/discovery/excludes/{}".format(exclude_id))
+ response = self.delete("/discovery/excludes/{}".format(exclude_id))
return response
def patch_discovery_exclude(self, exclude_id, body):
'''Update an exclude.'''
- response = dr.discoPatch(self, "/discovery/excludes/{}".format(exclude_id), body)
+ response = self.patch("/discovery/excludes/{}".format(exclude_id), body)
return response
def get_discovery_run(self, run_id=None):
'''Get details of all or specific currently processing discovery runs.'''
if run_id:
- req = dr.discoRequest(self, "/discovery/runs/{}".format(run_id))
+ req = self.get("/discovery/runs/{}".format(run_id))
else:
- req = dr.discoRequest(self, "/discovery/runs")
+ req = self.get("/discovery/runs")
return req
get_discovery_runs = property(get_discovery_run)
def getDiscoveryRuns(self):
'''Get details of all currently processing discovery runs.'''
- response = dr.discoRequest(self, "/discovery/runs")
+ response = self.get("/discovery/runs")
return response
def getDiscoveryRun(self, runid):
'''Get details of specific currently processing discovery run.'''
- response = dr.discoRequest(self, "/discovery/runs/{}".format(runid))
+ response = self.get("/discovery/runs/{}".format(runid))
return response
def post_discovery_run(self, body):
'''Alternative API call for POST /discovery/runs.'''
- response = dr.discoPost(self, "/discovery/runs", body)
+ response = self.post("/discovery/runs", body)
return response
def discoveryRun(self, body):
'''Create a new snapshot discovery run.'''
- response = dr.discoPost(self, "/discovery/runs", body)
+ response = self.post("/discovery/runs", body)
return response
def patch_discovery_run(self, run_id, body):
'''Alternate API call for PATCH /discovery/runs.'''
- response = dr.discoPatch(self, "/discovery/runs/{}".format(run_id), body)
+ response = self.patch("/discovery/runs/{}".format(run_id), body)
return response
def updateDiscoveryRun(self, runid, body):
'''Update the state of a specific discovery run.'''
- response = dr.discoPatch(self, "/discovery/runs/{}".format(runid), body)
+ response = self.patch("/discovery/runs/{}".format(runid), body)
return response
def get_discovery_run_results(self, run_id, result=None, offset=None, results_id=None, format=None, limit = 100, delete = False):
@@ -117,14 +116,14 @@ def get_discovery_run_results(self, run_id, result=None, offset=None, results_id
self.params['format'] = format
self.params['limit'] = limit
self.params['delete'] = delete
- response = dr.discoRequest(self, "/discovery/runs/{}/results/{}".format(run_id,result))
+ response = self.get("/discovery/runs/{}/results/{}".format(run_id,result))
else:
- response = dr.discoRequest(self, "/discovery/runs/{}/results".format(run_id))
+ response = self.get("/discovery/runs/{}/results".format(run_id))
return response
def getDiscoveryRunResults(self, runid):
'''Get a summary of the results from scanning all endpoints in the run, partitioned by result type.'''
- response = dr.discoRequest(self, "/discovery/runs/{}/results".format(runid))
+ response = self.get("/discovery/runs/{}/results".format(runid))
return response
def getDiscoveryRunResult(self, runid, result="Success", offset=None, results_id=None, format=None, limit = 100, delete = False):
@@ -134,7 +133,7 @@ def getDiscoveryRunResult(self, runid, result="Success", offset=None, results_id
self.params['format'] = format
self.params['limit'] = limit
self.params['delete'] = delete
- response = dr.discoRequest(self, "/discovery/runs/{}/results/{}".format(runid,result))
+ response = self.get("/discovery/runs/{}/results/{}".format(runid,result))
return response
def get_discovery_run_inferred(self, run_id, inferred_kind, offset=None, results_id=None, format=None, limit = 100, delete = False):
@@ -145,14 +144,14 @@ def get_discovery_run_inferred(self, run_id, inferred_kind, offset=None, results
self.params['format'] = format
self.params['limit'] = limit
self.params['delete'] = delete
- response = dr.discoRequest(self, "/discovery/runs/{}/inferred/{}".format(run_id,inferred_kind))
+ response = self.get("/discovery/runs/{}/inferred/{}".format(run_id,inferred_kind))
else:
- response = dr.discoRequest(self, "/discovery/runs/{}/inferred".format(run_id))
+ response = self.get("/discovery/runs/{}/inferred".format(run_id))
return response
def getDiscoveryRunInferred(self, runid):
'''Get a summary of all inferred devices from a discovery run, partitioned by device type.'''
- response = dr.discoRequest(self, "/discovery/runs/{}/inferred".format(runid))
+ response = self.get("/discovery/runs/{}/inferred".format(runid))
return response
def getDiscoveryRunInferredKind(self, runid, inferred_kind, offset=None, results_id=None, format=None, limit = 100, delete = False):
@@ -162,48 +161,48 @@ def getDiscoveryRunInferredKind(self, runid, inferred_kind, offset=None, results
self.params['format'] = format
self.params['limit'] = limit
self.params['delete'] = delete
- response = dr.discoRequest(self, "/discovery/runs/{}/inferred/{}".format(runid,inferred_kind))
+ response = self.get("/discovery/runs/{}/inferred/{}".format(runid,inferred_kind))
return response
def get_discovery_run_schedule(self, run_id=None):
'''Get a list of all scheduled runs or specific.'''
if run_id:
- req = dr.discoRequest(self, "/discovery/runs/scheduled/{}".format(run_id))
+ req = self.get("/discovery/runs/scheduled/{}".format(run_id))
else:
- req = dr.discoRequest(self, "/discovery/runs/scheduled")
+ req = self.get("/discovery/runs/scheduled")
return req
get_discovery_run_schedules = property(get_discovery_run_schedule)
def post_discovery_run_schedule(self, body):
'''Add a new scheduled run.'''
- response = dr.discoPost(self, "/discovery/runs/scheduled", body)
+ response = self.post("/discovery/runs/scheduled", body)
return response
def delete_discovery_run_schedule(self, run_id):
'''Delete a specific scheduled discovery run.'''
- response = dr.discoDelete(self, "/discovery/runs/scheduled/{}".format(run_id))
+ response = self.delete("/discovery/runs/scheduled/{}".format(run_id))
return response
def patch_discovery_run_schedule(self, run_id, body):
'''Update the parameters of a specific scheduled discovery run.'''
- response = dr.discoPatch(self, "/discovery/runs/scheduled/{}".format(run_id), body)
+ response = self.patch("/discovery/runs/scheduled/{}".format(run_id), body)
return response
def get_discovery_outpost(self, outpost_id=None):
'''Get all configured Outposts or a specific one.'''
if outpost_id:
- req = dr.discoRequest(self, "/discovery/outposts/{}".format(outpost_id))
+ req = self.get("/discovery/outposts/{}".format(outpost_id))
else:
- req = dr.discoRequest(self, "/discovery/outposts")
+ req = self.get("/discovery/outposts")
return req
get_discovery_outposts = property(get_discovery_outpost)
def post_discovery_outpost(self, body):
'''Register a new Outpost.'''
- response = dr.discoPost(self, "/discovery/outposts", body)
+ response = self.post("/discovery/outposts", body)
return response
def delete_discovery_outpost(self, outpost_id):
'''Delete an Outpost.'''
- response = dr.discoDelete(self, "/discovery/outposts/{}".format(outpost_id))
+ response = self.delete("/discovery/outposts/{}".format(outpost_id))
return response
diff --git a/tideway/events.py b/tideway/events.py
index efba9a2..fc09c48 100644
--- a/tideway/events.py
+++ b/tideway/events.py
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
import tideway
-import warnings
-
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Events(appliance):
@@ -11,16 +8,5 @@ class Events(appliance):
def post_events(self, body):
'''An alternate API call for POST /events'''
- response = dr.discoPost(self, "/events", body)
+ response = self.post("/events", body)
return response
-
- def status(self, body):
- '''
- Returns a unique ID if the event has been recorded, otherwise an
- empty string is returned e.g. if the event source has been disabled.
- '''
- warnings.warn(
- "status() is deprecated; use post_events() instead.",
- DeprecationWarning,
- )
- return self.post_events(body)
diff --git a/tideway/kerberos.py b/tideway/kerberos.py
index 85aacba..339753a 100644
--- a/tideway/kerberos.py
+++ b/tideway/kerberos.py
@@ -2,7 +2,6 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Kerberos(appliance):
@@ -11,58 +10,70 @@ class Kerberos(appliance):
def get_vault_kerberos_realm(self, realm_name=None):
'''Retrieve all or specific realm.'''
if realm_name:
- req = dr.discoRequest(self, "/vault/kerberos/realms/{}".format(realm_name))
+ req = self.get("/vault/kerberos/realms/{}".format(realm_name))
else:
- req = dr.discoRequest(self, "/vault/kerberos/realms")
+ req = self.get("/vault/kerberos/realms")
return req
get_vault_kerberos_realms = property(get_vault_kerberos_realm)
def delete_vault_kerberos_realm(self, realm_name):
'''Delete a realm.'''
- req = dr.discoDelete(self, "/vault/kerberos/realms/{}".format(realm_name))
+ req = self.delete("/vault/kerberos/realms/{}".format(realm_name))
return req
def patch_vault_kerberos_realm(self, realm_name, body):
'''Update a Kerberos realm.'''
- req = dr.discoPatch(self, "/vault/kerberos/realms/{}".format(realm_name), body)
+ req = self.patch("/vault/kerberos/realms/{}".format(realm_name), body)
return req
def post_vault_kerberos_realm(self, realm_name, body, test=False):
'''Create a realm and Test user credentials by attempting to acquire a new Kerberos Ticket Granting Ticket (TGT)'''
- req = dr.discoPost(self, "/vault/kerberos/realms/{}".format(realm_name), body)
+ req = self.post("/vault/kerberos/realms/{}".format(realm_name), body)
if test:
- req = dr.discoPost(self, "/vault/kerberos/realms/{}/test".format(realm_name), body)
+ req = self.post("/vault/kerberos/realms/{}/test".format(realm_name), body)
return req
def get_vault_kerberos_keytabs(self, realm_name):
'''List users with a Kerberos keytab file'''
- req = dr.discoRequest(self, "/vault/kerberos/realms/{}/keytabs".format(realm_name))
+ req = self.get("/vault/kerberos/realms/{}/keytabs".format(realm_name))
return req
def post_vault_kerberos_keytab(self, realm_name, username, keytab):
'''Upload a Kerberos keytab file'''
# Not Tested
- req = dr.keytabPost(self, "/vault/kerberos/realms/{}/keytabs".format(realm_name), keytab, username)
- return req
+ with open(keytab, "rb") as kf:
+ files = {"keytab": kf, "username": (None, username)}
+ return self.post(
+ "/vault/kerberos/realms/{}/keytabs".format(realm_name),
+ files=files,
+ response="application/json",
+ content_type="multipart/form-data",
+ )
def delete_vault_kerberos_keytab(self, realm_name, username):
'''Delete a keytab file'''
# Not Tested
- req = dr.discoDelete(self, "/vault/kerberos/realms/{}/keytabs/{}".format(realm_name, username))
+ req = self.delete("/vault/kerberos/realms/{}/keytabs/{}".format(realm_name, username))
return req
def get_vault_kerberos_ccaches(self, realm_name):
'''List users with a Kerberos credential cache file.'''
- req = dr.discoRequest(self, "/vault/kerberos/realms/{}/ccaches".format(realm_name))
+ req = self.get("/vault/kerberos/realms/{}/ccaches".format(realm_name))
return req
def post_vault_kerberos_ccache(self, realm_name, username, ccache):
'''Upload a Kerberos credential cache file'''
# Not Tested
- req = dr.keytabPost(self, "/vault/kerberos/realms/{}/ccaches".format(realm_name), ccache, username)
- return req
+ with open(ccache, "rb") as cache_file:
+ files = {"keytab": cache_file, "username": (None, username)}
+ return self.post(
+ "/vault/kerberos/realms/{}/ccaches".format(realm_name),
+ files=files,
+ response="application/json",
+ content_type="multipart/form-data",
+ )
def delete_vault_kerberos_ccache(self, realm_name, username):
'''Delete a credential cache file'''
- req = dr.discoDelete(self, "/vault/kerberos/realms/{}/ccaches/{}".format(realm_name, username))
- return req
\ No newline at end of file
+ req = self.delete("/vault/kerberos/realms/{}/ccaches/{}".format(realm_name, username))
+ return req
diff --git a/tideway/knowledge.py b/tideway/knowledge.py
index 17e1762..81cd9c7 100644
--- a/tideway/knowledge.py
+++ b/tideway/knowledge.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import tideway
-import warnings
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Knowledge(appliance):
@@ -11,28 +9,12 @@ class Knowledge(appliance):
def get_knowledge(self):
'''Get the current state of the appliance's knowledge, including TKU versions.'''
- return dr.discoRequest(self, "/knowledge")
-
- def getKnowledgeManagement(self):
- '''Get the current state of the appliance's knowledge, including TKU versions.'''
- warnings.warn(
- "getKnowledgeManagement() is deprecated; use get_knowledge() instead.",
- DeprecationWarning,
- )
- return self.get_knowledge()
+ return self.get("/knowledge")
get_knowledge_property = property(get_knowledge)
- def getUploadStatus(self):
- '''Get the current state of a knowledge upload.'''
- warnings.warn(
- "getUploadStatus() is deprecated; use get_knowledge_status() instead.",
- DeprecationWarning,
- )
- return self.get_knowledge_status()
-
def get_knowledge_status(self):
'''Get the current state of a knowledge upload.'''
- return dr.discoRequest(self, "/knowledge/status")
+ return self.get("/knowledge/status")
get_knowledge_status_property = property(get_knowledge_status)
@@ -40,20 +22,17 @@ def post_knowledge(self, filename, file, activate=True, allow_restart=False):
'''Alternate API call for POST /knowledge/filename'''
self.params['activate'] = activate
self.params['allow_restart'] = allow_restart
- response = dr.filePost(self, "/knowledge/{}".format(filename), file)
- return response
-
- def uploadKnowledge(self, filename, file, activate=True, allow_restart=False):
- '''Upload a TKU or pattern module to the appliance.'''
- warnings.warn(
- "uploadKnowledge() is deprecated; use post_knowledge() instead.",
- DeprecationWarning,
- )
- return self.post_knowledge(filename, file, activate, allow_restart)
+ with open(file, "rb") as upload:
+ files = {"file": upload}
+ return self.post(
+ "/knowledge/{}".format(filename),
+ files=files,
+ response="text/html",
+ )
def getKnowledgeTriggerPatterns(self, lookup_data_sources=None):
'''Get a list of all knowledge trigger patterns.'''
self.params['lookup_data_sources'] = lookup_data_sources
- response = dr.discoRequest(self, "/knowledge/trigger_patterns")
+ response = self.get("/knowledge/trigger_patterns")
return response
get_knowledge_trigger_patterns = property(getKnowledgeTriggerPatterns)
diff --git a/tideway/main.py b/tideway/main.py
index f640a96..18daacc 100644
--- a/tideway/main.py
+++ b/tideway/main.py
@@ -5,12 +5,11 @@
from . import discoRequests as dr
from . import endpoints
import tideway
-import warnings
class Appliance:
'''An appliance instance.'''
- def __init__(self, target, token, limit = 100, delete = False, api_version = "1.14", ssl_verify = False):
+ def __init__(self, target, token, limit = 100, delete = False, api_version = "1.16", ssl_verify = False):
self.target = target
self.token = token
self.default_limit = limit
@@ -29,33 +28,41 @@ def reset_params(self):
self.params['limit'] = self.default_limit
self.params['delete'] = self.default_delete
- def get(self,endpoint):
+ def get(self, endpoint, response="application/json"):
'''Request any endpoint.'''
- req = dr.discoRequest(self,endpoint)
+ req = dr.discoRequest(self, endpoint, response=response)
self.reset_params()
return req
- def post(self,endpoint,body):
+ def post(self, endpoint, body=None, response="application/json", files=None, data=None, content_type=None):
'''Post any endpoint.'''
- req = dr.discoPost(self, endpoint, body)
+ req = dr.discoPost(
+ self,
+ endpoint,
+ body,
+ response=response,
+ files=files,
+ data=data,
+ content_type=content_type,
+ )
self.reset_params()
return req
- def delete(self,endpoint):
+ def delete(self, endpoint, response="application/json"):
'''Delete any endpoint.'''
- req = dr.discoDelete(self, endpoint)
+ req = dr.discoDelete(self, endpoint, response=response)
self.reset_params()
return req
- def patch(self,endpoint,body):
+ def patch(self, endpoint, body, response="application/json"):
'''Patch any endpoint.'''
- req = dr.discoPatch(self, endpoint, body)
+ req = dr.discoPatch(self, endpoint, body, response=response)
self.reset_params()
return req
- def put(self,endpoint,body):
+ def put(self, endpoint, body, response="application/json"):
'''Update any endpoint.'''
- req = dr.discoPut(self, endpoint, body)
+ req = dr.discoPut(self, endpoint, body, response=response)
self.reset_params()
return req
@@ -103,7 +110,7 @@ def vault(self):
v = tideway.vault(self.target, self.token, api_version=self.api_version, ssl_verify=self.verify)
return v
- ### Admin ###
+ ### API Admin ###
@property
def api_about(self):
@@ -112,14 +119,6 @@ def api_about(self):
req = requests.get(url, verify=self.verify)
return req
- def about(self):
- '''Return about data.'''
- warnings.warn(
- "about() is deprecated; use api_about instead.",
- DeprecationWarning,
- )
- return self.api_about
-
def _get_api_schema(self):
'''Helper to fetch API schema, trying /swagger.json first, then /openapi.json.'''
for path in ["/swagger.json", "/openapi.json"]:
@@ -134,14 +133,6 @@ def api_swagger(self):
'''Alternate API call for swagger.'''
return self._get_api_schema()
- def swagger(self):
- '''Fetch API schema, trying /swagger.json first, then /openapi.json.'''
- warnings.warn(
- "swagger() is deprecated; use api_swagger instead.",
- DeprecationWarning,
- )
- return self.api_swagger
-
def _load_schema(self):
'''Return cached API schema as dict, fetching it if necessary.'''
if getattr(self, '_api_schema', None) is None:
@@ -160,64 +151,44 @@ def api_paths(self, path=None):
return paths.get(path)
return paths
+ @property
+ def api_help(self):
+ '''Help on endpoints.'''
+ endpoints.docs()
+ #print("")
+
+ def help(*args):
+ '''Help on endpoints.'''
+ if len(args) > 1:
+ endpoints.docs(args[1])
+ else:
+ endpoints.docs()
+ #print("\n")
+
+### Discovery Admin ###
+
@property
def get_admin_baseline(self):
'''Alternate API call for baseline.'''
- response = dr.discoRequest(self, "/admin/baseline")
- return response
-
- def baseline(self):
- '''Get a summary of the appliance status, and details of which baseline checks have passed or failed.'''
- warnings.warn(
- "baseline() is deprecated; use get_admin_baseline instead.",
- DeprecationWarning,
- )
- return self.get_admin_baseline
+ return self.get("/admin/baseline")
@property
def get_admin_about(self):
'''Alternate API call for /admin/about.'''
- response = dr.discoRequest(self, "/admin/about")
- return response
+ return self.get("/admin/about")
@property
def get_admin_licensing(self):
'''Alternate API call for licensing report.'''
- response = dr.discoRequest(self, "/admin/licensing",response="text/plain")
- return response
+ return self.get("/admin/licensing", response="text/plain")
@property
def get_admin_licensing_csv(self):
'''Alternate API call for licensing report CSV.'''
- response = dr.discoRequest(self, "/admin/licensing/csv",response="application/zip")
- return response
+ return self.get("/admin/licensing/csv", response="application/zip")
@property
def get_admin_licensing_raw(self):
'''Alternate API call for licensing report raw.'''
- response = dr.discoRequest(self, "/admin/licensing/raw",response="application/zip")
- return response
-
- def licensing(self,content_type="text/plain"):
- '''Get the latest signed licensing report.'''
- if content_type == "csv":
- response = dr.discoRequest(self, "/admin/licensing/csv",response="application/zip")
- elif content_type == "raw":
- response = dr.discoRequest(self, "/admin/licensing/raw",response="application/zip")
- else:
- response = dr.discoRequest(self, "/admin/licensing",response=content_type)
- return response
-
- @property
- def api_help(self):
- '''Help on endpoints.'''
- endpoints.docs()
- #print("")
+ return self.get("/admin/licensing/raw", response="application/zip")
- def help(*args):
- '''Help on endpoints.'''
- if len(args) > 1:
- endpoints.docs(args[1])
- else:
- endpoints.docs()
- #print("\n")
\ No newline at end of file
diff --git a/tideway/models.py b/tideway/models.py
index a78fb91..286bf3c 100644
--- a/tideway/models.py
+++ b/tideway/models.py
@@ -2,7 +2,6 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Models(appliance):
@@ -30,45 +29,45 @@ def get_model(self,name=None,type=None,kind=None,published=None,review_suggested
self.params['results_id'] = results_id
if delete:
self.params['delete'] = delete
- response = dr.discoRequest(self, "/models")
+ response = self.get("/models")
return response
get_models = property(get_model)
def post_model(self, body):
'''Create a new model.'''
- response = dr.discoPost(self, "/models", body)
+ response = self.post("/models", body)
return response
def post_model_multi(self, body):
'''Manipulate multiple models in a single request.'''
- response = dr.discoPost(self, "/models/multi", body)
+ response = self.post("/models/multi", body)
return response
def delete_model(self, key):
'''Delete a model.'''
- response = dr.discoDelete(self, "/models/{}".format(key))
+ response = self.delete("/models/{}".format(key))
return response
def get_model_key(self, key):
'''Get model definition for the specified key.'''
- req = dr.discoRequest(self, "/models/{}".format(key))
+ req = self.get("/models/{}".format(key))
return req
def patch_model(self, key, body):
'''Modify a model.'''
- response = dr.discoPatch(self, "/models/{}".format(key), body)
+ response = self.patch("/models/{}".format(key), body)
return response
def get_model_topology(self, key, attributes=None):
'''Get topology for the model definition specified by key.'''
if attributes:
self.params['attributes']=attributes
- req = dr.discoRequest(self, "/models/{}/topology".format(key))
+ req = self.get("/models/{}/topology".format(key))
return req
def get_model_nodecount(self, key):
'''Get node count for the model definition specified by key.'''
- req = dr.discoRequest(self, "/models/{}/nodecount".format(key))
+ req = self.get("/models/{}/nodecount".format(key))
return req
def get_model_nodes(self, key, format=None, limit=100, results_id=None, delete=False, kind=None):
@@ -80,38 +79,38 @@ def get_model_nodes(self, key, format=None, limit=100, results_id=None, delete=F
self.params['limit'] = limit
self.params['delete'] = delete
if kind:
- response = dr.discoRequest(self, "/models/{}/nodes/{}".format(key,kind))
+ response = self.get("/models/{}/nodes/{}".format(key,kind))
else:
- response = dr.discoRequest(self, "/models/{}/nodes".format(key))
+ response = self.get("/models/{}/nodes".format(key))
return response
def delete_model_by_node_id(self, node_id):
'''Delete a model.'''
- response = dr.discoDelete(self, "/models/by_node_id/{}".format(node_id))
+ response = self.delete("/models/by_node_id/{}".format(node_id))
return response
def get_model_by_node_id(self, node_id, expand_related=None):
'''Get model definition for the specified node id.'''
if expand_related:
self.params['expand_related'] = expand_related
- response = dr.discoRequest(self, "/models/by_node_id/{}".format(node_id))
+ response = self.get("/models/by_node_id/{}".format(node_id))
return response
def patch_model_by_node_id(self, node_id, body):
'''Modify a model.'''
- response = dr.discoPatch(self, "/models/by_node_id/{}".format(node_id), body)
+ response = self.patch("/models/by_node_id/{}".format(node_id), body)
return response
def get_topology_by_node_id(self, node_id, attributes=None):
'''Get topology for the model definition specified by node id.'''
if attributes:
self.params['attributes']=attributes
- response = dr.discoRequest(self, "/models/by_node_id/{}/topology".format(node_id))
+ response = self.get("/models/by_node_id/{}/topology".format(node_id))
return response
def get_nodecount_by_node_id(self, node_id):
'''Get node count for the model definition specified by node id.'''
- response = dr.discoRequest(self, "/models/by_node_id/{}/nodecount".format(node_id))
+ response = self.get("/models/by_node_id/{}/nodecount".format(node_id))
return response
def get_nodes_by_node_id(self, node_id, format=None, limit=100, results_id=None, delete=False, kind=None):
@@ -123,8 +122,8 @@ def get_nodes_by_node_id(self, node_id, format=None, limit=100, results_id=None,
self.params['limit'] = limit
self.params['delete'] = delete
if kind:
- response = dr.discoRequest(self, "/models/by_node_id/{}/nodes/{}".format(node_id,kind))
+ response = self.get("/models/by_node_id/{}/nodes/{}".format(node_id,kind))
else:
- response = dr.discoRequest(self, "/models/by_node_id/{}/nodes".format(node_id))
+ response = self.get("/models/by_node_id/{}/nodes".format(node_id))
return response
-
\ No newline at end of file
+
diff --git a/tideway/security.py b/tideway/security.py
index 22ae76f..bc177c9 100644
--- a/tideway/security.py
+++ b/tideway/security.py
@@ -2,8 +2,6 @@
import tideway
-# Request helpers
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Security(appliance):
@@ -11,62 +9,62 @@ class Security(appliance):
def get_security_ldap(self):
'''Retrieve LDAP configuration.'''
- return dr.discoRequest(self, "/security/ldap")
+ return self.get("/security/ldap")
get_security_ldaps = property(get_security_ldap)
def put_security_ldap(self, body):
'''Replace LDAP configuration.'''
- return dr.discoPut(self, "/security/ldap", body)
+ return self.put("/security/ldap", body)
def patch_security_ldap(self, body):
'''Update LDAP configuration.'''
- return dr.discoPatch(self, "/security/ldap", body)
+ return self.patch("/security/ldap", body)
def get_security_group(self, group_name=None):
'''Retrieve all groups or a specific group.'''
if group_name:
- return dr.discoRequest(self, f"/security/groups/{group_name}")
- return dr.discoRequest(self, "/security/groups")
+ return self.get( f"/security/groups/{group_name}")
+ return self.get("/security/groups")
get_security_groups = property(get_security_group)
def post_security_group(self, body):
'''Create a new group.'''
- return dr.discoPost(self, "/security/groups", body)
+ return self.post("/security/groups", body)
def patch_security_group(self, group_name, body):
'''Update a group.'''
- return dr.discoPatch(self, f"/security/groups/{group_name}", body)
+ return self.patch( f"/security/groups/{group_name}", body)
def delete_security_group(self, group_name):
'''Delete a group.'''
- return dr.discoDelete(self, f"/security/groups/{group_name}")
+ return self.delete( f"/security/groups/{group_name}")
def get_security_permission(self, permission=None):
'''Retrieve permissions or a specific permission set.'''
if permission:
- return dr.discoRequest(self, f"/security/permissions/{permission}")
- return dr.discoRequest(self, "/security/permissions")
+ return self.get( f"/security/permissions/{permission}")
+ return self.get("/security/permissions")
get_security_permissions = property(get_security_permission)
def get_security_user(self, username=None):
'''Retrieve users or a specific user.'''
if username:
- return dr.discoRequest(self, f"/security/users/{username}")
- return dr.discoRequest(self, "/security/users")
+ return self.get( f"/security/users/{username}")
+ return self.get("/security/users")
get_security_users = property(get_security_user)
def post_security_user(self, body):
'''Create a new user.'''
- return dr.discoPost(self, "/security/users", body)
+ return self.post("/security/users", body)
def patch_security_user(self, username, body):
'''Update a user.'''
- return dr.discoPatch(self, f"/security/users/{username}", body)
+ return self.patch( f"/security/users/{username}", body)
def delete_security_user(self, username):
'''Delete a user.'''
- return dr.discoDelete(self, f"/security/users/{username}")
+ return self.delete( f"/security/users/{username}")
def post_security_token(self, body):
'''Retrieve an authentication token for a user.'''
- return dr.discoPost(self, "/security/token", body)
+ return self.post("/security/token", body)
diff --git a/tideway/taxonomy.py b/tideway/taxonomy.py
index 6ac8848..43c928c 100644
--- a/tideway/taxonomy.py
+++ b/tideway/taxonomy.py
@@ -2,7 +2,6 @@
import tideway
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Taxonomy(appliance):
@@ -11,13 +10,13 @@ class Taxonomy(appliance):
@property
def get_taxonomy_sections(self):
'''Get list of taxonomy model sections.'''
- req = dr.discoRequest(self, "/taxonomy/sections")
+ req = self.get("/taxonomy/sections")
return req
@property
def get_taxonomy_locales(self):
'''Get list of known taxonomy locales.'''
- req = dr.discoRequest(self, "/taxonomy/locales")
+ req = self.get("/taxonomy/locales")
return req
def get_taxonomy_nodekind(self, format=None, section=None, locale=None, kind=None, fieldlists=False):
@@ -26,21 +25,21 @@ def get_taxonomy_nodekind(self, format=None, section=None, locale=None, kind=Non
self.params['format']=format
self.params['section']=section
self.params['locale']=locale
- req = dr.discoRequest(self, "/taxonomy/nodekinds")
+ req = self.get("/taxonomy/nodekinds")
elif kind:
self.params['locale']=locale
if fieldlists:
- req = dr.discoRequest(self, "/taxonomy/nodekinds/{}/fieldlists".format(kind))
+ req = self.get("/taxonomy/nodekinds/{}/fieldlists".format(kind))
else:
- req = dr.discoRequest(self, "/taxonomy/nodekinds/{}".format(kind))
+ req = self.get("/taxonomy/nodekinds/{}".format(kind))
else:
- req = dr.discoRequest(self, "/taxonomy/nodekinds")
+ req = self.get("/taxonomy/nodekinds")
return req
get_taxonomy_nodekinds = property(get_taxonomy_nodekind)
def get_taxonomy_nodekind_fieldlist(self, kind, fieldlist):
'''Get list of fields for a node kind field list.'''
- req = dr.discoRequest(self, "/taxonomy/nodekinds/{}/fieldlists/{}".format(kind,fieldlist))
+ req = self.get("/taxonomy/nodekinds/{}/fieldlists/{}".format(kind,fieldlist))
return req
def get_taxonomy_relkind(self, format=None, locale=None, kind=None):
@@ -48,11 +47,11 @@ def get_taxonomy_relkind(self, format=None, locale=None, kind=None):
if format:
self.params['format']=format
self.params['locale']=locale
- req = dr.discoRequest(self, "/taxonomy/relkinds")
+ req = self.get("/taxonomy/relkinds")
elif kind:
self.params['locale']=locale
- req = dr.discoRequest(self, "/taxonomy/relkinds/{}".format(kind))
+ req = self.get("/taxonomy/relkinds/{}".format(kind))
else:
- req = dr.discoRequest(self, "/taxonomy/relkinds")
+ req = self.get("/taxonomy/relkinds")
return req
- get_taxonomy_relkinds = property(get_taxonomy_relkind)
\ No newline at end of file
+ get_taxonomy_relkinds = property(get_taxonomy_relkind)
diff --git a/tideway/topology.py b/tideway/topology.py
index 02d69ae..36197c1 100644
--- a/tideway/topology.py
+++ b/tideway/topology.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import tideway
-import warnings
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Topology(appliance):
@@ -14,94 +12,29 @@ def get_data_nodes_graph(self, node_id, focus="software-connected", apply_rules=
self.params['focus'] = focus
self.params['apply_rules'] = apply_rules
self.params['complete'] = complete
- response = dr.discoRequest(self, "/data/nodes/{}/graph".format(node_id))
+ response = self.get("/data/nodes/{}/graph".format(node_id))
return response
- def graphNode(self, node_id, focus="software-connected", apply_rules=True):
- '''
- Graph data represents a set of nodes and relationships that are
- associated to the given node.
- '''
- warnings.warn(
- "graphNode() is deprecated; use get_data_nodes_graph() instead.",
- DeprecationWarning,
- )
- return self.get_data_nodes_graph(
- node_id,
- focus=focus,
- apply_rules=apply_rules,
- complete=False,
- )
-
def post_topology_nodes(self, body):
'''Alternate API call for POST /topology/nodes.'''
- response = dr.discoPost(self, "/topology/nodes", body)
+ response = self.post("/topology/nodes", body)
return response
- def getNodes(self, body):
- '''Get topology data from one or more starting nodes.'''
- warnings.warn(
- "getNodes() is deprecated; use post_topology_nodes() instead.",
- DeprecationWarning,
- )
- return self.post_topology_nodes(body)
-
def post_topology_nodes_kinds(self, body):
'''Alternate API call for POST /topology/nodes/kinds.'''
- response = dr.discoPost(self, "/topology/nodes/kinds", body)
+ response = self.post("/topology/nodes/kinds", body)
return response
- def getNodeKinds(self, body):
- '''
- Get nodes of the specified kinds which are related to a given set of
- nodes.
- '''
- warnings.warn(
- "getNodeKinds() is deprecated; use post_topology_nodes_kinds() instead.",
- DeprecationWarning,
- )
- return self.post_topology_nodes_kinds(body)
-
- def visualizationState(self):
- '''
- Get the current state of the visualization for the authenticated
- user.
- '''
- warnings.warn(
- "visualizationState() is deprecated; use get_topology_viz_state instead.",
- DeprecationWarning,
- )
- return dr.discoRequest(self, "/topology/visualization_state")
- get_topology_viz_state = property(visualizationState)
+ def get_topology_viz_state(self):
+ '''Get the current visualization state for the authenticated user.'''
+ return self.get("/topology/visualization_state")
def patch_topology_viz_state(self, body):
'''Alternate API call for PATCH /topology/visualization_state'''
- response = dr.discoPatch(self, "/topology/visualization_state", body)
+ response = self.patch("/topology/visualization_state", body)
return response
- def updateVizState(self, body):
- '''
- Update one or more attributes of the current state of the
- visualization for the authenticated user.
- '''
- warnings.warn(
- "updateVizState() is deprecated; use patch_topology_viz_state() instead.",
- DeprecationWarning,
- )
- return self.patch_topology_viz_state(body)
-
def put_topology_viz_state(self, body):
'''Alternate API call for PUT /topology/visualization_state'''
- response = dr.discoPut(self, "/topology/visualization_state", body)
+ response = self.put("/topology/visualization_state", body)
return response
-
- def replaceVizState(self, body):
- '''
- Update any or all of the attributes of the current state of the
- visualization for the authenticated user.
- '''
- warnings.warn(
- "replaceVizState() is deprecated; use put_topology_viz_state() instead.",
- DeprecationWarning,
- )
- return self.put_topology_viz_state(body)
diff --git a/tideway/vault.py b/tideway/vault.py
index 91a889d..1fa006b 100644
--- a/tideway/vault.py
+++ b/tideway/vault.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import tideway
-import warnings
-dr = tideway.discoRequests
appliance = tideway.main.Appliance
class Vault(appliance):
@@ -11,26 +9,10 @@ class Vault(appliance):
def get_vault(self):
'''Get details of the state of the vault.'''
- return dr.discoRequest(self, "/vault")
-
- def getVault(self):
- '''Get details of the state of the vault.'''
- warnings.warn(
- "getVault() is deprecated; use get_vault() instead.",
- DeprecationWarning,
- )
- return self.get_vault()
+ return self.get("/vault")
get_vault_property = property(get_vault)
def patch_vault(self, body):
'''Alternate API call for PATCH /vault'''
- response = dr.discoPatch(self, "/vault", body)
+ response = self.patch("/vault", body)
return response
-
- def updateVault(self, body):
- '''Change the state of the vault.'''
- warnings.warn(
- "updateVault() is deprecated; use patch_vault() instead.",
- DeprecationWarning,
- )
- return self.patch_vault(body)