UNPKG

48 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 q = require('q');
20const BigIp = require('../lib/bigIp');
21const Logger = require('../lib/logger');
22const ActiveError = require('../lib/activeError');
23const ipc = require('../lib/ipc');
24const signals = require('../lib/signals');
25const util = require('../lib/util');
26const metricsCollector = require('../lib/metricsCollector');
27const commonOptions = require('./commonOptions');
28const cloudProviderFactory = require('../lib/cloudProviderFactory');
29const localCryptoUtil = require('../lib/localCryptoUtil');
30const cryptoUtil = require('../lib/cryptoUtil');
31
32(function run() {
33 const runner = {
34 /**
35 * Runs the onboarding script
36 *
37 * @param {String[]} argv - The process arguments
38 * @param {Object} testOpts - Options used during testing
39 * @param {Object} testOpts.bigIp - BigIp object to use for testing
40 * @param {Function} cb - Optional cb to call when done
41 */
42 run(argv, testOpts, cb) {
43 const DEFAULT_LOG_FILE = '/tmp/onboard.log';
44 const ARGS_FILE_ID = `onboard_${Date.now()}`;
45 const ARGS_TO_STRIP = ['--wait-for'];
46
47 const KEYS_TO_MASK = [
48 '-p',
49 '--password',
50 '--set-password',
51 '--set-root-password',
52 '--big-iq-password'
53 ];
54 const OPTIONS_TO_UNDEFINE = [
55 'bigIqPasswordUri',
56 'bigIqPassword',
57 'metrics',
58 'password',
59 'passwordUrl',
60 'skuKeyword1',
61 'skuKeyword2',
62 'unitOfMeasure',
63 'tenant'
64 ];
65 const REQUIRED_OPTIONS = ['host'];
66 const globalSettings = {};
67 const dbVars = {};
68 const provisionModules = {};
69 const provisionModule = {};
70 const rootPasswords = {};
71 const updateUsers = [];
72 const loggerOptions = {};
73 const metrics = {};
74 const createRegKeyPool = {};
75 const createLicensePool = {};
76 const optionsForTest = {};
77
78 const providerOptions = {};
79
80 let loggableArgs;
81 let logger;
82 let logFileName;
83 let bigIp;
84 let rebooting;
85 let exiting;
86 let index;
87 let randomUser;
88
89 let provider;
90 let bigIqPasswordData;
91
92 Object.assign(optionsForTest, testOpts);
93
94 /**
95 * Special case of util.pair. Used to parse root password options in the form of
96 * old:oldRootPassword,new:newRootPassword
97 * Since passwords can contain any character, a delimiter is difficult to find.
98 * Compromise by looking for ',new:' as a delimeter
99 */
100 const parseRootPasswords = function (passwordsValue) {
101 const set = passwordsValue.split(',new:');
102
103 if (set.length === 2) {
104 rootPasswords.old = set[0].split('old:')[1];
105 rootPasswords.new = set[1];
106 }
107 };
108
109 /**
110 * Control whether to load f5-cloud-libs-{provider} library.
111 * There are cases where the cloud name is needed, but a cloud provider is not.
112 */
113 const shouldLoadProviderLibrary = function (options) {
114 return (!!options.signalResource);
115 };
116
117 try {
118 /* eslint-disable max-len */
119 const options = commonOptions.getCommonOptions(DEFAULT_LOG_FILE)
120 .option(
121 '--ntp <ntp_server>',
122 'Set NTP server. For multiple NTP servers, use multiple --ntp entries.',
123 util.collect,
124 []
125 )
126 .option(
127 '--tz <timezone>',
128 'Set timezone for NTP setting.'
129 )
130 .option(
131 '--dns <DNS server>',
132 'Set DNS server. For multiple DNS severs, use multiple --dns entries.',
133 util.collect,
134 []
135 )
136 .option(
137 '--ssl-port <ssl_port>', 'Set the SSL port for the management IP',
138 parseInt
139 )
140 .option(
141 '-l, --license <license_key>',
142 'License device with <license_key>.'
143 )
144 .option(
145 '-a, --add-on <add_on_key>',
146 'License device with <add_on_key>. For multiple keys, use multiple -a entries.',
147 util.collect,
148 []
149 )
150 .option(
151 '--cloud <provider>',
152 'Cloud provider (aws | azure | etc.). This is required if licensing via BIG-IQ 5.4+ is being used, signalling resource provisioned, or providing a primary passphrase'
153 )
154 .option(
155 '--provider-options <cloud_options>',
156 'Options specific to cloud_provider. Ex: param1:value1,param2:value2',
157 util.map,
158 providerOptions
159 )
160 .option(
161 '--license-pool',
162 'License BIG-IP from a BIG-IQ license pool. Supply the following:'
163 )
164 .option(
165 ' --big-iq-host <ip_address or FQDN>',
166 ' IP address or FQDN of BIG-IQ'
167 )
168 .option(
169 ' --big-iq-user <user>',
170 ' BIG-IQ admin user name'
171 )
172 .option(
173 ' --big-iq-password [password]',
174 ' BIG-IQ admin user password.'
175 )
176 .option(
177 ' --big-iq-password-uri [password_uri]',
178 ' URI (file, http(s), arn) to location that contains BIG-IQ admin user password. Use this or --big-iq-password.'
179 )
180 .option(
181 ' --big-iq-password-encrypted',
182 ' Indicates that the BIG-IQ password is encrypted.'
183 )
184 .option(
185 ' --license-pool-name <pool_name>',
186 ' Name of BIG-IQ license pool.'
187 )
188 .option(
189 ' --sku-keyword-1 [sku_keyword_1]',
190 ' skuKeyword1 parameter for CLPv2 licensing. Default none.'
191 )
192 .option(
193 ' --sku-keyword-2 [sku_keyword_2]',
194 ' skuKeyword2 parameter for CLPv2 licensing. Default none.'
195 )
196 .option(
197 ' --unit-of-measure [unit_of_measure]',
198 ' unitOfMeasure parameter for CLPv2 licensing. Default none.'
199 )
200 .option(
201 ' --tenant [tenant]',
202 ' tenant parameter for CLPv2 licensing. Default none.'
203 )
204 .option(
205 ' --big-ip-mgmt-address <big_ip_address>',
206 ' IP address or FQDN of BIG-IP management port. Use this if BIG-IP reports an address not reachable from BIG-IQ.'
207 )
208 .option(
209 ' --big-ip-mgmt-port <big_ip_port>',
210 ' Port for the management address. Use this if the BIG-IP is not reachable from BIG-IQ via the port used in --port'
211 )
212 .option(
213 ' --no-unreachable',
214 ' Do not use the unreachable API even if it is supported by BIG-IQ.'
215 )
216 .option(
217 ' --revoke',
218 ' Request BIG-IQ to revoke this units license rather than granting one.'
219 )
220 .option(
221 '--signal-resource',
222 'Signal cloud provider when BIG-IP has been provisioned.'
223 )
224 .option(
225 '--big-iq-password-data-uri <key_uri>',
226 'URI (arn, url, etc.) to a JSON file containing the BIG-IQ passwords (required keys: admin, root, primarypassphrase)'
227 )
228 .option(
229 ' --big-iq-password-data-encrypted',
230 ' Indicates that the BIG-IQ password data is encrypted (either with encryptDataToFile or generatePassword)'
231 )
232 .option(
233 '-n, --hostname <hostname>',
234 'Set device hostname.'
235 )
236 .option(
237 '-g, --global-setting <name:value>',
238 'Set global setting <name> to <value>. For multiple settings, use multiple -g entries.',
239 util.pair,
240 globalSettings
241 )
242 .option(
243 '-d, --db <name:value>',
244 'Set db variable <name> to <value>. For multiple settings, use multiple -d entries.',
245 util.pair,
246 dbVars
247 )
248 .option(
249 '--set-root-password <old:old_password,new:new_password>',
250 'Set the password for the root user from <old_password> to <new_password>.',
251 parseRootPasswords
252 )
253 .option(
254 '--set-primary-key',
255 'If running on a BIG-IQ, set the primary key with a random passphrase'
256 )
257 .option(
258 '--create-license-pool <name:reg_key>',
259 'If running on a BIG-IQ, create a pool-style license (purchased pool, utility, volume, or FPS) with the name and reg key.',
260 util.pair,
261 createLicensePool
262 )
263 .option(
264 '--create-reg-key-pool <name:reg_key_list>',
265 'If running on a BIG-IQ, create a reg key pool with the given name and reg keys. Reg keys should be comma separated.',
266 util.pair,
267 createRegKeyPool
268 )
269 .option(
270 '--update-user <user:user,password:password,passwordUrl:passwordUrl,role:role,shell:shell>',
271 'Update user password (or password from passwordUrl), or create user with password, role, and shell. Role and shell are only valid on create.',
272 util.mapArray,
273 updateUsers
274 )
275 .option(
276 '-m, --module <name:level>',
277 'Provision module <name> to <level>. For multiple entries, use --modules',
278 util.pair,
279 provisionModule
280 )
281 .option(
282 '--modules <name:level>',
283 'Provision module(s) <name> to <level> (comma-separated list of module:level pairs).',
284 util.map,
285 provisionModules
286 )
287 .option(
288 '--install-ilx-package <package_uri>',
289 'URI (file) of an iControl LX/iApps LX package to install. The package must already exist at this location.',
290 util.collect,
291 []
292 )
293 .option(
294 '--ping [address]',
295 'Do a ping at the end of onboarding to verify that the network is up. Default address is f5.com'
296 )
297 .option(
298 '--update-sigs',
299 'Update ASM signatures'
300 )
301 .option(
302 '--metrics [customerId:unique_id, deploymentId:deployment_id, templateName:template_name, templateVersion:template_version, cloudName:[aws | azure | gce | etc.], region:region, bigIpVersion:big_ip_version, licenseType:[byol | payg]]',
303 'Optional usage metrics to collect. Customer ID should not identify a specific customer.',
304 util.map,
305 metrics
306 )
307 .option(
308 '--force-reboot',
309 'Force a reboot at the end. This may be necessary for certain configurations. Option --force-reboot and --no-reboot cannot be specified simultaneously.'
310 )
311 .parse(argv);
312 /* eslint-enable max-len */
313
314 loggerOptions.console = options.console;
315 loggerOptions.logLevel = options.logLevel;
316 loggerOptions.module = module;
317
318 if (options.output) {
319 loggerOptions.fileName = options.output;
320 }
321
322 if (options.errorFile) {
323 loggerOptions.errorFile = options.errorFile;
324 }
325
326 logger = Logger.getLogger(loggerOptions);
327 ipc.setLoggerOptions(loggerOptions);
328 util.setLoggerOptions(loggerOptions);
329 metricsCollector.setLoggerOptions(loggerOptions);
330
331 // Remove specific options with no provided value
332 OPTIONS_TO_UNDEFINE.forEach((opt) => {
333 if (typeof options[opt] === 'boolean') {
334 logger.debug(`No value set for option ${opt}. Removing option.`);
335 options[opt] = undefined;
336 }
337 });
338
339 // Log the input, but don't log passwords
340 loggableArgs = argv.slice();
341 for (let i = 0; i < loggableArgs.length; i++) {
342 if (KEYS_TO_MASK.indexOf(loggableArgs[i]) !== -1) {
343 loggableArgs[i + 1] = '*******';
344 }
345 }
346 index = loggableArgs.indexOf('--update-user');
347 if (index !== -1) {
348 loggableArgs[index + 1] = loggableArgs[index + 1].replace(/password:([^,])+/, '*******');
349 }
350 logger.info(`${loggableArgs[1]} called with`, loggableArgs.join(' '));
351
352 for (let i = 0; i < REQUIRED_OPTIONS.length; i++) {
353 if (!options[REQUIRED_OPTIONS[i]]) {
354 const error = `${REQUIRED_OPTIONS[i]} is a required command line option.`;
355
356 ipc.send(signals.CLOUD_LIBS_ERROR);
357
358 util.logError(error, loggerOptions);
359 util.logAndExit(error, 'error', 1);
360 }
361 }
362
363 if (options.forceReboot && !options.reboot) {
364 const error = 'Option --force-reboot and --no-reboot cannot be specified simultaneously.';
365
366 ipc.send(signals.CLOUD_LIBS_ERROR);
367
368 util.logError(error, loggerOptions);
369 util.logAndExit(error, 'error', 1);
370 }
371
372 if (options.user && !(options.password || options.passwordUrl)) {
373 const error = 'If specifying --user, --password or --password-url is required.';
374
375 ipc.send(signals.CLOUD_LIBS_ERROR);
376
377 util.logError(error, loggerOptions);
378 util.logAndExit(error, 'error', 1);
379 }
380
381 // When running in cloud init, we need to exit so that cloud init can complete and
382 // allow the device services to start
383 if (options.background) {
384 logFileName = options.output || DEFAULT_LOG_FILE;
385 logger.info('Spawning child process to do the work. Output will be in', logFileName);
386 util.runInBackgroundAndExit(process, logFileName);
387 }
388
389 // Use hostname if both hostname and global-settings hostname are set
390 if (globalSettings && options.hostname) {
391 if (globalSettings.hostname || globalSettings.hostName) {
392 logger.info('Using host-name option to override global-settings hostname');
393 delete globalSettings.hostName;
394 delete globalSettings.hostname;
395 }
396 }
397
398 // Check whether a cloud provider is required
399 if (options.cloud && shouldLoadProviderLibrary(options)) {
400 provider = optionsForTest.cloudProvider;
401 if (!provider) {
402 provider = cloudProviderFactory.getCloudProvider(
403 options.cloud,
404 {
405 loggerOptions
406 }
407 );
408 }
409 }
410 // Start processing...
411
412 // Save args in restart script in case we need to reboot to recover from an error
413 util.saveArgs(argv, ARGS_FILE_ID)
414 .then(() => {
415 if (options.waitFor) {
416 logger.info('Waiting for', options.waitFor);
417 return ipc.once(options.waitFor);
418 }
419
420 return q();
421 })
422 .then(() => {
423 // Whatever we're waiting for is done, so don't wait for
424 // that again in case of a reboot
425 return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
426 })
427 .then(() => {
428 if (provider) {
429 logger.info('Initializing cloud provider');
430 return provider.init(providerOptions);
431 }
432 return q();
433 })
434 .then(() => {
435 logger.info('Onboard starting.');
436 ipc.send(signals.ONBOARD_RUNNING);
437
438 // Retrieve, and save, stored password data
439 if (options.bigIqPasswordDataUri) {
440 return util.readData(options.bigIqPasswordDataUri,
441 true,
442 {
443 clOptions: providerOptions,
444 logger,
445 loggerOptions
446 })
447 .then((uriData) => {
448 if (options.bigIqPasswordDataEncrypted) {
449 return localCryptoUtil.decryptPassword(uriData);
450 }
451 return q(uriData);
452 })
453 .then((uriData) => {
454 const parsedData = (typeof uriData === 'string')
455 ? JSON.parse(uriData.trim())
456 : uriData;
457 bigIqPasswordData = util.lowerCaseKeys(
458 parsedData
459 );
460 })
461 .then(() => {
462 if (!bigIqPasswordData.admin
463 || !bigIqPasswordData.root
464 || !bigIqPasswordData.primarypassphrase
465 ) {
466 const msg =
467 'Required passwords missing from --biq-iq-password-data-uri';
468 logger.info(msg);
469 return q.reject(msg);
470 }
471 return q();
472 })
473 .catch((err) => {
474 logger.info('Unable to retrieve JSON from --big-iq-password-data-uri');
475 return q.reject(err);
476 });
477 }
478 return q();
479 })
480 .then(() => {
481 if (!options.user) {
482 logger.info('Generating temporary user.');
483 return cryptoUtil.nextRandomUser();
484 }
485
486 return q(
487 {
488 user: options.user,
489 password: options.password || options.passwordUrl
490 }
491 );
492 })
493 .then((credentials) => {
494 randomUser = credentials.user; // we need this info later to delete it
495
496 // Create the bigIp client object
497 bigIp = optionsForTest.bigIp || new BigIp({ loggerOptions });
498
499 logger.info('Initializing device.');
500 return bigIp.init(
501 options.host,
502 credentials.user,
503 credentials.password,
504 {
505 port: options.port,
506 passwordIsUrl: typeof options.passwordUrl !== 'undefined',
507 passwordEncrypted: options.passwordEncrypted,
508 clOptions: providerOptions
509 }
510 );
511 })
512 .then(() => {
513 logger.info('Waiting for device to be ready.');
514 return bigIp.ready();
515 })
516 .then(() => {
517 logger.info('Device is ready.');
518 // Set admin password
519 if (options.bigIqPasswordDataUri) {
520 return bigIp.onboard.updateUser('admin', bigIqPasswordData.admin);
521 }
522 return q();
523 })
524 .then(() => {
525 const deferred = q.defer();
526 // Set the PrimaryKey if it's not set, using either a random passphrase or
527 // a passphrase provided via --password-data-uri
528 if (bigIp.isBigIq()) {
529 bigIp.onboard.isPrimaryKeySet()
530 .then((isSet) => {
531 if (isSet) {
532 logger.info('Primary key is already set.');
533 deferred.resolve();
534 } else if (options.setPrimaryKey) {
535 logger.info('Setting primary key.');
536
537 bigIp.onboard.setRandomPrimaryPassphrase()
538 .then(() => {
539 deferred.resolve();
540 })
541 .catch((err) => {
542 logger.info(
543 'Unable to set primary key',
544 err && err.message ? err.message : err
545 );
546 deferred.reject(err);
547 });
548 } else if (bigIqPasswordData) {
549 logger.info('Setting primary passphrase from password data uri');
550 bigIp.onboard.setPrimaryPassphrase(
551 bigIqPasswordData.primarypassphrase
552 )
553 .then(() => {
554 deferred.resolve();
555 })
556 .catch((err) => {
557 logger.info(
558 'Unable to set primary passphrase',
559 err && err.message ? err.message : err
560 );
561 deferred.reject(err);
562 });
563 }
564 })
565 .catch((err) => {
566 logger.info(
567 'Unable to check primary key', err && err.message ? err.message : err
568 );
569 deferred.reject(err);
570 });
571 } else {
572 deferred.resolve();
573 }
574
575 return deferred.promise;
576 })
577 .then(() => {
578 if (options.sslPort) {
579 logger.info('Setting SSL port.');
580 return bigIp.onboard.sslPort(options.sslPort);
581 }
582
583 return q();
584 })
585 .then((response) => {
586 let portIndex;
587
588 logger.debug(response);
589
590 // If we just successfully changed the SSL port, save --port
591 // as an argument in case we reboot
592 if (options.sslPort) {
593 // If there is already a port argument, remove it
594 if (options.port) {
595 portIndex = argv.indexOf('--port');
596 if (portIndex !== -1) {
597 argv.splice(portIndex, 2);
598 }
599 }
600 argv.push('--port', options.sslPort);
601 return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
602 }
603
604 return q();
605 })
606 .then((response) => {
607 logger.debug(response);
608
609 if (Object.keys(rootPasswords).length > 0) {
610 if (!rootPasswords.old || !rootPasswords.new) {
611 return q.reject('Old or new password missing for root user.');
612 }
613
614 logger.info('Setting rootPassword.');
615 return bigIp.onboard.password('root', rootPasswords.new, rootPasswords.old);
616 }
617
618 return q();
619 })
620 .then((response) => {
621 logger.debug(response);
622
623 if (options.cloud && options.bigIqPasswordDataUri) {
624 logger.info('Setting root password');
625 return bigIp.onboard.setRootPassword(
626 bigIqPasswordData.root,
627 undefined,
628 { enableRoot: true }
629 );
630 }
631 return q();
632 })
633 .then((response) => {
634 const promises = [];
635
636 logger.debug(response);
637
638 if (updateUsers.length > 0) {
639 for (let i = 0; i < updateUsers.length; i++) {
640 logger.info('Updating user', updateUsers[i].user);
641 promises.push(bigIp.onboard.updateUser(
642 updateUsers[i].user,
643 updateUsers[i].password || updateUsers[i].passwordUrl,
644 updateUsers[i].role,
645 updateUsers[i].shell,
646 {
647 passwordIsUrl: typeof updateUsers[i].passwordUrl !== 'undefined'
648 }
649 ));
650 }
651 return q.all(promises);
652 }
653
654 return q();
655 })
656 .then((response) => {
657 let ntpBody;
658
659 logger.debug(response);
660
661 if (options.ntp.length > 0 || options.tz) {
662 logger.info('Setting up NTP.');
663
664 ntpBody = {};
665
666 if (options.ntp && options.ntp.length > 0) {
667 ntpBody.servers = options.ntp;
668 }
669
670 if (options.tz) {
671 ntpBody.timezone = options.tz;
672 }
673
674 return bigIp.modify(
675 '/tm/sys/ntp',
676 ntpBody
677 );
678 }
679
680 return q();
681 })
682 .then((response) => {
683 logger.debug(response);
684
685 if (options.dns.length > 0) {
686 logger.info('Setting up DNS.');
687
688 return bigIp.modify(
689 '/tm/sys/dns',
690 {
691 'name-servers': options.dns
692 }
693 );
694 }
695
696 return q();
697 })
698 .then((response) => {
699 logger.debug(response);
700
701 if (options.hostname) {
702 logger.info('Setting hostname to', options.hostname);
703 return bigIp.onboard.hostname(options.hostname);
704 }
705
706 return q();
707 })
708 .then((response) => {
709 logger.debug(response);
710
711 // BIG-IP and BIG-IQ disable their setup gui differently.
712 // We'll take care of BIG-IQ at the end of onboarding.
713 if (bigIp.isBigIp()) {
714 globalSettings.guiSetup = 'disabled';
715 }
716
717 if (Object.keys(globalSettings).length > 0) {
718 logger.info('Setting global settings.');
719 return bigIp.onboard.globalSettings(globalSettings);
720 }
721
722 return q();
723 })
724 .then((response) => {
725 logger.debug(response);
726
727 if (Object.keys(dbVars).length > 0) {
728 logger.info('Setting DB vars.');
729 return bigIp.onboard.setDbVars(dbVars);
730 }
731
732 return q();
733 })
734 .then((response) => {
735 logger.debug(response);
736
737 const registrationKey = options.license;
738 const addOnKeys = options.addOn;
739
740 if (registrationKey || addOnKeys.length > 0) {
741 logger.info('Licensing.');
742
743 return bigIp.onboard.license(
744 {
745 registrationKey,
746 addOnKeys,
747 overwrite: true
748 }
749 );
750 } else if (options.licensePool) {
751 if (
752 !options.bigIqHost ||
753 !options.bigIqUser ||
754 !(options.bigIqPassword || options.bigIqPasswordUri) ||
755 !options.licensePoolName
756 ) {
757 return q.reject(new Error('Missing parameters for BIG-IQ license pool'));
758 }
759
760 if (options.revoke) {
761 logger.info('Requesting BIG-IQ to revoke licnse.');
762 return bigIp.onboard.revokeLicenseViaBigIq(
763 options.bigIqHost,
764 options.bigIqUser,
765 options.bigIqPassword || options.bigIqPasswordUri,
766 options.licensePoolName,
767 {
768 passwordIsUri: typeof options.bigIqPasswordUri !== 'undefined',
769 passwordEncrypted: options.bigIqPasswordEncrypted,
770 bigIpMgmtAddress: options.bigIpMgmtAddress,
771 bigIpMgmtPort: options.bigIpMgmtPort,
772 noUnreachable: !options.unreachable
773 }
774 );
775 }
776
777 logger.info('Getting license from BIG-IQ license pool.');
778 return bigIp.onboard.licenseViaBigIq(
779 options.bigIqHost,
780 options.bigIqUser,
781 options.bigIqPassword || options.bigIqPasswordUri,
782 options.licensePoolName,
783 options.cloud,
784 {
785 passwordIsUri: typeof options.bigIqPasswordUri !== 'undefined',
786 passwordEncrypted: options.bigIqPasswordEncrypted,
787 bigIpMgmtAddress: options.bigIpMgmtAddress,
788 bigIpMgmtPort: options.bigIpMgmtPort,
789 skuKeyword1: options.skuKeyword1,
790 skuKeyword2: options.skuKeyword2,
791 unitOfMeasure: options.unitOfMeasure,
792 tenant: options.tenant,
793 noUnreachable: !options.unreachable
794 }
795 );
796 }
797
798 return q();
799 })
800 .then((response) => {
801 logger.debug(response);
802
803 if (Object.keys(provisionModule).length > 0) {
804 logger.info('Provisioning module', provisionModule);
805 return bigIp.onboard.provision(provisionModule);
806 }
807
808 return q();
809 })
810 .then((response) => {
811 logger.debug(response);
812
813 if (Object.keys(provisionModules).length > 0) {
814 logger.info('Provisioning modules', provisionModules);
815 return bigIp.onboard.provision(provisionModules);
816 }
817
818 return q();
819 })
820 .then((response) => {
821 logger.debug(response);
822
823 if (options.updateSigs) {
824 logger.info('Updating ASM signatures');
825 return bigIp.create(
826 '/tm/asm/tasks/update-signatures',
827 {}
828 );
829 }
830
831 return q();
832 })
833 .then((response) => {
834 logger.debug(response);
835
836 const names = Object.keys(createLicensePool);
837 if (names.length > 0 && bigIp.isBigIq()) {
838 const regKey = createLicensePool[names[0]];
839 logger.info('Creating license pool.');
840 return bigIp.onboard.createLicensePool(names[0], regKey);
841 }
842 return q();
843 })
844 .then((response) => {
845 logger.debug(response);
846
847 const names = Object.keys(createRegKeyPool);
848 if (names.length > 0 && bigIp.isBigIq()) {
849 const regKeyCsv = createRegKeyPool[names[0]];
850 const regKeys = regKeyCsv.split(',');
851 logger.info('Creating reg key pool.');
852 return bigIp.onboard.createRegKeyPool(names[0], regKeys);
853 }
854 return q();
855 })
856 .then((response) => {
857 logger.debug(response);
858
859 if (options.installIlxPackage) {
860 const packages = options.installIlxPackage;
861 const promises = [];
862
863 if (packages.length > 0) {
864 packages.forEach((packagePath) => {
865 promises.push(bigIp.onboard.installIlxPackage(packagePath));
866 });
867 return q.all(promises);
868 }
869 }
870 return q();
871 })
872 .then(() => {
873 // Have installed the ilx package(s); strip out args, including --install-ilx-package
874 ARGS_TO_STRIP.push('--install-ilx-package');
875 return util.saveArgs(argv, ARGS_FILE_ID, ARGS_TO_STRIP);
876 })
877 .then(() => {
878 if (bigIp.isBigIq()) {
879 // Disable the BIG-IQ setup gui. BIG-IP is done
880 // via global settings and is handled with other global
881 // settings above
882 return bigIp.modify(
883 '/shared/system/setup',
884 {
885 isSystemSetup: true,
886 isRootPasswordChanged: true,
887 isAdminPasswordChanged: true
888 }
889 );
890 }
891 return q();
892 })
893 .then((response) => {
894 logger.debug(response);
895 logger.info('Saving config.');
896 return bigIp.save();
897 })
898 .then((response) => {
899 logger.debug(response);
900 logger.info('Waiting for device to be active.');
901 return bigIp.active();
902 })
903 .then((response) => {
904 logger.debug(response);
905 let address;
906 if (options.ping) {
907 address = options.ping === true ? 'f5.com' : options.ping;
908 logger.info('Pinging', address);
909 return bigIp.ping(address);
910 }
911
912 return q();
913 })
914 .then((response) => {
915 logger.debug(response);
916 if (Object.keys(metrics).length > 0) {
917 logger.info('Sending metrics');
918 metrics.action = 'onboard';
919 metrics.cloudLibsVersion = options.version();
920 return metricsCollector.upload(metrics);
921 }
922
923 return q();
924 })
925 .then((response) => {
926 logger.debug(response);
927 logger.info('Device onboard complete.');
928 return bigIp.rebootRequired();
929 })
930 .then((response) => {
931 if (response === true) {
932 logger.warn('Reboot required.');
933 rebooting = true;
934 }
935 if (options.forceReboot) {
936 rebooting = true;
937 // After reboot, we just want to send our done signal,
938 // in case any other scripts are waiting on us. So, modify
939 // the saved args for that
940 const forcedRebootArgsToStrip = util.getArgsToStripDuringForcedReboot(options);
941 return util.saveArgs(argv, ARGS_FILE_ID, forcedRebootArgsToStrip);
942 }
943
944 return q();
945 })
946 .then(() => {
947 if (rebooting) {
948 logger.info('Rebooting and exiting. Will continue after reboot.');
949 return util.reboot(bigIp, { signalOnly: !options.reboot });
950 }
951 return q();
952 })
953 .then(() => {
954 if (!rebooting && provider && options.signalResource) {
955 logger.info('Signalling provider that instance provisioned.');
956 return provider.signalInstanceProvisioned();
957 }
958 return q();
959 })
960 .catch((err) => {
961 let message;
962
963 if (!err) {
964 message = 'unknown reason';
965 } else {
966 message = err.message;
967 }
968
969 if (err) {
970 if (err instanceof ActiveError || err.name === 'ActiveError') {
971 logger.warn('Device active check failed.');
972 rebooting = true;
973 return util.reboot(bigIp, { signalOnly: !options.reboot });
974 }
975 }
976
977 ipc.send(signals.CLOUD_LIBS_ERROR);
978
979 const error = `Onboard failed: ${message}`;
980 util.logError(error, loggerOptions);
981 util.logAndExit(error, 'error', 1);
982
983 exiting = true;
984 return q();
985 })
986 .done((response) => {
987 logger.debug(response);
988
989 if (!options.user) {
990 logger.info('Deleting temporary user.');
991 util.deleteUser(randomUser);
992 }
993
994 if ((!rebooting || !options.reboot) && !exiting) {
995 ipc.send(options.signal || signals.ONBOARD_DONE);
996 }
997
998 if (cb) {
999 cb();
1000 }
1001
1002 if (!rebooting) {
1003 util.deleteArgs(ARGS_FILE_ID);
1004 if (!exiting) {
1005 util.logAndExit('Onboard finished.');
1006 }
1007 } else if (!options.reboot) {
1008 // If we are rebooting, but we were called with --no-reboot, send signal
1009 if (!exiting) {
1010 util.logAndExit('Onboard finished. Reboot required but not rebooting.');
1011 }
1012 } else {
1013 util.logAndExit('Onboard finished. Reboot required.');
1014 }
1015 });
1016
1017 // If another script has signaled an error, exit, marking ourselves as DONE
1018 ipc.once(signals.CLOUD_LIBS_ERROR)
1019 .then(() => {
1020 ipc.send(options.signal || signals.ONBOARD_DONE);
1021 util.logAndExit('ERROR signaled from other script. Exiting');
1022 });
1023
1024 // If we reboot due to some other script, exit - otherwise cloud
1025 // providers won't know we're done. If we forced the reboot ourselves,
1026 // we will exit when that call completes.
1027 ipc.once('REBOOT')
1028 .then(() => {
1029 if (!rebooting) {
1030 util.logAndExit('REBOOT signaled. Exiting.');
1031 }
1032 });
1033 } catch (err) {
1034 if (logger) {
1035 logger.error('Onbarding error:', err);
1036 }
1037
1038 if (cb) {
1039 cb();
1040 }
1041 }
1042 }
1043 };
1044
1045 module.exports = runner;
1046
1047 // If we're called from the command line, run
1048 // This allows for test code to call us as a module
1049 if (!module.parent) {
1050 runner.run(process.argv);
1051 }
1052}());