-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtowerbox.py
224 lines (177 loc) · 6.62 KB
/
towerbox.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env python
"""
This script creates an inventory in Ansible Tower based on information in NetBox.
Add this script as a "custom script" in "Inventory scripts" in Ansible Tower.
Then create a new inventory using the newly added inventory script.
Once executed, the inventory will contain hosts defined in NetBox.
Modify the variables NETBOX_HOST_URL and NETBOX_AUTH_TOKEN according to your NetBox setup.
"""
from __future__ import print_function, unicode_literals
import argparse
from collections import defaultdict
import json
import six
from six.moves.urllib.parse import urlencode, urlparse
if six.PY2:
from httplib import HTTPSConnection, HTTPConnection
else:
from http.client import HTTPSConnection, HTTPConnection
NETBOX_HOST_URL = 'https://example.com'
NETBOX_AUTH_TOKEN = '0123456789abcdef0123456789abcdef01234567'
class Device(object):
"""A NetBox device.
This class implements the data model of a NetBox Device object that
can be serialised by this module's `NetBoxInventory`.
"""
default_ssh_port = 22
default_ssh_user = 'root'
api_url = '/api/dcim/devices/'
def __init__(self, ns):
"""Store the namespace.
Parameters:
ns:dict The raw data payload for this NetBox object
"""
self._ns = ns
@property
def name(self):
"""The name of this object.
This is used as the FQDN of the host in Ansible Tower.
Returns:
str
"""
return self._ns['name']
@property
def ip_address(self):
"""The primary IP address of this host.
Returns:
str
"""
if self._ns['primary_ip'] is None:
address = "0.0.0.0"
else:
address = self._ns['primary_ip']['address'].split('/')[0]
return address
@property
def platform(self):
"""The platform of this object.
Returns:
str
"""
if self._ns['platform'] is None:
platform = "undefined"
else:
platform = self._ns['platform']['slug']
return platform
@property
def device_role(self):
"""The device role of this object.
Returns:
str
"""
if self._ns['device_role'] is None:
device_role = "undefined"
else:
device_role = self._ns['device_role']['slug']
return device_role
@property
def hostvars(self):
"""The Host Vars associated with this object.
These are typically used as Ansible Facts in the execution of a Job.
Returns:
Dict[str, str]
"""
return {
self.name: {
'ansible_port': self.default_ssh_port,
'ansible_host': self.ip_address,
'ansible_user': self.default_ssh_user,
'netbox_device_role': self.device_role,
'netbox_platform': self.platform,
'netbox_tags': [slug_dict['slug'] for slug_dict in self._ns['tags']],
'netbox_status': self._ns['status']['value']
}
}
def __getitem__(self, item):
"""Attribute queries on this object are delegated to its namespace."""
return self._ns[item]
class HttpClient(object):
"""A simple HTTP client that is based on the Python 2's stdlib."""
def __init__(self, host_url, headers=None):
"""Initialise the object with the host URL and any headers to use.
Parameters:
host_url:str the URL of the host, e.g. https://tower.local
headers:Dict any headers to include in the HTTP requests
"""
url = urlparse(host_url)
conn_type = HTTPConnection if url.scheme == 'http' else HTTPSConnection
self.conn = conn_type(url.netloc)
self.headers = {} if headers is None else headers
def get(self, path, params=None):
"""Perform a GET request for a given path on the remote host.
Parameters:
path:str The URL path
Returns:
Response The response object provided by the library.
"""
encoded_params = urlencode(params or {})
self.conn.request('GET', path, encoded_params, self.headers)
return self.conn.getresponse()
class NetBoxInventory(object):
"""A NetBox inventory for the DCIM Device type"""
grouping = 'site'
entity_type = Device
def __init__(self, host_url, token='', http_client=None):
"""Initialise with the host URL, the API Token, and a given client.
Parameters:
host_url:str The URL of the host, e.g. https://tower.local
token:AnyStr A valid NetBox API token
http_client:HttpClient This module's http client
"""
headers = {'Authorization': 'Token ' + token} if token else {}
self.client = http_client or HttpClient(host_url, headers=headers)
@property
def entities(self):
"""The entities, i.e. hosts, associated with this inventory.
Returns:
Generator[Device, None, None]
"""
next_path = self.entity_type.api_url
while next_path:
response = self.client.get(next_path)
data = json.load(response)
next_path = data['next']
for item in data['results']:
yield self.entity_type(item)
def as_ansible_namespace(self):
"""Serialise the objects into an Ansible Tower compatible namespace.
Objects must implement the following interface:
class Object:
@property
@abstractmethod
def name(self) -> Text:
'''The name of the host, i.e. the FQDN.'''
@property
@abstractmethod
def hostvars(self) -> Dict[str, Dict[str, Dict[str, str]]]:
'''The HostVars for this host.'''
"""
ns = defaultdict(lambda: defaultdict(list))
_meta_ns = defaultdict(dict)
for entity in self.entities:
device_group = entity[self.grouping]['slug']
ns[device_group]['hosts'] += [entity.name]
_meta_ns['hostvars'].update(entity.hostvars)
ns.update({'_meta': _meta_ns})
return ns
def main():
"""Show help or dump NetBox device information in Ansible format."""
parser = argparse.ArgumentParser()
parser.add_argument('--list', dest='list', action='store_true')
args = parser.parse_args()
netbox_inv = NetBoxInventory(NETBOX_HOST_URL, token=NETBOX_AUTH_TOKEN)
if args.list:
print(json.dumps(netbox_inv.as_ansible_namespace()))
else:
parser.print_usage()
if __name__ == '__main__':
main()