@itentialopensource/adapter-mockdevice 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/adapter.js ADDED
@@ -0,0 +1,506 @@
1
+ // Set globals
2
+ /* global database pronghornProps log eventSystem */
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { unzip } = require('zlib');
6
+ const EventEmitterCl = require('events').EventEmitter;
7
+ const util = require('util');
8
+
9
+ const encProps = pronghornProps.pathProps.encrypted;
10
+ const Ajv = require('ajv');
11
+
12
+ const ajv = new Ajv({ allErrors: true });
13
+ const mockDeviceSchema = JSON.parse(
14
+ fs.readFileSync(path.join(__dirname, './mockDeviceSchema.json'), 'utf-8')
15
+ );
16
+ const configMgrPath = path.join(__dirname, '../../@itential/app-configuration_manager');
17
+ const utilsPath = path.join(__dirname, '../../@itential/itential-utils');
18
+ let MOCK_DEVICES;
19
+ let CONFIG_PARSERS;
20
+ let getDriver;
21
+ let translateChangesToNativeConfig;
22
+ let Discovery;
23
+ let discovery;
24
+
25
+ if (process.env.NODE_ENV !== 'test') {
26
+ ({ Discovery } = require(`${utilsPath}`));
27
+ discovery = new Discovery();
28
+ console.log(discovery);
29
+ MOCK_DEVICES = database.collection('mock_devices');
30
+ CONFIG_PARSERS = database.collection('ucm_config_parsers');
31
+ ({ getDriver } = discovery.require(
32
+ `${configMgrPath}/shared/device-config/index.js`,
33
+ encProps
34
+ ));
35
+ ({ translateChangesToNativeConfig } = discovery.require(
36
+ `${configMgrPath}/config-tools/nativeConfigConverter.js`,
37
+ encProps
38
+ ));
39
+ }
40
+
41
+ const getDeviceParser = async (deviceType) => {
42
+ try {
43
+ const parserData = await CONFIG_PARSERS.findOne({ name: deviceType });
44
+ if (parserData) {
45
+ return parserData;
46
+ }
47
+ return null;
48
+ } catch (error) {
49
+ log.error(`Failed to get device parser. ${error.message || error}`);
50
+ return null;
51
+ }
52
+ };
53
+
54
+ // Converts lines of config into changesets
55
+ // You need changesets to rebuild a config
56
+ /**
57
+ * @param lineObj
58
+ * @param p
59
+ * @param changes
60
+ */
61
+ function lineToChangeSet(lineObj, p, changes) {
62
+ const line = lineObj.words.join(' ');
63
+ const parent = [...p];
64
+ if (lineObj.lines.length === 0) {
65
+ changes.push({
66
+ parents: p,
67
+ new: line,
68
+ old: null
69
+ });
70
+ } else {
71
+ parent.push(line);
72
+ lineObj.lines.forEach((element) => {
73
+ lineToChangeSet(element, parent, changes);
74
+ });
75
+ }
76
+ }
77
+
78
+ // Puts ! comments on new lines because they get removed
79
+ // This is sketchy at best
80
+ /**
81
+ * @param native
82
+ */
83
+ function bangOn(native) {
84
+ let indent = 0;
85
+ const newLines = [];
86
+ const lines = native.split('\n');
87
+ lines.forEach((line) => {
88
+ const spacecount = line.search(/\S|$/);
89
+ if (spacecount < indent) {
90
+ let bString = '';
91
+ for (let index = 0; index < spacecount; index += 1) {
92
+ bString += ' ';
93
+ }
94
+ bString += '!';
95
+ newLines.push(bString);
96
+ }
97
+ newLines.push(line);
98
+ indent = spacecount;
99
+ });
100
+ return newLines.join('\n');
101
+ }
102
+
103
+ // Finds misplaced end and moves it to the end
104
+ // This is mostly important for cisco-ios
105
+ /**
106
+ * @param native
107
+ */
108
+ function findEnd(native) {
109
+ const lines = native.split('\n');
110
+ const index = lines.indexOf('end');
111
+ if (index > -1) {
112
+ lines.splice(index, 1);
113
+ lines.push('end');
114
+ }
115
+ return lines.join('\n');
116
+ }
117
+
118
+ /**
119
+ * @param native
120
+ * @param line
121
+ */
122
+ function removeLine(native, line) {
123
+ const lines = native.split('\n');
124
+ const index = lines.indexOf(line);
125
+ if (index > -1) {
126
+ lines.splice(index, 1);
127
+ }
128
+ return lines.join('\n');
129
+ }
130
+
131
+ /**
132
+ * @param native
133
+ */
134
+ function negation(native) {
135
+ const regex = /no .*/g;
136
+ const lines = native.split('\n');
137
+ const negatives = lines.filter((line) => line.match(regex));
138
+
139
+ let updated = native;
140
+ negatives.forEach((n) => {
141
+ const pieces = n.split('no ');
142
+ let ltr = pieces[1];
143
+ if (pieces[0].length > 0) {
144
+ ltr = pieces[0].split('no ') + ltr;
145
+ }
146
+ updated = removeLine(updated, ltr);
147
+ updated = removeLine(updated, n);
148
+ });
149
+ return updated;
150
+ }
151
+
152
+ /**
153
+ * @param data
154
+ */
155
+ function decompressData(data) {
156
+ return new Promise((resolve, reject) => {
157
+ const buffer = Buffer.from(data, 'base64');
158
+ unzip(buffer, (error, bufferString) => {
159
+ if (error) reject(error);
160
+ resolve(bufferString.toString());
161
+ });
162
+ });
163
+ }
164
+
165
+ /**
166
+ * @param device
167
+ */
168
+ function validateSchema(device) {
169
+ const validate = ajv.compile(mockDeviceSchema);
170
+ const valid = validate(device);
171
+ return valid;
172
+ }
173
+
174
+ class MockDevice {
175
+ constructor(prongid, properties) {
176
+ log.debug(`Mock device properties ${JSON.stringify(properties)}`);
177
+ console.log(`config path: ${configMgrPath}`);
178
+ this.props = properties;
179
+ this.id = prongid;
180
+ }
181
+
182
+ connect() {
183
+ setTimeout(() => {
184
+ this.emit('ONLINE', { id: this.id });
185
+ const msg = {
186
+ id: this.id,
187
+ server: 'foo',
188
+ type: 'MockDevice',
189
+ time: Date.now().toString()
190
+ };
191
+ eventSystem.publish('adapterOnline', msg);
192
+ }, 1000);
193
+ }
194
+
195
+ healthCheck(callback) {
196
+ const status = {
197
+ id: this.id,
198
+ status: 'success'
199
+ };
200
+ callback(status);
201
+ }
202
+
203
+ hasEntities(entityType, deviceList, callback) {
204
+ (async () => {
205
+ if (entityType !== 'Device') {
206
+ return callback(
207
+ null,
208
+ `${this.id} does not support entity ${entityType}`
209
+ );
210
+ }
211
+ // TODO: $in deviceList
212
+ const devices = await MOCK_DEVICES.find({ deleted: false }).toArray();
213
+ const findings = deviceList.reduce((map, device) => {
214
+ map[device] = false;
215
+ devices.forEach((foundDevice) => {
216
+ if (foundDevice.name === device) {
217
+ map[device] = true;
218
+ }
219
+ });
220
+ return map;
221
+ }, {});
222
+ return callback(findings);
223
+ })();
224
+ }
225
+
226
+ getDevice(deviceId, callback) {
227
+ (async () => {
228
+ const device = await MOCK_DEVICES.findOne({
229
+ deleted: false,
230
+ name: deviceId
231
+ });
232
+ if (!device) {
233
+ return callback(null, 'Device not found');
234
+ }
235
+ const copy = JSON.parse(JSON.stringify(device));
236
+ delete copy.states;
237
+ delete copy.actions;
238
+ delete copy._id;
239
+ delete copy.deleted;
240
+ delete copy.online;
241
+ return callback(copy);
242
+ })();
243
+ }
244
+
245
+ getDevicesFiltered(searchOptions, callback) {
246
+ (async () => {
247
+ const result = await MOCK_DEVICES.find({
248
+ deleted: false,
249
+ name: { $regex: `.*${searchOptions.filter.name}.*` }
250
+ }).toArray();
251
+ const result2 = [];
252
+ // eslint-disable-next-line guard-for-in
253
+ for (const r in result) {
254
+ const validateDevice = result[r];
255
+ if (validateSchema(validateDevice)) {
256
+ delete result[r]._id;
257
+ delete result[r].deleted;
258
+ delete result[r].online;
259
+ result2.push(result[r]);
260
+ }
261
+ }
262
+ // eslint-disable-next-line prefer-object-spread
263
+ Object.assign({}, result2);
264
+ return callback({
265
+ total: result2.length,
266
+ list: result2
267
+ });
268
+ })();
269
+ }
270
+
271
+ addDevice(device, callback) {
272
+ return callback({
273
+ code: 0,
274
+ value: reply
275
+ });
276
+ }
277
+
278
+ deleteDevice(deviceId, callback) {
279
+ return callback({
280
+ code: 0,
281
+ value: reply
282
+ });
283
+ }
284
+
285
+ isAlive(deviceId, callback) {
286
+ (async () => {
287
+ const device = await MOCK_DEVICES.findOne({
288
+ deleted: false,
289
+ name: deviceId
290
+ });
291
+ if (!device) {
292
+ return callback(null, 'Device not found');
293
+ }
294
+ const newDev = {};
295
+ newDev.response = device.online;
296
+ return callback(device.online);
297
+ })();
298
+ }
299
+
300
+ getConfig(deviceId, format, callback) {
301
+ (async () => {
302
+ const result = await MOCK_DEVICES.findOne({
303
+ deleted: false,
304
+ name: deviceId
305
+ });
306
+ if (result) {
307
+ if (
308
+ result.config !== undefined
309
+ && Object.hasOwnProperty.call(result.config, 'type')
310
+ && result.config.type === 'Buffer'
311
+ ) {
312
+ const decompressConfig = await decompressData(result.config);
313
+
314
+ return callback({
315
+ code: 200,
316
+ config: decompressConfig,
317
+ value: '?'
318
+ });
319
+ }
320
+ return callback({
321
+ device: deviceId,
322
+ config: result.config
323
+ });
324
+ }
325
+ return callback({
326
+ code: 404,
327
+ config: '?',
328
+ value: '?'
329
+ });
330
+ })();
331
+ }
332
+
333
+ setConfig(deviceId, transactions, callback) {
334
+ (async () => {
335
+ console.log(
336
+ `Incoming Changeset:\n ${util.inspect(transactions, {
337
+ showHidden: false,
338
+ depth: null,
339
+ colors: true
340
+ })}`
341
+ );
342
+ console.log(`Remediating config for: ${deviceId}`);
343
+ const device = await MOCK_DEVICES.findOne({
344
+ deleted: false,
345
+ name: deviceId
346
+ });
347
+ console.log(`Getting parser for OS: ${device.ostype}`);
348
+ const deviceParser = await getDeviceParser(device.ostype);
349
+ if (!deviceParser) {
350
+ const e = `Unable to find a parser for ${deviceId} with type ${device.ostype}`;
351
+ return callback(null, new Error(e));
352
+ }
353
+ const driver = getDriver(device.ostype, deviceParser);
354
+ if (!driver) {
355
+ const e = `Unable to find a driver for type: ${device.ostype}`;
356
+ return callback(null, new Error(e));
357
+ }
358
+ const parsedConfig = driver.parse(device.config);
359
+ const changes = [];
360
+ parsedConfig.lines.forEach((element) => {
361
+ lineToChangeSet(element, [], changes);
362
+ });
363
+
364
+ translateChangesToNativeConfig(
365
+ changes.concat(transactions),
366
+ deviceParser,
367
+ async (native) => {
368
+ console.log(
369
+ `Remediation changeset successfully integrated into: ${deviceId}`
370
+ );
371
+ const newConfig = negation(bangOn(findEnd(native)));
372
+ device.config = newConfig;
373
+ const r = await MOCK_DEVICES.updateOne(
374
+ { name: deviceId },
375
+ { $set: device }
376
+ );
377
+ console.log(`Configuration saved for: ${deviceId}`);
378
+ return callback({
379
+ status: 200,
380
+ results: {
381
+ attemptedConfig: 'TODO'
382
+ }
383
+ });
384
+ }
385
+ );
386
+ })();
387
+ }
388
+
389
+ restoreConfig(deviceId, config, callback) {
390
+ (async () => {
391
+ const result = await MOCK_DEVICES.findOne({
392
+ deleted: false,
393
+ name: deviceId
394
+ });
395
+ if (!result) {
396
+ return callback(
397
+ null,
398
+ `Cannot call restoreConfig on ${deviceId} not found`
399
+ );
400
+ }
401
+ result.config = config;
402
+ const r = await MOCK_DEVICES.updateOne(
403
+ { name: deviceId },
404
+ { $set: { config } }
405
+ );
406
+ return callback('success');
407
+ })();
408
+ }
409
+
410
+ getDeviceGroups(callback) {
411
+ const groups = [];
412
+ groups.push({ name: 'mock', devices: [] });
413
+ return callback(groups);
414
+ }
415
+
416
+ /**
417
+ * @param {Array} devices Array of unique id strings
418
+ * @param {string} command The command to run against the devices
419
+ * @param {object} options (optional) Possible specific options
420
+ * @param {Function} callback The callback function
421
+ */
422
+ runCommand(devices, command, options = {}, callback) {
423
+ (async () => {
424
+ const cmd = command.split(' ')[0];
425
+ const results = [];
426
+ for (const deviceId of devices) {
427
+ const device = await MOCK_DEVICES.findOne({
428
+ deleted: false,
429
+ name: deviceId
430
+ });
431
+ if (device) {
432
+ let result = 'ok';
433
+ if (Object.hasOwnProperty.call(device.actions, cmd)) {
434
+ const r = await this.doAction(device.actions[cmd], device);
435
+ results.push({ device: device.name, result: r, status: 'success' });
436
+ } else {
437
+ if (
438
+ Object.hasOwnProperty.call(device.states, 'type')
439
+ && device.states.type === 'Buffer'
440
+ ) {
441
+ const decompressedState = await decompressData(device.states);
442
+ device.states = JSON.parse(decompressedState);
443
+ }
444
+ if (Object.hasOwnProperty.call(device.states[device.current_state], command)) {
445
+ result = device.states[device.current_state][command];
446
+ results.push({
447
+ device: device.name,
448
+ result,
449
+ status: 'success'
450
+ });
451
+ } else {
452
+ results.push({
453
+ device: device.name,
454
+ result,
455
+ status: 'error'
456
+ });
457
+ }
458
+ }
459
+ } else {
460
+ console.error(`${device.name} not found?`);
461
+ results.push({
462
+ device: device.name,
463
+ result: 'no such device',
464
+ status: 'error'
465
+ });
466
+ }
467
+ }
468
+ return callback(results);
469
+ })();
470
+ }
471
+
472
+ async doAction(cmd, device) {
473
+ switch (cmd.action) {
474
+ case 'next_state':
475
+ const max_state = device.states.length - 1;
476
+ if (device.current_state < max_state) {
477
+ device.current_state += 1;
478
+ } else {
479
+ device.current_state = 0;
480
+ }
481
+ break;
482
+ case 'reset_state':
483
+ device.current_state = 0;
484
+ break;
485
+ case 'turn_on':
486
+ device.online = true;
487
+ break;
488
+ case 'turn_off':
489
+ device.online = false;
490
+ break;
491
+ case 'overwrite_config':
492
+ device.config = device.states[device.current_state][cmd.value];
493
+ break;
494
+ default:
495
+ console.error(`unsupported action ${action}`);
496
+ }
497
+ const r = await MOCK_DEVICES.updateOne(
498
+ { name: device.name },
499
+ { $set: device }
500
+ );
501
+ return 'Device state has been updated!';
502
+ }
503
+ }
504
+
505
+ util.inherits(MockDevice, EventEmitterCl);
506
+ module.exports = MockDevice;
@@ -0,0 +1,58 @@
1
+ vlan 10
2
+ name HOST_10
3
+ !
4
+ vlan 4093
5
+ name BGP_PEER
6
+ trunk group BGP_PEER
7
+ !
8
+ interface Port-Channel1
9
+ switchport mode trunk
10
+ switchport trunk group BGP_PEER
11
+ switchport trunk group MLAG
12
+ !
13
+ interface Ethernet5
14
+ channel-group 1 mode active
15
+ !
16
+ interface Ethernet6
17
+ no switchport
18
+ ip address 192.168.255.1/31
19
+ !
20
+ interface Ethernet7
21
+ no switchport
22
+ ip address 192.168.255.17/31
23
+ !
24
+ interface Loopback0
25
+ ip address 192.168.254.3/32
26
+ !
27
+ interface Vlan10
28
+ ip address 192.168.10.2/24
29
+ ip virtual-router address 192.168.10.1
30
+ !
31
+ interface Vlan4093
32
+ ip address 1.1.1.2/31
33
+ !
34
+ ip routing
35
+ no ip hardware fib route unprogrammed parent-drop
36
+ !
37
+ route-map BGP_PREPEND permit 10
38
+ set as-path prepend 65101
39
+ !
40
+ router bgp 65000
41
+ router-id 192.168.254.3
42
+ update wait-install
43
+ bgp log-neighbor-changes
44
+ distance bgp 20 200 200
45
+ maximum-paths 32 ecmp 32
46
+ neighbor ARISTA peer-group
47
+ neighbor ARISTA remote-as 64600
48
+ neighbor ARISTA allowas-in 1
49
+ neighbor ARISTA fall-over bfd
50
+ neighbor ARISTA route-map BGP_PREPEND out
51
+ neighbor ARISTA password p4ssw0rd
52
+ neighbor 1.1.1.3 peer-group ARISTA
53
+ neighbor 1.1.1.3 remote-as 65000
54
+ neighbor 1.1.1.3 next-hop-self
55
+ neighbor 192.168.255.0 peer-group ARISTA
56
+ neighbor 192.168.255.16 peer-group ARISTA
57
+ network 192.168.10.0/24
58
+ network 192.168.254.3/32
@@ -0,0 +1,105 @@
1
+ :
2
+ ASA Version 9.0(1)
3
+ names
4
+ !
5
+ interface Ethernet0
6
+ nameif test
7
+ security-level 10
8
+ ip address 10.1.1.2 255.255.255.254
9
+ !
10
+ interface Ethernet1
11
+ nameif inside
12
+ security-level 100
13
+ ip address 10.1.1.3 255.255.254.0
14
+ !
15
+ interface Ethernet2
16
+ shutdown
17
+ no nameif
18
+ security-level 0
19
+ no ip address
20
+ !
21
+ interface Ethernet3
22
+ shutdown
23
+ no nameif
24
+ security-level 0
25
+ no ip address
26
+ !
27
+ interface Ethernet4
28
+ shutdown
29
+ no nameif
30
+ security-level 0
31
+ no ip address
32
+ !
33
+ interface Ethernet5
34
+ shutdown
35
+ no nameif
36
+ security-level 0
37
+ no ip address
38
+ !
39
+ enable password 8Ry2YjIyt7RRXU24 encrypted
40
+ passwd 2KFQnbNIdI.2KYOU encrypted
41
+ hostname example1
42
+ domain-name example.com
43
+ boot system flash:/cdisk.bin
44
+ ftp mode passive
45
+ pager lines 24
46
+ mtu test 1500
47
+ mtu inside 1500
48
+ monitor-interface test
49
+ monitor-interface inside
50
+ ASDM image flash:ASDM
51
+ no ASDM history enable
52
+ arp timeout 14400
53
+ route inside 0.0.0.0 0.0.0.0 10.1.1.2
54
+ timeout xlate 3:00:00
55
+ timeout conn 2:00:00 half-closed 1:00:00 udp 0:02:00 icmp 1:00:00 rpc 1:00:00 h3
56
+ 23 0:05:00 h225 1:00:00 mgcp 0:05:00 mgcp-pat 0:05:00 sip 0:30:00 sip_media 0:02
57
+ :00
58
+ timeout uauth 0:00:00 absolute
59
+ http server enable
60
+ http 0.0.0.0 0.0.0.0 inside
61
+ no snmp-server location
62
+ no snmp-server contact
63
+ snmp-server enable traps snmp
64
+ fragment size 200 test
65
+ fragment chain 24 test
66
+ fragment timeout 5 test
67
+ fragment size 200 inside
68
+ fragment chain 24 inside
69
+ fragment timeout 5 inside
70
+ telnet 0.0.0.0 0.0.0.0 inside
71
+ telnet timeout 1440
72
+ ssh timeout 5
73
+ console timeout 0
74
+ group-policy todd internal
75
+ !
76
+ class-map inspection_default
77
+ match default-inspection-traffic
78
+ !
79
+ !
80
+ policy-map abc_global_fw_policy
81
+ class inspection_default
82
+ inspect dns
83
+ inspect ftp
84
+ inspect h323 h225
85
+ inspect h323 ras
86
+ inspect http
87
+ inspect ils
88
+ inspect mgcp
89
+ inspect netbios
90
+ inspect rpc
91
+ inspect rsh
92
+ inspect rtsp
93
+ inspect sip
94
+ inspect skinny
95
+ inspect sqlnet
96
+ inspect tftp
97
+ inspect xdmcp
98
+ inspect ctiqbe
99
+ inspect cuseeme
100
+ inspect icmp
101
+ !
102
+ terminal width 80
103
+ service-policy abc_global_fw_policy global
104
+ Cryptochecksum:bfecf4b9d1b98b7e8d97434851f57e14
105
+ : end