/
nsoneimporter.py
495 lines (360 loc) · 16.2 KB
/
nsoneimporter.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
from nsone import NSONE, Config
from nsone.rest.errors import ResourceException
from twisted.internet import defer, reactor, task
class NsoneImporter(object):
"""
Attributes:
config (nsone.Config): The configuration for the nsone requests.
nsoneObj (nsone.NSONE): Instance of the nsone object used for http requests
data (dict): Dictionary containing the zone data used by all methods for importing
deleteData (bool): Attribute used to call deletion endpoints instead of importing
"""
config = Config()
def __init__(self, apiKey, data, delete):
"""
Args:
apiKey (str): The Nsone Api Key
data (Dict): Zone Data Dict
delete (bool): Delete Flag, defaults to false from argument parser
"""
self.config.createFromAPIKey(apiKey)
self.config['transport'] = 'twisted'
self.nsoneObj = NSONE(config=self.config)
self.data = data
self.deleteData = delete
def _deleteZoneData(self):
"""
Parent method that triggers callback chain for deleting all zones.
Makes call to deleteZonesandRecords method and depending on the response,
the success or error callbacks are fired.
Each call to deleteZonesandRecords is an instance of a deferred object and
each object is collected for graceful termination
"""
dl = []
for zoneName, records in self.data:
deleteZoneRes = self._deleteZonesAndRecords(zoneName, records, self.nsoneObj)
deleteZoneRes.addCallback(self._deleteZoneSuccess, zoneName)
deleteZoneRes.addErrback(self._deleteZoneFailure, zoneName)
dl.append(deleteZoneRes)
return defer.DeferredList(dl, fireOnOneErrback=True)
@defer.inlineCallbacks
def _deleteZonesAndRecords(self, zoneName, records, nsoneObj):
"""
Returns a deferred object that calls the delete method of the nsone object.
Args:
zoneName (str): The zone name from the data dictionary
records (Dict): a list of records belonging to this zone
nsoneObj (nsone.NSONE): Instance of the nsone object
"""
zone = yield nsoneObj.loadZone(zoneName)
yield zone.delete()
def _deleteZoneSuccess(self, response, zoneName):
"""
Success callback for deleteZonesAndRecords deferred object.
Triggered if there are no errors when calling the api
Args:
response (None): Upon success, the delete method returns None
zoneName (str): The zone name
"""
print 'Successfully Deleted Zone: {}'.format(zoneName)
def _deleteZoneFailure(self, failure, zoneName):
"""
Error callback for deleteZonesAndRecords deferred object.
Triggered if there are errors when calling the api
Args:
failure (twisted.python.failure)
zoneName (str): The zone name
"""
print '{}: {}'.format(zoneName, failure.getErrorMessage())
def _importZoneData(self):
"""
The parent method that triggers all of the callback chains for importing zone data.
Loops through all of the zone data and creates a deferred object for every
zone and adds Success and Error callbacks for each deferred instance.
Tracks all of the deferreds by appending them to a deferred list.
If no errors occur, the success callback is triggered. If even a single
error occurs, the fireOnOneErrback parameter is set to true and
the error callback is triggered and the program exits.
NOTE: Auth exceptions are not explicitely checked since if an
nsone.rest.errors.AuthException is raised, the deferred list error back
will fire and the program will exit
Returns:
defer.DeferredList
"""
dl = []
for zoneName, records in self.data:
zone = self._createZone(zoneName)
zone.addCallback(self._createZoneSuccess, zoneName, records, self.nsoneObj)
zone.addErrback(self._createZoneFailure, zoneName, records, self.nsoneObj)
dl.append(zone)
return defer.DeferredList(dl, fireOnOneErrback=True)
@defer.inlineCallbacks
def _createZone(self, zoneName):
"""
Returns the result of the deferred zone api call when available.
Args:
zoneName (str): The zone name in the data dictionary
Returns:
nsone.zones.Zone
"""
zone = yield self.nsoneObj.createZone(zoneName)
defer.returnValue(zone)
def _createZoneSuccess(self, response, zoneName, records, nsoneObj):
"""
Success callback for _createZone deferred object.
Triggered if there are no errors when calling the api.
If a zone is created successfully, then the next logical step
during the import process is to add all of the records for that zone
This method calls _createRecords which creates
deferred objects for all of the records in the record list and adde
both success and error callbacks for each instance.
Each deferred record is tracked with the DeferredList object
and the error back is fired as soon as one error back is fired
Args:
response (nsone.zones.Zone): The zone returned from createZone
zoneName (str): The zone name
records (list): The list of records belonging to the zone
nsoneObj (nsone.NSONE): Instance of the nsone object
Returns:
defer.DeferredList
"""
return self._createRecords(response, zoneName, records, nsoneObj)
@defer.inlineCallbacks
def _createZoneFailure(self, failure, zoneName, records, nsoneObj):
"""
Failure Error callback if a zone cannot be created
If a zone fails to be created, it is likely due to the fact that
it exists already. The next logical step is to try to load the existing zone.
A deferred zone is created with the _loadZone method. If a zone is loaded
successfully, the _loadZoneSuccess callback is fired. Othewise, the error
callback is fired
A deferred zone object is yielded and the response from _loadZone is returned
to the generator when it is available.
Args:
failure (twisted.python.failure)
zoneName (str): The zone name
records (list): The list of records belonging to the zone
nsoneObj (nsone.NSONE): Instance of the nsone object
Yields:
twisted.internet.defer
"""
f = failure.trap(ResourceException)
print failure.getErrorMessage()
zone = self._loadZone(zoneName, nsoneObj)
zone.addCallback(self._loadZoneSuccess, zoneName, records, nsoneObj)
zone.addErrback(self._loadZoneFailure, zoneName)
yield zone
@defer.inlineCallbacks
def _loadZone(self, zoneName, nsoneObj):
"""
Gets the result of the deferred load zone api call when available.
Args:
zoneName (str): The zone name in the data dictionary
nsoneObj (nsone.NSONE): Instance of the nsone object
Returns:
nsone.zones.Zone
"""
zone = yield nsoneObj.loadZone(zoneName)
defer.returnValue(zone)
def _loadZoneSuccess(self, response, zoneName, records, nsoneObj):
"""
Success callback for _loadZone deferred object.
Triggered if there are no errors when calling the api.
If a zone is loaded successfully, then the next logical step
during the import process is to add all of the records for that zone
similar to creating a zone
This method calls a helper method that creates deferred objects
for all of the records in the record list and addes both success
and error callbacks for each instance.
Each deferred record is tracked with the DeferredList object
and the error back is fired as soon as one error back is fired
Args:
response (nsone.zones.Zone): The zone returned from createZone
zoneName (str): The zone name
records (list): The list of records belonging to the zone
nsoneObj (nsone.NSONE): Instance of the nsone object
Returns:
defer.DeferredList
"""
print 'Successfully Loaded Zone: {}'.format(zoneName)
return self._createRecords(response, zoneName, records, nsoneObj)
def _loadZoneFailure(self, failure, zoneName):
"""
Triggered when a zone cannot be loaded
Prints the error message from the failure
Args:
failure (twisted.python.failure):
zoneName (str): The zone name
"""
print '{}: {}'.format(zoneName, failure.getErrorMessage())
def _createRecords(self, response, zoneName, records, nsoneObj):
"""
This method reates deferred objects
for all of the records in the record list and addes both success
and error callbacks for each instance.
Records are logically created either after creating a zone
or loading a zone successfully which is why this functionality
is modularized into this function to remove duplicate logic.
Each deferred record is tracked with the DeferredList object
and the error back is fired as soon as one error back is fired
Args:
response (nsone.zones.Zone): The zone returned from createZone
zoneName (str): The zone name
records (list): The list of records belonging to the zone
nsoneObj (nsone.NSONE): Instance of the nsone object
Returns:
defer.DeferredList
"""
dl = []
zone = response
for rec in records:
answers = rec['Data'].split()
methodName = 'add_{}'.format(rec['Type'])
addMethod = getattr(zone, methodName)
record = self._createRecord(addMethod, zoneName, [answers], rec['TTL'])
record.addCallback(self._createRecordSuccess)
record.addErrback(self._createRecordFailure, zoneName, rec['Type'], answers, nsoneObj)
dl.append(record)
return defer.DeferredList(dl, fireOnOneErrback=True)
@defer.inlineCallbacks
def _createRecord(self, addMethod, zoneName, answers, ttl):
"""
Calls the add_X method on zones for creating records and
returns the value when it's available
Args:
addMethod (function) : add_X method of the zone where X is dynamic
zoneName (str): The zone name from the data dict
answers (list): The answers for the record in list form
ttl (str): The TTL value for the record
Return:
nsone.records.Record
"""
record = yield addMethod(zoneName, answers, ttl=ttl)
defer.returnValue(record)
def _createRecordSuccess(self, response):
"""
Triggered when a record is created successfully
If a record is created successfully, an nsone record instance is returned
Args:
response (nsone.records.Record): an instance of an nsone record object
"""
print 'Created record: {}'.format(response)
@defer.inlineCallbacks
def _createRecordFailure(self, failure, zoneName, recType, answers, nsoneObj):
"""
Triggered when a record cannot be created.
Logically if a record can't be created, an attempt at loading it is made.
A deferred record object is created and both success and error callbacks
are chained onto it.
Args:
failure (twisted.python.failure): the failure object
Yields:
nsone.records.Record
"""
f = failure.trap(ResourceException)
if f == ResourceException:
record = self._loadRecord(zoneName, recType, nsoneObj)
record.addCallback(self._loadRecordSuccess, answers)
record.addErrback(self._loadRecordFailure)
yield record
@defer.inlineCallbacks
def _loadRecord(self, zoneName, recType, nsoneObj):
"""
Calls the loadRecord method on nsoneObj
returns the value of the record when it's available
Args:
zoneName (str): The zone name from the data dict
recType (str): The record type
nsoneObj (nsone.NSONE): Instance of the nsone object
Return:
nsone.records.Record
"""
record = yield nsoneObj.loadRecord(zoneName, recType, zoneName)
defer.returnValue(record)
@defer.inlineCallbacks
def _loadRecordSuccess(self, response, answers):
"""
Triggered when a record is successfully loaded
Logically if a record is loaded the next step would be to
try to add answers to it.
A deferred object is yielded and the response from the
addAnswers method on the record is returned to the generator
when available. The response from the method triggers either
the succes or error callback on the deferred object.
Args:
response (nsone.records.Record): the record instance
answers (list): The answers to be added to the record
Yields:
twisted.internet.defer
"""
print 'Successfully loaded Record: {}'.format(response)
record = response
addRecordAnswersRes = self._addRecordAnswers(record, answers)
addRecordAnswersRes.addCallback(self._addRecordAnswersSuccess, answers)
addRecordAnswersRes.addErrback(self._addRecordAnswersFailure)
yield addRecordAnswersRes
def _loadRecordFailure(self, failure):
"""
Prints the failure message if a record fails to load
Args:
failure (twisted.python.failure): The failure object
"""
print failure.getErrorMessage()
@defer.inlineCallbacks
def _addRecordAnswers(self, record, answers):
"""
Calls the addAnswers method on the nsone.records.Record object
If the record answers already contain the answers passed
into this method, then nothing is done. Otherwise, the
addAnswers method is called and the answers are added to
the record
Returns a deferred object with the response from the
addAnswers method on the record object when it is available
or returns None if the record answers intersect with
what's passed in here.
Args:
record (nsone.records.Record): The nsone record object
answers (list): The record answers
Yields:
None
"""
recordData = yield record.data
recordAnswers = {answer['answer'][0] for answer in recordData['answers']}
if not recordAnswers.intersection(answers):
print 'Adding answer: {}'.format(answers)
yield record.addAnswers(answers)
else:
print 'Answer already exists: {}'.format(answers[0])
def _addRecordAnswersSuccess(self, response, answers):
"""
Prints a success message to show that the answers were added successfully.
Args:
response (None): the addAnswers method on the record returns None on success
answers (list): The record answers
"""
print 'Successfully processed answers: {}'.format(answers)
def _addRecordAnswersFailure(self, failure):
"""
Prints a failure message to show that the answers weren't added
Args:
failure (twisted.python.failure): the twisted failure object
"""
print failure.getErrorMessage()
def _startRequests(self, reactor):
"""
This method initializes either the zone data import or deletion.
All of the api requests are triggered here
Args:
reactor (twisted.internet.reactor)
Return:
function
"""
if self.deleteData:
return self._deleteZoneData()
return self._importZoneData()
def run(self):
"""
Schedules the startRequests method and gracefully exits the program when either
all of the deferred objects fire successfully or fail
"""
task.react(self._startRequests)