├── .gitignore
├── LICENSE
├── README.md
├── examples
├── With-pynetbox-client.ipynb
├── holding.txt
├── setup.env
├── testdrive.py
└── testdrive_pynb.py
├── netbox_pyswagger
├── __init__.py
├── client.py
└── patch_2_2.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # PyCharm
71 | .idea
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # SageMath parsed files
83 | *.sage.py
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jeremy Schulman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python Swagger Client for Netbox
2 |
3 | If you love [Netbox](https://github.com/digitalocean/netbox), you want to automate it
4 | via the REST API, and you want a client that supports an interactive enviornment like [ipython](https://ipython.org/)
5 | and [jupyter notesbooks](http://jupyter.org/), then this client is for you!
6 |
7 | The gist of this client is that it consumes the Netbox Swagger 2.0 spec and uses that information
8 | to dynamically create a client with all API capabilities, model parameters as objects, and perform data validataton
9 | on all request parameters and responses. You never have to worry about the client being "out of sync"
10 | with the Netbox API. This client is specifically designed so that you can *introspect*, that is show in a "help" like manner,
11 | everything about each API, parameter, and data-type so that you do not need to have any API docs. You can use
12 | this client in an interactive Python shell, such as ipython and jupyter notebooks, in order to create a *CLI-like* user
13 | experience and not require **hard-core** programming to automate the Netbox system. For these purpose, this
14 | client was built with the halutz package - see these [tutorials](https://github.com/jeremyschulman/halutz/tree/master/docs)
15 | for usage.
16 |
17 | ---
18 | **NOTE:** If you are already using the pynetbox
19 | client you can use both together. See this example [tutorial](examples/With-pynetbox-client.ipynb).
20 |
21 | ---
22 |
23 | # Installation
24 |
25 | ```bash
26 | pip install netbox-pyswagger
27 | ```
28 |
29 | # Quickstart
30 |
31 | This example shows you how to create a client, and create a VLAN.
32 | ```python
33 | from netbox_pyswagger.client import Client
34 |
35 | server_url = "localhost:32768"
36 | api_token = "0123456789abcdef0123456789abcdef01234567"
37 |
38 | netbox = Client(server_url, api_token=api_token)
39 |
40 | # Use the `request` attribute to access a specific API based on the Swagger tag value
41 | # (ipam) and the Swagger operationId value (ipam_vlans_create)
42 |
43 | new_vlan = netbox.request.ipam.ipam_vlans_create
44 |
45 | # the command body parameter is called 'data' and here we set the required values
46 | # if you set an invalid value, like setting `vid` to "foobaz", you will get a validation
47 | # exception
48 |
49 | new_vlan.data.vid = 10
50 | new_vlan.data.name = 'Blue'
51 |
52 | # finally execute the command, which provides us the response data and the indication
53 | # if the command executed ok or not. If the command had addition (non-body) parameters
54 | # you would provide them as key-value pairs to the call.
55 |
56 | resp, ok = new_vlan()
57 | ```
58 |
59 | The variable `ok` is True when the command is successful, and here is the example output
60 | of the response data, `resp`:
61 |
62 | ```json
63 | {
64 | "status": 1,
65 | "group": null,
66 | "name": "Blue",
67 | "vid": 10,
68 | "site": null,
69 | "role": null,
70 | "id": 8,
71 | "tenant": null,
72 | "description": ""
73 | }
74 | ```
75 |
76 | ## Built With
77 |
78 | - [bravado](https://github.com/Yelp/bravado) for consuming the Swagger spec
79 | - [jsonschemas](https://github.com/Julian/jsonschema) for validating Swagger json-schema
80 | - [python-jsonschema-objects](https://github.com/cwacek/python-jsonschema-objects) for classbuilding
81 | the json-schema objects
82 | - [halutz](https://github.com/jeremyschulman/halutz) for wrapping all the above together
83 |
84 | ## Other Links
85 |
86 | - [Swagger 2.0 Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md)
87 | - [JSON-schema](http://json-schema.org/)
88 |
89 | ## Acknowledgements
90 |
91 | The Netbox probject is quite amazing, with a vibrant community of users and contributors! Many
92 | thanks to [@jeremystretch](https://github.com/jeremystretch]) and [@digitalocean](https://github.com/digitalocean)!
93 |
94 | ## Questions / Contributes?
95 |
96 | Please use the issues link to ask questions. Contributions are welcome and apprecaited.
--------------------------------------------------------------------------------
/examples/With-pynetbox-client.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Using both netbox-pyswagger and pynetbox\n",
8 | "\n",
9 | "This tutorial illustrates how you can use both the `netbox-pyswagger` client when you have, or want to have, a `pynetbox` client as well. The `pynetbox` client is very powerful as a standalone and offers many excellent features and capabilities. The `netbox-pyswagger` client adds the \"make it like a CLI\" experience. Let's get started, using\n",
10 | "a locally hosted Netbox:"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 11,
16 | "metadata": {},
17 | "outputs": [],
18 | "source": [
19 | "server_url = 'http://localhost:32768'\n",
20 | "token = '0123456789abcdef0123456789abcdef01234567'"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "metadata": {},
26 | "source": [
27 | "Now let's create the netbox-pyswagger client. There is a classmethod specifically designed to bolt into the pynetbox client. This example simulatenously creates the pynetbox client and passes it directly to create the netbox-pyswagger client."
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": 12,
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "import pynetbox\n",
37 | "from netbox_pyswagger.client import Client\n",
38 | "\n",
39 | "netbox = Client.from_pynetbox(pynetbox.api(server_url, token=token))"
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "The netbox-pyswagger client retains the instance of the pynetbox client in an attribute called `remote`. For example:"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": 13,
52 | "metadata": {},
53 | "outputs": [
54 | {
55 | "data": {
56 | "text/plain": [
57 | ""
58 | ]
59 | },
60 | "execution_count": 13,
61 | "metadata": {},
62 | "output_type": "execute_result"
63 | }
64 | ],
65 | "source": [
66 | "netbox.remote"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "So you can then use this attribute to perform any pynetbox API call. Let's use it to create a VLAN:"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": 14,
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "resp = netbox.remote.ipam.vlans.create(name='Green', vid=1001)"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "And we can introspect the `resp` value:"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": 15,
95 | "metadata": {},
96 | "outputs": [
97 | {
98 | "data": {
99 | "text/plain": [
100 | "{u'description': u'',\n",
101 | " u'group': None,\n",
102 | " u'id': 9,\n",
103 | " u'name': u'Green',\n",
104 | " u'role': None,\n",
105 | " u'site': None,\n",
106 | " u'status': 1,\n",
107 | " u'tenant': None,\n",
108 | " u'vid': 1001}"
109 | ]
110 | },
111 | "execution_count": 15,
112 | "metadata": {},
113 | "output_type": "execute_result"
114 | }
115 | ],
116 | "source": [
117 | "resp"
118 | ]
119 | },
120 | {
121 | "cell_type": "markdown",
122 | "metadata": {},
123 | "source": [
124 | "# Next Steps\n",
125 | "\n",
126 | "Please review the [halutz tutorials](https://github.com/jeremyschulman/halutz/tree/master/docs) to learn how to best utilize the netbox-pyswagger client."
127 | ]
128 | }
129 | ],
130 | "metadata": {
131 | "kernelspec": {
132 | "display_name": "Python 2",
133 | "language": "python",
134 | "name": "python2"
135 | },
136 | "language_info": {
137 | "codemirror_mode": {
138 | "name": "ipython",
139 | "version": 2
140 | },
141 | "file_extension": ".py",
142 | "mimetype": "text/x-python",
143 | "name": "python",
144 | "nbconvert_exporter": "python",
145 | "pygments_lexer": "ipython2",
146 | "version": "2.7.10"
147 | },
148 | "toc": {
149 | "nav_menu": {},
150 | "number_sections": true,
151 | "sideBar": true,
152 | "skip_h1_title": false,
153 | "toc_cell": false,
154 | "toc_position": {},
155 | "toc_section_display": "block",
156 | "toc_window_display": false
157 | }
158 | },
159 | "nbformat": 4,
160 | "nbformat_minor": 2
161 | }
162 |
--------------------------------------------------------------------------------
/examples/holding.txt:
--------------------------------------------------------------------------------
1 | The more interesting examples are when you are doing comamnds that have body parameters
2 | such as POST and PATCH. See [this tutorial](https://github.com/jeremyschulman/halutz/blob/master/docs/Request-Body.ipynb) for a good example.
3 |
4 | # Example - with pynetbox
5 |
6 | Here is the same example of using netbox-pyswagger and pynetbox
7 | client. When you use this approach, the netbox-pyswagger instance
8 | maintains an attribute called `remote` which is set to the pynetbox
9 | instance you provided.
10 |
11 | ```python
12 | import os
13 | import sys
14 |
15 | import pynetbox
16 | from netbox_pyswagger import Client
17 |
18 | base_url = os.getenv('NETBOX_SERVER')
19 | if not base_url:
20 | sys.exit('NETBOX_SERVER not found in enviornment')
21 |
22 | netbox_token = os.getenv('NETBOX_TOKEN')
23 | if not netbox_token:
24 | sys.exit('NETBOX_TOKEN not found in environment')
25 |
26 | # create the netbox-pyswagger instance, passing in the
27 | # pynetbox client instance
28 |
29 | netbox = Client(pynb=pynetbox.api(url=base_url, token=netbox_token))
30 |
31 | # run the command to get all VLANs using the pynetbox method
32 |
33 | resp = netbox.remote.ipam.vlans.all()
34 | ```
35 |
36 | The `resp` contains the pynetbox VLAN instance values. For the
37 | same example, `resp` would contain the list `[green, blue]`.
38 | For example, dumping out the *green* instance:
39 |
40 | ```python
41 | for name, value in resp[0]:
42 | print(name, value)
43 | ```
44 |
45 | results in:
46 | ```bash
47 | status {u'value': 1, u'label': u'Active'}
48 | group None
49 | name green
50 | vid 99
51 | site None
52 | role None
53 | tenant None
54 | display_name 99 (green)
55 | id 4
56 | custom_fields {}
57 | description
58 | ```
59 |
--------------------------------------------------------------------------------
/examples/setup.env:
--------------------------------------------------------------------------------
1 | export NETBOX_TOKEN=0123456789abcdef0123456789abcdef01234567
2 | export NETBOX_SERVER=http://localhost:32768
3 |
--------------------------------------------------------------------------------
/examples/testdrive.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import os
4 | import sys
5 |
6 |
7 | from netbox_pyswagger.client import Client
8 |
9 |
10 | server_url = os.getenv('NETBOX_SERVER')
11 | if not server_url:
12 | sys.exit('NETBOX_SERVER not found in enviornment')
13 |
14 |
15 | netbox_token = os.getenv('NETBOX_TOKEN')
16 | if not netbox_token:
17 | sys.exit('NETBOX_TOKEN not found in environment')
18 |
19 |
20 |
21 | netbox = Client(server_url, api_token=netbox_token)
22 |
--------------------------------------------------------------------------------
/examples/testdrive_pynb.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import os
4 | import sys
5 |
6 | import pynetbox
7 | from netbox_pyswagger.client import Client
8 |
9 |
10 | server_url = os.getenv('NETBOX_SERVER')
11 | if not server_url:
12 | sys.exit('NETBOX_SERVER not found in enviornment')
13 |
14 |
15 | netbox_token = os.getenv('NETBOX_TOKEN')
16 | if not netbox_token:
17 | sys.exit('NETBOX_TOKEN not found in environment')
18 |
19 |
20 | netbox = Client.from_pynetbox(
21 | pynetbox.api(url=server_url, token=netbox_token))
22 |
--------------------------------------------------------------------------------
/netbox_pyswagger/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.2.0'
2 | __author__ = 'Jeremy Schulman'
3 |
4 |
--------------------------------------------------------------------------------
/netbox_pyswagger/client.py:
--------------------------------------------------------------------------------
1 |
2 | from requests import Session
3 | from halutz.client import Client as HalutzClient
4 |
5 | __all__ = ['Client']
6 |
7 |
8 | class Client(HalutzClient):
9 | apidocs_url = "/api/docs?format=openapi"
10 |
11 | def __init__(self, api_token, **halutz_kwargs):
12 | # the api version will get set when the spec is loaded from the server
13 | self.api_version = None
14 |
15 | kwargs = halutz_kwargs or {}
16 |
17 | if 'session' not in kwargs:
18 | kwargs['session'] = Session()
19 |
20 | # setup the token in the session headers
21 | kwargs['session'].headers['Authorization'] = "Token %s" % api_token
22 |
23 | # invoke the halutz initializer
24 | super(Client, self).__init__(**kwargs)
25 |
26 | @classmethod
27 | def from_pynetbox(cls, pynb):
28 | kwargs = dict()
29 | kwargs['api_token'] = pynb.api_kwargs['token']
30 | kwargs['server_url'] = pynb.api_kwargs['base_url'].split('/api')[0]
31 | kwargs['remote'] = pynb
32 |
33 | return cls(**kwargs)
34 |
35 | def fetch_swagger_spec(self):
36 | url = "%s%s" % (self.server_url, self.apidocs_url)
37 | resp = self.session.get(url)
38 |
39 | if not resp.ok:
40 | raise RuntimeError(
41 | 'unable to fetch Swagger spec:', resp)
42 |
43 | spec = resp.json()
44 | if 'paths' not in spec:
45 | if spec.get('details', '') == 'Invalid token':
46 | raise RuntimeError('Invalid API token provided')
47 |
48 | self.api_version = resp.headers['API-Version']
49 |
50 | if self.api_version == "2.2":
51 | from .patch_2_2 import patch_spec
52 | patch_spec(spec)
53 |
54 | return spec
55 |
--------------------------------------------------------------------------------
/netbox_pyswagger/patch_2_2.py:
--------------------------------------------------------------------------------
1 |
2 | def patch_spec(spec):
3 | # Fix for Netbox #1853 https://github.com/digitalocean/netbox/issues/1853
4 | for path in spec['paths'].keys():
5 | for method in spec['paths'][path].keys():
6 |
7 | if 'parameters' not in spec['paths'][path][method].keys():
8 | continue
9 |
10 | for param in spec['paths'][path][method]['parameters']:
11 | if param['in'] != 'body':
12 | continue
13 |
14 | if 'custom_fields' not in param['schema']['properties'].keys():
15 | continue
16 |
17 | if param['schema']['properties']['custom_fields']['type'] == 'string':
18 | param['schema']['properties']['custom_fields']['type'] = 'object'
19 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup
5 | import netbox_pyswagger
6 |
7 |
8 | def read(fname):
9 | with open(fname) as fp:
10 | content = fp.read()
11 | return content
12 |
13 |
14 | setup(
15 | name='netbox-pyswagger',
16 | version=netbox_pyswagger.__version__,
17 | description='Swagger client for Netbox',
18 | author='Jeremy Schulman',
19 | author_email='nwkautomaniac@gmail.com',
20 | url='https://github.com/jeremyschulman/netbox-pyswagger',
21 | packages=['netbox_pyswagger'],
22 | license='MIT',
23 | zip_safe=False,
24 | install_requires=[
25 | 'halutz>=0.3.0',
26 | 'requests',
27 | 'six'
28 | ],
29 | keywords=('netbox', 'rest', 'json', 'api',
30 | 'network', 'automation'),
31 | classifiers=[
32 | 'Development Status :: 4 - Beta',
33 | 'Intended Audience :: Developers',
34 | 'License :: OSI Approved :: MIT License',
35 | 'Programming Language :: Python :: 2.7',
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------