UNPKG

59.3 kBJavaScriptView Raw
1/**
2 * Copyright 2016-2018 F5 Networks, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17'use strict';
18
19const assert = require('assert');
20const fs = require('fs');
21const q = require('q');
22const IControl = require('./iControl');
23const util = require('./util');
24const authn = require('./authn');
25const localKeyUtil = require('./localKeyUtil');
26const cryptoUtil = require('./cryptoUtil');
27const Logger = require('./logger');
28const BigIpCluster = require('./bigIpCluster');
29const BigIpGtm = require('./bigIpGtm');
30const BigIpOnboard = require('./bigIpOnboard');
31const ActiveError = require('./activeError');
32
33const bigIqOnboardMixins = require('./bigIqOnboardMixins');
34const bigIqClusterMixins = require('./bigIqClusterMixins');
35
36const KEYS = require('./sharedConstants').KEYS;
37const REG_EXPS = require('./sharedConstants').REG_EXPS;
38const BACKUP = require('../lib/sharedConstants').BACKUP;
39
40const UCS_TASK_PATH = '/tm/task/sys/ucs';
41
42/**
43 * BigIp constructor
44 *
45 * @class
46 * @classdesc
47 * Provides core functionality (CRUD operations, ready, etc) and maintains
48 * references to other modules in f5-cloud-libs.
49 *
50 * After createing a BigIp with this constructor, you must call the
51 * async init() method.
52 *
53 * @param {Object} [options] - Optional parameters.
54 * @param {Object} [options.logger] - Logger to use. Or, pass loggerOptions to
55 * get your own logger.
56 * @param {Object} [options.loggerOptions] - Options for the logger.
57 * See {@link module:logger.getLogger} for details.
58*/
59function BigIp(options) {
60 const logger = options ? options.logger : undefined;
61 let loggerOptions = options ? options.loggerOptions : undefined;
62 let dependentOptions = {};
63
64 if (logger) {
65 this.logger = logger;
66 util.setLogger(logger);
67 cryptoUtil.setLogger(logger);
68 localKeyUtil.setLogger(logger);
69 authn.setLogger(logger);
70 dependentOptions = { logger: this.logger };
71 } else {
72 loggerOptions = loggerOptions || { logLevel: 'none' };
73 loggerOptions.module = module;
74 this.logger = Logger.getLogger(loggerOptions);
75 util.setLoggerOptions(loggerOptions);
76 cryptoUtil.setLoggerOptions(loggerOptions);
77 localKeyUtil.setLoggerOptions(loggerOptions);
78 authn.setLoggerOptions(loggerOptions);
79 dependentOptions = { loggerOptions };
80 }
81
82 // We're not ready until we have all the info we need (password from URL, for example)
83 // Must call init() to set this
84 this.isInitialized = false;
85
86 this.cluster = new BigIpCluster(this, dependentOptions);
87 this.gtm = new BigIpGtm(this, dependentOptions);
88 this.onboard = new BigIpOnboard(this, dependentOptions);
89}
90
91/**
92 * Initialize this instance w/ host user password
93 *
94 * @param {String} host - Host to connect to.
95 * @param {String} user - User (with admin rights).
96 * @param {String} password - Password for user or URI (file, http, https, ARN, etc.)
97 * to location containing password.
98 * @param {Object} [options] - Optional parameters.
99 * @param {Number} [options.port] - Port to connect to. Default 443.
100 * @param {Boolean} [options.passwordIsUrl] - Indicates that password is a URL for the password
101 * @param {Boolean} [options.passwordIsToken] - Indicates that the password is an auth token.
102 * @param {Boolean} [options.passwordEncrypted] - Indicates that the password is encrypted (with
103 * the local cloud public key)
104 * @param {String} [options.product] - The product we are running on (BIG-IP | BIG-IQ). Default
105 * is to determine the product programmatically.
106 * @param {Object} [options.clOptions] - Command line options
107 *
108 * @returns {Promise} A promise which is resolved when initialization is complete
109 * or rejected if an error occurs.
110 */
111BigIp.prototype.init = function init(host, user, password, options) {
112 this.initOptions = {};
113 Object.assign(this.initOptions, options);
114
115 this.initPassword = password;
116 this.host = host.trim();
117 this.user = user.trim();
118 this.port = this.initOptions.port || 443;
119
120 const authnOptions = {
121 port: this.port,
122 passwordIsUri: this.initOptions.passwordIsUrl,
123 passwordIsToken: this.initOptions.passwordIsToken,
124 passwordEncrypted: this.initOptions.passwordEncrypted,
125 clOptions: this.initOptions.clOptions
126 };
127
128 // Are we a BIG-IP or BIG-IQ?
129 let productPromise;
130 if (this.initOptions.product || this.product) {
131 productPromise = q.resolve(this.initOptions.product || this.product);
132 } else {
133 productPromise = util.getProduct();
134 }
135
136 return productPromise
137 .then((response) => {
138 this.product = response;
139 this.logger.info('This is a', this.product);
140 if (this.isBigIq()) {
141 Object.assign(BigIpOnboard.prototype, bigIqOnboardMixins);
142 Object.assign(BigIpCluster.prototype, bigIqClusterMixins);
143 }
144 authnOptions.product = this.product;
145 return authn.authenticate(this.host, this.user, password, authnOptions);
146 })
147 .then((icontrol) => {
148 this.icontrol = icontrol;
149 this.password = this.icontrol.password;
150 this.isInitialized = true;
151
152 this.logger.info('Waiting for device to be ready.');
153 return this.ready();
154 })
155 .catch((err) => {
156 this.logger.info('Device initialization failed', err && err.message ? err.message : err);
157 return q.reject(err);
158 });
159};
160
161/**
162 * Low-level interface
163 */
164
165/**
166 * Submits a list (GET) request
167 *
168 * @param {String} path - The path to get.
169 * @param {Object} [iControlOptions] - Options for IControl.
170 * @param {Object} [retryOptions] - Options for retrying the request.
171 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
172 * 0 to not retry. Default 60.
173 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
174 * @param {Object} [options] - Options for this method.
175 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
176 * may contain sensitive information).
177 *
178 * @returns {Promise} A promise which is resolved when the request is complete
179 * or rejected if an error occurs.
180 */
181BigIp.prototype.list = function list(path, iControlOptions, retryOptions, options) {
182 const retry = retryOptions || util.DEFAULT_RETRY;
183 const methodOptions = {};
184 Object.assign(methodOptions, options);
185
186 const func = function () {
187 if (!methodOptions.silent) {
188 this.logger.debug('list', this.host, path);
189 }
190
191 return isInitialized(this)
192 .then(() => {
193 return this.icontrol.list(path, iControlOptions);
194 })
195 .then((response) => {
196 if (!methodOptions.silent) {
197 this.logger.debug(response);
198 }
199 return response;
200 });
201 };
202
203 return util.tryUntil(this, retry, func);
204};
205
206/**
207 * Submits a create (POST) request
208 *
209 * @param {String} path - The path to post.
210 * @param {Object} body - The body for the POST request.
211 * @param {Object} [iControlOptions] - Options for IControl.
212 * @param {Object} [retryOptions] - Options for retrying the request.
213 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
214 * 0 to not retry. Default 60.
215 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
216 * @param {Object} [options] - Options for this method.
217 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
218 * may contain sensitive information).
219 *
220 * @returns {Promise} A promise which is resolved when the request is complete
221 * or rejected if an error occurs.
222 */
223BigIp.prototype.create = function create(path, body, iControlOptions, retryOptions, options) {
224 const retry = retryOptions || util.DEFAULT_RETRY;
225 const methodOptions = {};
226 Object.assign(methodOptions, options);
227
228 const func = function () {
229 if (!methodOptions.silent) {
230 this.logger.debug('create', this.host, path, body);
231 }
232
233 return isInitialized(this)
234 .then(() => {
235 return this.icontrol.create(path, body, iControlOptions);
236 })
237 .then((response) => {
238 if (!methodOptions.silent) {
239 this.logger.debug(response);
240 }
241 return response;
242 });
243 };
244
245 return util.tryUntil(this, retry, func);
246};
247
248/**
249 * Submits a modify (PATCH) request
250 *
251 * @param {String} path - The path to patch.
252 * @param {Object} body - The body for the patch request.
253 * @param {Object} [iControlOptions] - Options for IControl.
254 * @param {Object} [retryOptions] - Options for retrying the request.
255 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
256 * 0 to not retry. Default 60.
257 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
258 * @param {Object} [options] - Options for this method.
259 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
260 * may contain sensitive information).
261 *
262 * @returns {Promise} A promise which is resolved when the request is complete
263 * or rejected if an error occurs.
264 */
265BigIp.prototype.modify = function modify(path, body, iControlOptions, retryOptions, options) {
266 const retry = retryOptions || util.DEFAULT_RETRY;
267 const methodOptions = {};
268 Object.assign(methodOptions, options);
269
270 const func = function () {
271 if (!methodOptions.silent) {
272 this.logger.debug('modify', this.host, path, body);
273 }
274
275 return isInitialized(this)
276 .then(() => {
277 return this.icontrol.modify(path, body, iControlOptions);
278 })
279 .then((response) => {
280 if (!methodOptions.silent) {
281 this.logger.debug(response);
282 }
283 return response;
284 });
285 };
286
287 return util.tryUntil(this, retry, func);
288};
289
290/**
291 * Submits a replace (PUT) request
292 *
293 * @param {String} path - The path to put.
294 * @param {Object} body - The body for the patch request.
295 * @param {Object} [iControlOptions] - Options for IControl.
296 * @param {Object} [retryOptions] - Options for retrying the request.
297 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
298 * 0 to not retry. Default 60.
299 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
300 * @param {Object} [options] - Options for this method.
301 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
302 * may contain sensitive information).
303 *
304 * @returns {Promise} A promise which is resolved when the request is complete
305 * or rejected if an error occurs.
306 */
307BigIp.prototype.replace = function replace(path, body, iControlOptions, retryOptions, options) {
308 const retry = retryOptions || util.DEFAULT_RETRY;
309 const methodOptions = {};
310 Object.assign(methodOptions, options);
311
312 const func = function () {
313 if (!methodOptions.silent) {
314 this.logger.debug('replace', this.host, path, body);
315 }
316
317 return isInitialized(this)
318 .then(() => {
319 return this.icontrol.replace(path, body, iControlOptions);
320 })
321 .then((response) => {
322 if (!methodOptions.silent) {
323 this.logger.debug(response);
324 }
325 return response;
326 });
327 };
328
329 return util.tryUntil(this, retry, func);
330};
331
332/**
333 * Submits a delete (DELETE) request
334 *
335 * @param {String} path - The path to delete.
336 * @param {Object} body - The body for the delete request.
337 * @param {Object} [iControlOptions] - Options for IControl.
338 * @param {Object} [retryOptions] - Options for retrying the request.
339 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
340 * 0 to not retry. Default 60.
341 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
342 * @param {Object} [options] - Options for this method.
343 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
344 * may contain sensitive information).
345 *
346 * @returns {Promise} A promise which is resolved when the request is complete
347 * or rejected if an error occurs.
348 */
349BigIp.prototype.delete = function deletez(path, body, iControlOptions, retryOptions, options) {
350 const retry = retryOptions || util.DEFAULT_RETRY;
351 const methodOptions = {};
352 Object.assign(methodOptions, options);
353
354 const func = function () {
355 if (!methodOptions.silent) {
356 this.logger.debug('delete', this.host, path, body);
357 }
358
359 return isInitialized(this)
360 .then(() => {
361 return this.icontrol.delete(path, body, iControlOptions);
362 })
363 .then((response) => {
364 if (!methodOptions.silent) {
365 this.logger.debug(response);
366 }
367 return response;
368 });
369 };
370
371 return util.tryUntil(this, retry, func);
372};
373
374/**
375 * Creates or modifies an object
376 *
377 * @param {String} path - The path to patch.
378 * @param {Object} body - The body for the patch request.
379 * @param {String} body.name - The name used to determine if the object exists.
380 * @param {Object} [iControlOptions] - Options for IControl.
381 * @param {Object} [retryOptions] - Options for retrying the request.
382 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
383 * 0 to not retry. Default 60.
384 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
385 * @param {Object} [options] - Options for this method.
386 * @param {Boolen} [options.silent] - Do not log any info (for requests/repsonses that
387 * may contain sensitive information).
388 *
389 * @returns {Promise} A promise which is resolved when the request is complete
390 * or rejected if an error occurs.
391 */
392BigIp.prototype.createOrModify = function createOrModify(path, body, iControlOptions, retryOptions, options) {
393 const retry = retryOptions || util.DEFAULT_RETRY;
394 const methodOptions = {};
395 Object.assign(methodOptions, options);
396 let finalPath = path;
397 let partitionPath;
398
399 // user objects handle partitions in their own way
400 if (path === '/tm/auth/user' || path === '/tm/net/trunk') {
401 partitionPath = '';
402 } else if (body.partition) {
403 partitionPath = `~${body.partition}~`;
404 } else {
405 partitionPath = '~Common~';
406 }
407
408 assert.equal(typeof body.name, 'string', 'body.name is required');
409
410 const func = function () {
411 return isInitialized(this)
412 .then(() => {
413 const deferred = q.defer();
414
415 this.icontrol.list(`${path}/${partitionPath}${body.name}`)
416 .then(() => {
417 finalPath = `${path}/${partitionPath}${body.name}`;
418 if (!methodOptions.silent) {
419 this.logger.silly(`${finalPath} exists, modifying`);
420 this.logger.debug('modify', this.host, finalPath, body);
421 }
422 deferred.resolve('modify');
423 })
424 .catch((err) => {
425 if (err.code === 404) {
426 if (!methodOptions.silent) {
427 this.logger.silly(
428 `${path}/${partitionPath}${body.name} does not exist, creating`
429 );
430 this.logger.debug('create', this.host, finalPath, body);
431 }
432 deferred.resolve('create');
433 } else {
434 deferred.reject(err);
435 }
436 });
437 return deferred.promise;
438 })
439 .then((method) => {
440 return this.icontrol[method](finalPath, body, iControlOptions);
441 })
442 .then((response) => {
443 if (!methodOptions.silent) {
444 this.logger.debug(response);
445 }
446 return response;
447 });
448 };
449
450 return util.tryUntil(this, retry, func);
451};
452
453/**
454 * Higher level interface
455 */
456
457/**
458 * Determines if the device status is either active or standby
459 *
460 * @param {Object} [retryOptions] - Options for retrying the request.
461 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
462 * 0 to not retry. Default 60.
463 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
464 *
465 * @returns {Promise} A promise which is resolved when the status is either active or standby.
466 */
467BigIp.prototype.active = function active(retryOptions) {
468 const retry = {};
469 Object.assign(retry, retryOptions || util.DEFAULT_RETRY);
470
471 // While waiting for active, we may get errors but we want to keep trying
472 retry.continueOnError = true;
473
474 const func = function () {
475 const deferred = q.defer();
476
477 this.ready()
478 .then(() => {
479 return this.list('/tm/cm/failover-status', undefined, util.NO_RETRY);
480 })
481 .then((response) => {
482 const state = response.entries['https://localhost/mgmt/tm/cm/failover-status/0']
483 .nestedStats.entries.status.description;
484 this.logger.debug('Current state:', state);
485 if (state === 'ACTIVE' || state === 'STANDBY') {
486 deferred.resolve();
487 } else {
488 deferred.reject(new ActiveError('Device not active.'));
489 }
490 })
491 .catch((err) => {
492 deferred.reject(new ActiveError(err ? err.message : ''));
493 })
494 .done();
495
496 return deferred.promise;
497 };
498
499 return util.tryUntil(this, retry, func);
500};
501
502/**
503 * Creates a folder if it does not exists
504 *
505 * @param {String} folder - Name of folder
506 * @param {Object} [options] - Optional parameters
507 * @param {String} [options.subPath] - The folder subPath. Use '/' for top level folders.
508 * Default '/Common'
509 * @param {String} [options.deviceGroup] - Device group for folder. Default 'none'
510 * @param {String} [options.trafficGroup] - Traffic group for folder. Default 'none'
511 *
512 * @returns {Promise} A promise which is resolved when the request is complete
513 * or rejected if an error occurs.
514 */
515BigIp.prototype.createFolder = function createFolder(folder, options) {
516 const subPath = options ? options.subPath || '/Common' : '/Common';
517 const deviceGroup = options ? options.deviceGroup || 'none' : 'none';
518 const trafficGroup = options ? options.trafficGroup || 'none' : 'none';
519
520 return this.ready()
521 .then(() => {
522 return this.list('/tm/sys/folder');
523 })
524 .then((folders) => {
525 const fullPath = subPath + (subPath.endsWith('/') ? '' : '/') + folder;
526 const folderExists = function (element) {
527 return element.fullPath === fullPath;
528 };
529
530 if (folders.find(folderExists)) {
531 return q();
532 }
533
534 const body = {
535 subPath,
536 name: folder,
537 deviceGroup: deviceGroup || 'none',
538 trafficGroup: trafficGroup || 'none'
539 };
540
541 return this.create('/tm/sys/folder', body);
542 });
543};
544
545/**
546 * Gets the => device info
547 *
548 * @param {Object} [retryOptions] - Options for retrying the request.
549 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
550 * 0 to not retry. Default 60.
551 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
552 *
553 * @returns {Promise} A promise which is resolved when the request is complete
554 * or rejected if an error occurs.
555 */
556BigIp.prototype.deviceInfo = function deviceInfo(retryOptions) {
557 const retry = retryOptions || util.DEFAULT_RETRY;
558
559 const func = function () {
560 return this.list('/shared/identified-devices/config/device-info', undefined, util.NO_RETRY);
561 };
562
563 return util.tryUntil(this, retry, func);
564};
565
566/**
567 * Gets the Device State for a BIG-IP device by hostname
568 *
569 * @param {String} hostname - Hostname of device
570 * @param {Object} [retryOptions] - Options for retrying the request.
571 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
572 * 0 to not retry. Default 60.
573 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
574 *
575 * @returns {Promise} A promise which is resolved when the request is complete
576 * or rejected if an error occurs.
577 */
578BigIp.prototype.deviceState = function deviceState(hostname, retryOptions) {
579 const retry = retryOptions || util.DEFAULT_RETRY;
580
581 const func = function () {
582 return this.list(`/tm/cm/device/~Common~${hostname}`);
583 };
584
585 return util.tryUntil(this, retry, func);
586};
587
588/**
589 * Gets the path to the latest private key
590 *
591 * @param {String} folder - Folder in which to search for the key
592 * @param {String} name - Name of the key
593 *
594 * @returns {Promise} A promise which is resolved when the request is complete
595 * or rejected if an error occurs.
596 */
597BigIp.prototype.getPrivateKeyFilePath = function getPrivateKeyFilePath(folder, name) {
598 assert.equal(typeof folder, 'string', 'folder must be a string');
599 assert.equal(typeof name, 'string', 'name must be a string');
600
601 const PRIVATE_KEY_DIR = `/config/filestore/files_d/${folder}_d/certificate_key_d/`;
602
603 return this.ready()
604 .then(() => {
605 // List in descending time order, our key will be the first that matches
606 // the name
607 const commandBody = {
608 command: 'run',
609 utilCmdArgs: `-c "ls -1t ${PRIVATE_KEY_DIR}"`
610 };
611 return this.create('/tm/util/bash', commandBody, undefined, util.NO_RETRY);
612 })
613 .then((response) => {
614 const KEY_FILE_PREFIX = `:${folder}:${name.replace(REG_EXPS.KEY_SUFFIX, '')}`;
615 const files = response.commandResult.split('\n');
616 const ourKey = files.find((element) => {
617 return element.startsWith(KEY_FILE_PREFIX);
618 });
619 if (!ourKey) {
620 return q();
621 }
622 return PRIVATE_KEY_DIR + ourKey;
623 });
624};
625
626/**
627 * Installs a private key and then deletes the original private key file.
628 *
629 * @param {String} privateKeyFile - Full path to private key file. This file
630 * must be on the BIG-IP disk and will be deleted
631 * upon successful installation to MCP
632 * @param {String} folder - Folder in which to put key
633 * @param {String} name - Name for key
634 * @param {Object} [options] - Optional parameters
635 * @param {String} [options.passphrase] - Optional passphrase for key
636 *
637 * @returns {Promise} A promise which is resolved when the request is complete
638 * or rejected if an error occurs.
639 */
640BigIp.prototype.installPrivateKey = function installPrivateKey(privateKeyFile, folder, name, options) {
641 const CRYPTO_PATH = '/tm/sys/crypto/key';
642
643 const deferred = q.defer();
644
645 assert.equal(typeof privateKeyFile, 'string', 'privateKeyFile must be a string');
646 assert.equal(typeof folder, 'string', 'folder must be a string');
647 assert.equal(typeof name, 'string', 'name must be a string');
648
649 const passphrase = options ? options.passphrase : undefined;
650
651 const installBody = {
652 command: 'install',
653 name: `/${folder}/${name}`,
654 fromLocalFile: privateKeyFile
655 };
656
657 if (passphrase) {
658 installBody.passphrase = passphrase;
659 }
660
661 const checkForKey = function checkForKey() {
662 return this.list(`${CRYPTO_PATH}/~${folder}~${name}`);
663 };
664
665 this.ready()
666 .then(() => {
667 return this.createFolder(folder, { subPath: '/' });
668 })
669 .then(() => {
670 return this.create(CRYPTO_PATH, installBody, undefined, util.NO_RETRY);
671 })
672 .then(() => {
673 // wait for the key to be installed
674 return util.tryUntil(this, util.MEDIUM_RETRY, checkForKey);
675 })
676 .then(() => {
677 fs.unlink(privateKeyFile, (err) => {
678 if (err) {
679 this.logger.debug('Failed to delete private key:', err);
680 }
681
682 deferred.resolve();
683 });
684 });
685
686 return deferred.promise;
687};
688
689/**
690 * Get the metadata for the cloud libs private key
691 *
692 * @returns {Promise} A promise which is resolved when the request is complete
693 * or rejected if an error occurs.
694 */
695BigIp.prototype.getPrivateKeyMetadata = function getPrivateKeyMetadata(folder, name) {
696 return this.ready()
697 .then(() => {
698 return this.list('/tm/sys/file/ssl-key');
699 })
700 .then((response) => {
701 const keyPath = `/${folder}/${name}`;
702 const keyPathNoSuffix = keyPath.replace(REG_EXPS.KEY_SUFFIX, '');
703 const sslKeys = {};
704
705 response.forEach((key) => {
706 sslKeys[key.fullPath] = key;
707 });
708 // Look for specified key name, then the key name without the .key suffix
709 if (sslKeys[keyPath]) {
710 return q(sslKeys[keyPath]);
711 } else if (sslKeys[keyPathNoSuffix]) {
712 return q(sslKeys[keyPathNoSuffix]);
713 }
714 return q();
715 });
716};
717
718/**
719 * Returns this intance's password
720 *
721 * @returns {Promise} A promise that is resolved with this instances password
722 * or rejected if an error occurs
723 */
724BigIp.prototype.getPassword = function getPassword() {
725 return q(this.password);
726};
727
728/**
729 * Returns whether or not the device is a BIG-IP
730 *
731 * @returns {Boolean} Whether or not this device is a BIG-IP
732 */
733BigIp.prototype.isBigIp = function isBigIp() {
734 return this.product === 'BIG-IP';
735};
736
737/**
738 * Returns whether or not the device is a BIG-IQ
739 *
740 * @returns {Boolean} Whether or not this device is a BIG-IQ
741 */
742BigIp.prototype.isBigIq = function isBigIq() {
743 return this.product === 'BIG-IQ';
744};
745
746/**
747 * Loads sys config
748 *
749 * @param {String} [file] - Full path on device of file to load. Default is
750 * to load the default config.
751 * @param {Object} [options] - Object map of load options
752 * (for example, {merge: true})
753 * @param {Object} [retryOptions] - Options for retrying the request.
754 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
755 * 0 to not retry. Default 60.
756 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
757 *
758 * @returns {Promise} A promise which is resolved when the config has been
759 * loaded or rejected if an error occurs.
760 */
761BigIp.prototype.loadConfig = function loadConfig(file, options, retryOptions) {
762 const retry = retryOptions || util.DEFAULT_RETRY;
763
764 const func = function () {
765 return this.ready()
766 .then(() => {
767 const commandBody = {
768 command: 'load',
769 options: []
770 };
771 let optionBody;
772 if (file) {
773 commandBody.options.push({ file });
774 } else {
775 commandBody.name = 'default';
776 }
777 if (options) {
778 Object.keys(options).forEach((option) => {
779 optionBody = {};
780 optionBody[option] = options[option];
781 commandBody.options.push(optionBody);
782 });
783 }
784 return this.create('/tm/sys/config', commandBody, undefined, util.NO_RETRY);
785 });
786 };
787
788 return util.tryUntil(this, retry, func);
789};
790
791
792/**
793 * Loads sys UCS
794 *
795 * @param {String} file - Full path on device of file to load.
796 * @param {Object} [loadOptions] - Options for the load ucs task
797 * (for example, {noLicense: true, resetTrust: true})
798 * @param {Object} [options] - Options for this command (not the load task itself)
799 * @param {Boolean} [options.initLocalKeys] - Re-create and install local public/private key pair
800 * used for password encryption
801 * @param {Boolean} [options.restoreUser] - Restore the current user after loading
802 * @param {Object} [retryOptions] - Options for retrying the request.
803 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
804 * 0 to not retry. Default 60.
805 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
806 *
807 * @returns {Promise} A promise which is resolved when the config has been
808 * loaded or rejected if an error occurs.
809 */
810BigIp.prototype.loadUcs = function loadUcs(file, loadOptions, options) {
811 const initLocalKeys = options ? options.initLocalKeys : undefined;
812 const restoreUser = options ? options.restoreUser : undefined;
813 const ucsLoadOptions = loadOptions || {};
814
815 const restorePlainTextPasswordFromUrl = function restorePlainTextPasswordFromUrl() {
816 const deferred = q.defer();
817
818 util.getDataFromUrl(this.initPassword)
819 .then((password) => {
820 this.password = password.trim();
821 this.icontrol = new IControl({
822 host: this.host,
823 port: this.port,
824 user: this.user,
825 password: this.password,
826 basePath: '/mgmt',
827 strict: false
828 });
829 this.isInitialized = true;
830 return this.ready();
831 })
832 .then(() => {
833 deferred.resolve();
834 })
835 .catch((err) => {
836 return deferred.reject(err);
837 });
838
839 return deferred.promise;
840 }.bind(this);
841
842 const restoreEncryptedPassword = function restoreEncryptedPassword() {
843 const deferred = q.defer();
844
845 cryptoUtil.encrypt(KEYS.LOCAL_PUBLIC_KEY_PATH, this.password)
846 .then((encryptedPassword) => {
847 return util.writeDataToUrl(encryptedPassword, this.initPassword);
848 })
849 .then(() => {
850 deferred.resolve();
851 })
852 .catch((err) => {
853 this.logger.info('error restoring user', err);
854 deferred.reject(err);
855 });
856
857 return deferred.promise;
858 }.bind(this);
859
860 this.logger.silly('loadUcs: calling ready before runLoadUcs');
861 return this.ready()
862 .then(() => {
863 this.logger.silly('loadUcs: calling runLoadUcs');
864 return runLoadUcs.call(this, file, ucsLoadOptions);
865 })
866 .then(() => {
867 this.logger.silly('loadUcs: runLoadUcs success');
868 if (initLocalKeys) {
869 this.logger.silly('Generating local key pair');
870 return localKeyUtil.generateAndInstallKeyPair(
871 KEYS.LOCAL_PUBLIC_KEY_DIR,
872 KEYS.LOCAL_PUBLIC_KEY_PATH,
873 KEYS.LOCAL_PRIVATE_KEY_FOLDER,
874 KEYS.LOCAL_PRIVATE_KEY,
875 {
876 force: true
877 }
878 );
879 }
880 return q();
881 })
882 .then(() => {
883 let promise;
884
885 if (this.initOptions.passwordIsUrl && !this.initOptions.passwordEncrypted) {
886 // Our password may have changed due to the UCS load. If we
887 // were given a password-url, we can get the new password
888 this.logger.silly('restoring plain text password file');
889 promise = restorePlainTextPasswordFromUrl();
890 } else if (this.initOptions.passwordIsUrl &&
891 this.initOptions.passwordEncrypted &&
892 options.initLocalKeys) {
893 // Otherwise, we can restore the old password (which we were called with) via tmsh
894 this.logger.silly('restoring encrypted password');
895 promise = restoreEncryptedPassword();
896 }
897 return promise;
898 })
899 .then(() => {
900 if (restoreUser && !this.initOptions.passwordIsToken) {
901 return util.runTmshCommand(`modify auth user ${this.user} password ${this.password}`);
902 } else if (restoreUser && this.initOptions.passwordIsToken) {
903 this.logger.info('Unable to restore user - token was used for initial authorization');
904 return q();
905 }
906 return q();
907 })
908 .catch((err) => {
909 this.logger.info('loadUcs failed', err);
910 return q.reject(err);
911 });
912};
913
914/**
915 * Pings a given address once
916 *
917 * @param {String} address - IP address or hostname to ping.
918 * @param {Object} [retryOptions] - Options for retrying the request.
919 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
920 * 0 to not retry. Default 60.
921 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
922 *
923 * @returns {Promise} A promise which is resolved if the ping succeeds
924 * or rejected if an error occurs.
925 */
926BigIp.prototype.ping = function ping(address, retryOptions) {
927 const retry = retryOptions || util.DEFAULT_RETRY;
928
929 if (!address) {
930 return q.reject(new Error('Address is required for ping.'));
931 }
932
933 const func = function () {
934 return this.ready()
935 .then(() => {
936 const pingCommand = {
937 command: 'run',
938 utilCmdArgs: `${address} -c 1`
939 };
940 return this.create('/tm/util/ping', pingCommand, undefined, util.NO_RETRY);
941 })
942 .then((response) => {
943 if (!response) {
944 this.logger.debug('No response from ping');
945 return q.reject();
946 }
947
948 const receivedRegex = new RegExp(/transmitted, (\d+) received/);
949 const receivedCheck = receivedRegex.exec(response.commandResult);
950 let packetsReceived;
951
952 if (receivedCheck && receivedCheck.length > 0) {
953 packetsReceived = receivedCheck[1];
954 this.logger.verbose('Ping received', packetsReceived, 'packet(s).');
955 if (packetsReceived > 0) {
956 return true;
957 }
958 return q.reject();
959 }
960 return q.reject();
961 });
962 };
963
964 return util.tryUntil(this, retry, func);
965};
966
967/**
968 * Resolves when device is ready.
969 *
970 * Device is determined to be ready when the nodejs echo-js worker
971 * is ready.
972 *
973 * @param {Object} [retryOptions] - Options for retrying the request.
974 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
975 * 0 to not retry. Default 60.
976 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
977 *
978 * @returns {Promise} A Promise which is resolved when device is ready
979 * or rejected after trying a fixed number of times.
980 */
981BigIp.prototype.ready = function ready(retryOptions) {
982 const retry = retryOptions || util.DEFAULT_RETRY_IGNORE_ERRORS;
983
984 const func = function () {
985 const promises = [];
986
987 const availabilityChecks = [
988 '/shared/echo/available',
989 '/shared/identified-devices/config/device-info/available',
990 '/tm/sys/available',
991 '/tm/cm/available'
992 ];
993
994 if (this.isBigIp()) {
995 availabilityChecks.push('/shared/iapp/package-management-tasks/available');
996 }
997
998 const mcpCheck = function () {
999 const deferred = q.defer();
1000
1001 this.list('/tm/sys/mcp-state', undefined, util.NO_RETRY)
1002 .then((response) => {
1003 const entries = response.entries;
1004 let allRunning = true;
1005 Object.keys(entries).forEach((entry) => {
1006 if (entries[entry].nestedStats.entries.phase.description !== 'running') {
1007 allRunning = false;
1008 }
1009 });
1010
1011 if (allRunning) {
1012 deferred.resolve();
1013 } else {
1014 deferred.reject(new Error('MCP not ready yet.'));
1015 }
1016 })
1017 .catch((err) => {
1018 deferred.reject(err);
1019 })
1020 .done();
1021
1022 return deferred.promise;
1023 };
1024
1025 const readyCheck = function () {
1026 // perform ready check against /sys/ready endpoint
1027 // only available for BIG-IP 13.1+
1028
1029 return this.deviceInfo()
1030 .then((deviceInfo) => {
1031 const version = deviceInfo.version;
1032
1033 if (util.versionCompare(version, '13.1.0') < 0) {
1034 return q.resolve({ skip: true });
1035 }
1036 return this.list('/tm/sys/ready', undefined, util.NO_RETRY);
1037 })
1038 .then((readyInfo) => {
1039 if (readyInfo.skip) {
1040 return q.resolve();
1041 }
1042
1043 const entries = readyInfo.entries['https://localhost/mgmt/tm/sys/ready/0']
1044 .nestedStats.entries;
1045
1046 let isReady = true;
1047 Object.keys(entries).forEach((entry) => {
1048 if (entries[entry].description !== 'yes') {
1049 isReady = false;
1050 }
1051 });
1052
1053 if (isReady) {
1054 return q.resolve();
1055 }
1056 return q.reject(new Error('System is not ready yet'));
1057 })
1058 .catch((err) => {
1059 return q.reject(err);
1060 });
1061 };
1062
1063 availabilityChecks.forEach((availabilityCheck) => {
1064 promises.push({
1065 promise: this.list,
1066 arguments: [availabilityCheck, undefined, util.NO_RETRY]
1067 });
1068 });
1069
1070 promises.push({
1071 promise: mcpCheck
1072 });
1073 promises.push({
1074 promise: readyCheck
1075 });
1076
1077 return isInitialized(this)
1078 .then(() => {
1079 return util.callInSerial(this, promises);
1080 });
1081 };
1082
1083 return util.tryUntil(this, retry, func);
1084};
1085
1086/**
1087 * Reboots the device
1088 */
1089BigIp.prototype.reboot = function reboot() {
1090 return this.create('/tm/sys', { command: 'reboot' }, undefined, util.NO_RETRY);
1091};
1092
1093/**
1094 * Checks to see if the device needs to be rebooted
1095 *
1096 * @param {Object} [retryOptions] - Options for retrying the request.
1097 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
1098 * 0 to not retry. Default 60.
1099 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
1100 *
1101 * @returns {Promise} A promise which is resolved with 'true' if reboot is
1102 * required and resolved with 'false' otherwise.
1103 */
1104BigIp.prototype.rebootRequired = function rebootRequired(retryOptions) {
1105 const retry = retryOptions || util.DEFAULT_RETRY;
1106
1107 const func = function () {
1108 const deferred = q.defer();
1109
1110 this.ready()
1111 .then(() => {
1112 return this.list('/tm/sys/db/provision.action', undefined, util.NO_RETRY);
1113 })
1114 .then((response) => {
1115 if (response.value) {
1116 deferred.resolve(response.value === 'reboot');
1117 } else {
1118 deferred.reject(new Error('no value in response'));
1119 }
1120 })
1121 .catch((err) => {
1122 deferred.reject(err);
1123 })
1124 .done();
1125
1126 return deferred.promise;
1127 };
1128
1129 return util.tryUntil(this, retry, func);
1130};
1131
1132/**
1133 * Executes an iControl REST task
1134 *
1135 * @param {String} taskPath - URL that created the task.
1136 * @param {String} taskConfig - Body of the task request, typically JSON.
1137 * @param {Object} [options] - Options controlling the task request.
1138 * @param {String} [options.idAttribute] - Name of the Task ID attribute in the task create response.
1139 * Default: '_taskId'
1140 * @param {Boolean} [options.validate] - Whether to send a VALIDATING task request. Default: true
1141 * @param {Boolean} [options.neverReject] - Whether to allow rejecting promise. Default: false.
1142 * @param {String} [options.statusAttribute] - Name of the Task Status attribute in the result response.
1143 * Default: '_taskState'
1144 *
1145 * @returns {Promise} A promise which is resolved with true if the task completes
1146 * successfully, resolved with false if task goes to error state
1147 * or rejected if some other error occurs.
1148 */
1149BigIp.prototype.runTask = function runTask(taskPath, taskConfig, options) {
1150 let taskId;
1151 return this.ready()
1152 .then(() => {
1153 return this.create(taskPath, taskConfig, undefined, util.NO_RETRY);
1154 })
1155 .then((response) => {
1156 const idAttribute = (options && options.idAttribute) ? options.idAttribute : '_taskId';
1157 taskId = response[idAttribute];
1158 this.logger.silly('taskId:', taskId);
1159 if (options && options.validate === false) {
1160 return q();
1161 }
1162 return this.replace(
1163 `${taskPath}/${taskId}`,
1164 {
1165 _taskState: 'VALIDATING'
1166 },
1167 undefined,
1168 util.NO_RETRY
1169 );
1170 })
1171 .then(() => {
1172 const checkOptions = {};
1173 if (options && options.statusAttribute) {
1174 checkOptions.statusAttribute = options.statusAttribute;
1175 }
1176 if (options && options.neverReject) {
1177 checkOptions.neverReject = options.neverReject;
1178 }
1179 return checkTask.call(this, taskPath, taskId, checkOptions);
1180 })
1181 .then((status) => {
1182 if (!status.success) {
1183 if (status.response && status.response.errorMessage) {
1184 return q.reject(new Error(status.response.errorMessage));
1185 }
1186 return q.reject(new Error(`task at ${taskPath} failed`));
1187 }
1188 return q();
1189 })
1190 .catch((err) => {
1191 return q.reject(err);
1192 });
1193};
1194
1195/**
1196 * Saves sys config
1197 *
1198 * @param {String} [file] - File to save to. Default is bigip.conf
1199 * @param {Object} [retryOptions] - Options for retrying the request.
1200 * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first try fails.
1201 * 0 to not retry. Default 60.
1202 * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries. Default 10000.
1203 *
1204 * @returns {Promise} A promise which is resolved when the licensing
1205 * is complete or rejected if an error occurs.
1206 */
1207BigIp.prototype.save = function save(file, retryOptions) {
1208 const retry = retryOptions || util.DEFAULT_RETRY_IGNORE_ERRORS;
1209
1210 const func = function () {
1211 return this.ready()
1212 .then(() => {
1213 const commandBody = {
1214 command: 'save'
1215 };
1216
1217 if (file) {
1218 commandBody.options = [{ file }];
1219 }
1220
1221 return this.create('/tm/sys/config', commandBody, undefined, util.NO_RETRY);
1222 });
1223 };
1224
1225 return util.tryUntil(this, retry, func);
1226};
1227
1228/**
1229 * Save a ucs file in /var/local/ucs
1230 *
1231 * @param {String} file - Base name of ucs file
1232 *
1233 * @returns {Promise} - A promise which is resolve when the ucs is saved
1234 * or rejected if an error occurs
1235 */
1236BigIp.prototype.saveUcs = function saveUcs(file) {
1237 return this.ready()
1238 .then(() => {
1239 const commandBody = {
1240 command: 'save',
1241 name: file
1242 };
1243
1244 return this.runTask(UCS_TASK_PATH, commandBody, { neverReject: true });
1245 })
1246 .then(() => {
1247 // the UCS file can take a while to show up...
1248 const fullPath = `/var/local/ucs/${file}.ucs`;
1249 const checkFile = function () {
1250 const deferred = q.defer();
1251 fs.access(fullPath, (err) => {
1252 if (err) {
1253 deferred.reject();
1254 } else {
1255 deferred.resolve();
1256 }
1257 });
1258 return deferred.promise;
1259 };
1260
1261 return util.tryUntil(
1262 this,
1263 {
1264 maxRetries: 60,
1265 retryIntervalMs: 2000
1266 },
1267 checkFile
1268 );
1269 })
1270 .then(() => {
1271 return util.runShellCommand(`gzip -t -v /var/local/ucs/${file}.ucs`);
1272 })
1273 .then((response) => {
1274 if (response.indexOf('NOT OK') !== -1) {
1275 return q.reject(new Error('Validation of generated UCS file failed; ' +
1276 'newly generated UCS file appears to be corrupted'));
1277 }
1278 return q.resolve();
1279 })
1280 .catch((err) => {
1281 this.logger.info('saveUcs failed', err);
1282 return q.reject(err);
1283 });
1284};
1285
1286/**
1287 * Sets the management ip port
1288 *
1289 * @param {Number} port - port to use for management IP
1290 *
1291 * @returns {Promise} A promise which is resolved when the operation is complete
1292 * or rejected if an error occurs.
1293 */
1294BigIp.prototype.setPort = function setPort(port) {
1295 this.port = port;
1296 this.icontrol.port = port;
1297
1298 return q();
1299};
1300
1301/**
1302 * Submits commands in a transaction
1303 *
1304 * @param {Object[]} commands - Array of command definitions. Each command should be:
1305 * {
1306 * method: 'list' | 'create' | 'modify' | 'delete',
1307 * path: path for command,
1308 * body: optional body for command
1309 * }
1310 * @returns {Promise} A promise which is resolved when the transaction is complete
1311 * or rejected if an error occurs.
1312 */
1313BigIp.prototype.transaction = function transaction(commands) {
1314 const TRANSACTION_PATH = '/tm/transaction/';
1315 const promises = [];
1316 let transactionId;
1317
1318 const startTransaction = function startTransaction() {
1319 return this.create(TRANSACTION_PATH, {}, undefined, util.NO_RETRY)
1320 .then((response) => {
1321 return response.transId;
1322 });
1323 }.bind(this);
1324
1325 const commitTransaction = function commitTransaction() {
1326 return this.modify(
1327 TRANSACTION_PATH + transactionId,
1328 { state: 'VALIDATING' },
1329 undefined,
1330 util.NO_RETRY
1331 )
1332 .then((response) => {
1333 if (response.state !== 'COMPLETED') {
1334 return q.reject(new Error(`Transaction state not completed (${response.state})`));
1335 }
1336 return q(response);
1337 });
1338 }.bind(this);
1339
1340 const getPromise = function getPromise(method) {
1341 switch (method.toUpperCase()) {
1342 case 'LIST':
1343 return this.list;
1344 case 'CREATE':
1345 return this.create;
1346 case 'MODIFY':
1347 return this.modify;
1348 case 'DELETE':
1349 return this.delete;
1350 default:
1351 return q();
1352 }
1353 }.bind(this);
1354
1355 if (!commands || commands.length === 0) {
1356 return q();
1357 }
1358
1359 return this.ready()
1360 .then(() => {
1361 return startTransaction();
1362 })
1363 .then((transId) => {
1364 transactionId = transId;
1365
1366 commands.forEach((command) => {
1367 promises.push({
1368 promise: getPromise(command.method),
1369 arguments: [
1370 command.path,
1371 command.body,
1372 {
1373 headers: {
1374 'X-F5-REST-Coordination-Id': transactionId
1375 }
1376 }
1377 ]
1378 });
1379 });
1380
1381 return util.callInSerial(this, promises);
1382 })
1383 .then(() => {
1384 return commitTransaction();
1385 });
1386};
1387
1388/**
1389 * Checks status of iControl REST task
1390 *
1391 * @param {String} taskPath - URL that created the task
1392 * @param {String} taskIdToCheck - ID of task as returned by create
1393 * @param {Object} [options] - Options for parsing task response
1394 * @param {String} [options.statusAttribute] - Name of the Task Status attribute in the result response.
1395 * Default: '_taskState'
1396 *
1397 * @returns {Promise} A promise which is resolved with true if the task completes
1398 * successfully, resolved with false if task goes to error state
1399 * or rejected if some other error occurs.
1400 */
1401function checkTask(taskPath, taskIdToCheck, options) {
1402 const func = function () {
1403 const deferred = q.defer();
1404 this.list(`${taskPath}/${taskIdToCheck}`, undefined, util.SHORT_RETRY)
1405 .then((response) => {
1406 const statusAttribute = (options && options.statusAttribute)
1407 ? options.statusAttribute
1408 : '_taskState';
1409 const taskState = response[statusAttribute];
1410 if (taskState === 'VALIDATING' || taskState === 'STARTED' || taskState === 'CREATED') {
1411 // this is a normal state, just not done yet - keep waiting
1412 deferred.reject();
1413 } else if (taskState === 'COMPLETED' || taskState === 'FINISHED') {
1414 deferred.resolve({ success: true, response });
1415 } else if (taskState === 'FAILED') {
1416 deferred.resolve({ success: false, response });
1417 } else {
1418 deferred.reject(new Error(`checkTask: unexpected command status: ${taskState}`));
1419 }
1420 })
1421 .catch((err) => {
1422 if (options.neverReject) {
1423 deferred.resolve({ success: true });
1424 } else {
1425 deferred.reject(err);
1426 }
1427 });
1428
1429 return deferred.promise;
1430 };
1431
1432 return util.tryUntil(this, util.DEFAULT_RETRY, func);
1433}
1434
1435function isInitialized(bigIp) {
1436 if (bigIp.isInitialized) {
1437 return q();
1438 }
1439 return q.reject();
1440}
1441
1442function restorePrimaryKey(ucsFile) {
1443 const logId = 'restorePrimaryKey:';
1444 const tempUcsDir = '/config/tempUcs';
1445
1446 this.logger.silly(logId, 'calling bigstart stop');
1447 return util.runShellCommand('bigstart stop')
1448 .then(() => {
1449 this.logger.silly(logId, 'bigstart stopped');
1450 const deferred = q.defer();
1451 fs.mkdir(tempUcsDir, (err) => {
1452 if (err) {
1453 this.logger.debug(
1454 logId,
1455 'error making temp ucs dir',
1456 err && err.message ? err.message : err
1457 );
1458 deferred.reject(err);
1459 } else {
1460 this.logger.silly(logId, 'mkdir succeeded');
1461 deferred.resolve();
1462 }
1463 });
1464 return deferred.promise;
1465 })
1466 .then(() => {
1467 this.logger.silly(logId, 'untarring ucs', ucsFile);
1468 return util.runShellCommand(
1469 `tar --warning=no-timestamp -xf ${ucsFile} -C ${tempUcsDir}`
1470 );
1471 })
1472 .then(() => {
1473 this.logger.silly(logId, 'untar success, reading key');
1474 return util.readDataFromFile(`${tempUcsDir}${BACKUP.PRIMARY_KEY_DIR}`);
1475 })
1476 .then((oldPrimaryKey) => {
1477 this.logger.silly(logId, 'read success, writing key');
1478 return util.writeDataToFile(oldPrimaryKey, BACKUP.PRIMARY_KEY_DIR);
1479 })
1480 .then(() => {
1481 this.logger.silly(logId, 'wrote primary key, reading unit key');
1482 return util.readDataFromFile(`${tempUcsDir}${BACKUP.UNIT_KEY_DIR}`);
1483 })
1484 .then((oldUnitKey) => {
1485 this.logger.silly(logId, 'read unitkey success, writing unit key');
1486 return util.writeDataToFile(oldUnitKey, BACKUP.UNIT_KEY_DIR);
1487 })
1488 .then(() => {
1489 this.logger.silly(logId, 'cleaning up');
1490 return util.runShellCommand(`rm -rf ${tempUcsDir}`);
1491 })
1492 .then(() => {
1493 this.logger.silly(logId, 'calling bigstart restart');
1494 return util.runShellCommand('bigstart start');
1495 })
1496 .catch((err) => {
1497 this.logger.debug('error restoring primary key', err && err.message ? err.message : err);
1498 return q.reject(err);
1499 });
1500}
1501
1502function runLoadUcs(file, ucsLoadOptions) {
1503 const deferred = q.defer();
1504
1505 const commandBody = {
1506 command: 'load',
1507 name: file
1508 };
1509 const commandOptions = [];
1510 let commandOption;
1511
1512 Object.keys(ucsLoadOptions).forEach((option) => {
1513 commandOption = {};
1514 commandOption[option] = ucsLoadOptions[option];
1515 commandOptions.push(commandOption);
1516 });
1517
1518 if (commandOptions.length > 0) {
1519 commandBody.options = commandOptions;
1520 }
1521
1522 this.logger.silly('runLoadUcs: calling runTask');
1523 this.runTask(UCS_TASK_PATH, commandBody)
1524 .then(() => {
1525 this.logger.silly('runLoadUcs: runTask success');
1526 return this.ready(util.LONG_RETRY);
1527 })
1528 .then(() => {
1529 this.logger.silly('runLoadUcs: bigIp ready');
1530 deferred.resolve();
1531 })
1532 .catch((err) => {
1533 // load may have failed because of encrypted private keys
1534 // workaround this issue
1535 this.logger.debug(
1536 'Initial load of UCS failed',
1537 err && err.message ? err.message : err,
1538 'trying to work around'
1539 );
1540 restorePrimaryKey.call(this, file)
1541 .then(() => {
1542 this.logger.silly('primary key restored, waiting for ready');
1543 return this.ready();
1544 })
1545 .then(() => {
1546 this.logger.silly('big ip ready after primary key restore');
1547 deferred.resolve();
1548 })
1549 .catch((copyErr) => {
1550 deferred.reject(copyErr);
1551 });
1552 });
1553
1554 return deferred.promise;
1555}
1556
1557module.exports = BigIp;