underpost 2.8.79 → 2.8.84

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.
Files changed (53) hide show
  1. package/.github/workflows/ghpkg.yml +22 -20
  2. package/.github/workflows/npmpkg.yml +15 -10
  3. package/.github/workflows/pwa-microservices-template.page.yml +12 -3
  4. package/.github/workflows/pwa-microservices-template.test.yml +20 -17
  5. package/.vscode/extensions.json +2 -3
  6. package/.vscode/settings.json +2 -42
  7. package/Dockerfile +14 -33
  8. package/README.md +43 -25
  9. package/bin/db.js +1 -0
  10. package/bin/deploy.js +104 -797
  11. package/bin/file.js +18 -1
  12. package/bin/vs.js +18 -3
  13. package/cli.md +367 -207
  14. package/conf.js +4 -0
  15. package/docker-compose.yml +1 -1
  16. package/manifests/deployment/dd-template-development/deployment.yaml +167 -0
  17. package/manifests/deployment/dd-template-development/proxy.yaml +46 -0
  18. package/manifests/deployment/tensorflow/tf-gpu-test.yaml +65 -0
  19. package/manifests/lxd/lxd-admin-profile.yaml +1 -0
  20. package/manifests/lxd/lxd-preseed.yaml +9 -37
  21. package/manifests/lxd/underpost-setup.sh +98 -81
  22. package/manifests/maas/device-scan.sh +43 -0
  23. package/manifests/maas/gpu-diag.sh +19 -0
  24. package/manifests/maas/lxd-preseed.yaml +32 -0
  25. package/manifests/maas/maas-setup.sh +120 -0
  26. package/manifests/maas/nat-iptables.sh +26 -0
  27. package/manifests/maas/snap-clean.sh +26 -0
  28. package/manifests/mariadb/statefulset.yaml +2 -1
  29. package/manifests/mariadb/storage-class.yaml +10 -0
  30. package/manifests/mongodb-4.4/service-deployment.yaml +2 -2
  31. package/manifests/valkey/service.yaml +3 -9
  32. package/manifests/valkey/statefulset.yaml +10 -12
  33. package/package.json +1 -1
  34. package/src/cli/baremetal.js +1280 -0
  35. package/src/cli/cloud-init.js +537 -0
  36. package/src/cli/cluster.js +506 -243
  37. package/src/cli/deploy.js +41 -3
  38. package/src/cli/env.js +2 -2
  39. package/src/cli/image.js +57 -9
  40. package/src/cli/index.js +271 -232
  41. package/src/cli/lxd.js +314 -81
  42. package/src/cli/repository.js +7 -4
  43. package/src/cli/run.js +262 -0
  44. package/src/cli/test.js +1 -1
  45. package/src/index.js +28 -1
  46. package/src/runtime/lampp/Dockerfile +41 -47
  47. package/src/server/conf.js +61 -0
  48. package/src/server/logger.js +3 -3
  49. package/src/server/process.js +16 -19
  50. package/src/server/runtime.js +1 -6
  51. package/src/server/ssl.js +1 -12
  52. package/src/server/valkey.js +3 -3
  53. package/supervisord-openssh-server.conf +0 -5
@@ -0,0 +1,537 @@
1
+ import dotenv from 'dotenv';
2
+ import { shellExec } from '../server/process.js';
3
+ import fs from 'fs-extra';
4
+ import UnderpostBaremetal from './baremetal.js';
5
+ import { loggerFactory } from '../server/logger.js';
6
+ import { getNpmRootPath } from '../server/conf.js';
7
+
8
+ dotenv.config();
9
+
10
+ const logger = loggerFactory(import.meta);
11
+
12
+ /**
13
+ * @class UnderpostCloudInit
14
+ * @description Manages the generation and deployment of cloud-init configuration files
15
+ * and associated scripts for baremetal provisioning. This class provides methods
16
+ * to build various shell scripts and a cloud-init configuration file tailored
17
+ * for MAAS (Metal as a Service) integration.
18
+ */
19
+ class UnderpostCloudInit {
20
+ static API = {
21
+ /**
22
+ * @method buildTools
23
+ * @description Builds and writes various shell scripts and configuration files
24
+ * to the NFS host path, which are then used by the target baremetal machine
25
+ * during the cloud-init process.
26
+ * @param {object} params - The parameters for building the tools.
27
+ * @param {string} params.workflowId - The identifier for the specific workflow configuration.
28
+ * @param {string} params.nfsHostPath - The base path on the NFS host where tools will be written.
29
+ * @param {string} params.hostname - The hostname of the target baremetal machine.
30
+ * @param {object} params.callbackMetaData - Metadata about the callback, used for dynamic configuration.
31
+ * @param {boolean} params.dev - Development mode flag.
32
+ * @returns {void}
33
+ */
34
+ buildTools({ workflowId, nfsHostPath, hostname, callbackMetaData, dev }) {
35
+ // Destructure workflow configuration for easier access.
36
+ const { systemProvisioning, chronyc, networkInterfaceName, debootstrap } =
37
+ UnderpostBaremetal.API.workflowsConfig[workflowId];
38
+ const { timezone, chronyConfPath } = chronyc;
39
+ // Define the specific directory for underpost tools within the NFS host path.
40
+ const nfsHostToolsPath = `${nfsHostPath}/underpost`;
41
+
42
+ // Determine the root path for npm and underpost based on development mode.
43
+ const npmRoot = getNpmRootPath();
44
+ const underpostRoot = dev === true ? '.' : `${npmRoot}/underpost`;
45
+
46
+ // Use a switch statement to handle different system provisioning types.
47
+ switch (systemProvisioning) {
48
+ case 'ubuntu': {
49
+ // Ensure the target directory for tools is clean and exists.
50
+ if (fs.existsSync(`${nfsHostToolsPath}`)) fs.removeSync(`${nfsHostToolsPath}`);
51
+ fs.mkdirSync(`${nfsHostToolsPath}`, { recursive: true });
52
+
53
+ // Build and write the date configuration script.
54
+ logger.info('Build', `${nfsHostToolsPath}/date.sh`);
55
+ fs.writeFileSync(
56
+ `${nfsHostToolsPath}/date.sh`,
57
+ UnderpostBaremetal.API.stepsRender(
58
+ UnderpostBaremetal.API.systemProvisioningFactory[systemProvisioning].timezone({
59
+ timezone,
60
+ chronyConfPath,
61
+ }),
62
+ false,
63
+ ),
64
+ 'utf8',
65
+ );
66
+
67
+ // Build and write the keyboard configuration script.
68
+ logger.info('Build', `${nfsHostToolsPath}/keyboard.sh`);
69
+ fs.writeFileSync(
70
+ `${nfsHostToolsPath}/keyboard.sh`,
71
+ UnderpostBaremetal.API.stepsRender(
72
+ UnderpostBaremetal.API.systemProvisioningFactory[systemProvisioning].keyboard(),
73
+ false,
74
+ ),
75
+ 'utf8',
76
+ );
77
+
78
+ // Build and write the hosts file configuration script.
79
+ logger.info('Build', `${nfsHostToolsPath}/host.sh`);
80
+ fs.writeFileSync(
81
+ `${nfsHostToolsPath}/host.sh`,
82
+ `echo -e "127.0.0.1 localhost\n127.0.1.1 ${hostname}" | tee -a /etc/hosts`,
83
+ 'utf8',
84
+ );
85
+
86
+ // Build and write the DNS configuration script.
87
+ logger.info('Build', `${nfsHostToolsPath}/dns.sh`);
88
+ fs.writeFileSync(
89
+ `${nfsHostToolsPath}/dns.sh`,
90
+ `rm /etc/resolv.conf
91
+ echo 'nameserver 8.8.8.8' > /run/systemd/resolve/stub-resolv.conf
92
+ ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf`,
93
+ 'utf8',
94
+ );
95
+
96
+ // Build and write the main startup script for cloud-init.
97
+ logger.info('Build', `${nfsHostToolsPath}/start.sh`);
98
+ fs.writeFileSync(
99
+ `${nfsHostToolsPath}/start.sh`,
100
+ `#!/bin/bash
101
+ set -x
102
+ # sudo cloud-init --all-stages
103
+ ${UnderpostBaremetal.API.stepsRender(
104
+ [
105
+ `/underpost/date.sh`,
106
+ `sleep 3`,
107
+ `/underpost/reset.sh`,
108
+ `sleep 3`,
109
+ `cloud-init init --local`,
110
+ `sleep 3`,
111
+ `cloud-init init`,
112
+ `sleep 3`,
113
+ `cloud-init modules --mode=config`,
114
+ `sleep 3`,
115
+ `cloud-init modules --mode=final`,
116
+ `sleep 3`,
117
+ `/underpost/enlistment.sh`,
118
+ ],
119
+ false,
120
+ )}`,
121
+ 'utf8',
122
+ );
123
+
124
+ // Build and write the cloud-init reset script.
125
+ logger.info('Build', `${nfsHostToolsPath}/reset.sh`);
126
+ fs.writeFileSync(
127
+ `${nfsHostToolsPath}/reset.sh`,
128
+ `sudo cloud-init clean --seed --configs all --machine-id # --logs
129
+ sudo rm -rf /var/lib/cloud/*
130
+ echo '' > /var/log/cloud-init.log
131
+ echo '' > /var/log/cloud-init-output.log`,
132
+ 'utf8',
133
+ );
134
+
135
+ // Build and write the cloud-init help script.
136
+ logger.info('Build', `${nfsHostToolsPath}/help.sh`);
137
+ fs.writeFileSync(
138
+ `${nfsHostToolsPath}/help.sh`,
139
+ `echo "=== Cloud init utils ==="
140
+ echo "sudo cloud-init --all-stages"
141
+ echo "sudo cloud-init clean --logs --seed --configs all --machine-id --reboot"
142
+ echo "sudo cloud-init init --local"
143
+ echo "sudo cloud-init init"
144
+ echo "sudo cloud-init modules --mode=config"
145
+ echo "sudo cloud-init modules --mode=final"`,
146
+ 'utf8',
147
+ );
148
+
149
+ // Build and write the test script for verifying configuration.
150
+ logger.info('Build', `${nfsHostToolsPath}/test.sh`);
151
+ fs.writeFileSync(
152
+ `${nfsHostToolsPath}/test.sh`,
153
+ `echo -e "\n=== Current date/time ==="
154
+ date '+%Y-%m-%d %H:%M:%S'
155
+ echo -e "\n=== Keyboard layout ==="
156
+ cat /etc/default/keyboard
157
+ echo -e "\n=== Registered users ==="
158
+ cut -d: -f1 /etc/passwd`,
159
+ 'utf8',
160
+ );
161
+
162
+ // Build and write the shutdown script.
163
+ logger.info('Build', `${nfsHostToolsPath}/shutdown.sh`);
164
+ fs.writeFileSync(`${nfsHostToolsPath}/shutdown.sh`, `sudo shutdown -h now`, 'utf8');
165
+
166
+ // Build and write the MAC address retrieval script.
167
+ logger.info('Build', `${nfsHostToolsPath}/mac.sh`);
168
+ fs.writeFileSync(
169
+ `${nfsHostToolsPath}/mac.sh`,
170
+ `echo "$(cat /sys/class/net/${networkInterfaceName}/address)" > /underpost/mac`,
171
+ 'utf8',
172
+ );
173
+
174
+ // Copy the device scan script from manifests.
175
+ logger.info('Build', `${nfsHostToolsPath}/device_scan.sh`);
176
+ fs.copySync(`${underpostRoot}/manifests/maas/device-scan.sh`, `${nfsHostToolsPath}/device_scan.sh`);
177
+
178
+ // Build and write the config path script.
179
+ logger.info('Build', `${nfsHostToolsPath}/config-path.sh`);
180
+ fs.writeFileSync(`${nfsHostToolsPath}/config-path.sh`, `echo "/etc/cloud/cloud.cfg.d/90_maas.cfg"`, 'utf8');
181
+
182
+ // Build and write the MAAS enlistment script.
183
+ logger.info('Build', `${nfsHostToolsPath}/enlistment.sh`);
184
+ fs.writeFileSync(
185
+ `${nfsHostToolsPath}/enlistment.sh`,
186
+ `#!/bin/bash
187
+ set -x
188
+
189
+ # ------------------------------------------------------------
190
+ # Step: Commission a machine in MAAS using OAuth1 authentication
191
+ # ------------------------------------------------------------
192
+
193
+ MACHINE_ID=$(cat /underpost/system-id)
194
+ CONSUMER_KEY=$(cat /underpost/consumer-key)
195
+ TOKEN_KEY=$(cat /underpost/token-key)
196
+ TOKEN_SECRET=$(cat /underpost/token-secret)
197
+
198
+ echo ">>> Starting MAAS machine commissioning for system_id: $MACHINE_ID …"
199
+
200
+ curl -X POST \\
201
+ --fail --location --verbose --include --raw --trace-ascii /dev/stdout\\
202
+ --header "Authorization:\\
203
+ OAuth oauth_version=1.0,\\
204
+ oauth_signature_method=PLAINTEXT,\\
205
+ oauth_consumer_key=$CONSUMER_KEY,\\
206
+ oauth_token=$TOKEN_KEY,\\
207
+ oauth_signature=&$TOKEN_SECRET,\\
208
+ oauth_nonce=$(uuidgen),\\
209
+ oauth_timestamp=$(date +%s)"\\
210
+ -F "commissioning_scripts=20-maas-01-install-lldpd"\\
211
+ -F "enable_ssh=1"\\
212
+ -F "skip_bmc_config=1"\\
213
+ -F "skip_networking=1"\\
214
+ -F "skip_storage=1"\\
215
+ -F "testing_scripts=none"\\
216
+ http://${callbackMetaData.runnerHost.ip}:5240/MAAS/api/2.0/machines/$MACHINE_ID/op-commission \\
217
+ 2>&1 | tee /underpost/enlistment.log || echo "ERROR: MAAS commissioning returned code $?"`,
218
+ 'utf8',
219
+ );
220
+
221
+ // Import SSH keys for root user.
222
+ logger.info('Import ssh keys');
223
+ shellExec(`sudo rm -rf ${nfsHostPath}/root/.ssh`);
224
+ shellExec(`sudo rm -rf ${nfsHostPath}/home/root/.ssh`); // Ensure home root .ssh is also clean.
225
+ logger.info('Copy', `/root/.ssh -> ${nfsHostPath}/root/.ssh`);
226
+ fs.copySync(`/root/.ssh`, `${nfsHostPath}/root/.ssh`);
227
+
228
+ // Enable execution permissions for all generated scripts and run a test.
229
+ logger.info('Enable tools execution and test');
230
+ UnderpostBaremetal.API.crossArchRunner({
231
+ nfsHostPath,
232
+ debootstrapArch: debootstrap.image.architecture,
233
+ callbackMetaData,
234
+ steps: [
235
+ `chmod +x /underpost/date.sh`,
236
+ `chmod +x /underpost/keyboard.sh`,
237
+ `chmod +x /underpost/dns.sh`,
238
+ `chmod +x /underpost/help.sh`,
239
+ `chmod +x /underpost/config-path.sh`,
240
+ `chmod +x /underpost/host.sh`,
241
+ `chmod +x /underpost/test.sh`,
242
+ `chmod +x /underpost/start.sh`,
243
+ `chmod +x /underpost/reset.sh`,
244
+ `chmod +x /underpost/shutdown.sh`,
245
+ `chmod +x /underpost/device_scan.sh`,
246
+ `chmod +x /underpost/mac.sh`,
247
+ `chmod +x /underpost/enlistment.sh`,
248
+ `sudo chmod 700 ~/.ssh/`, // Set secure permissions for .ssh directory.
249
+ `sudo chmod 600 ~/.ssh/authorized_keys`, // Set secure permissions for authorized_keys.
250
+ `sudo chmod 644 ~/.ssh/known_hosts`, // Set permissions for known_hosts.
251
+ `sudo chmod 600 ~/.ssh/id_rsa`, // Set secure permissions for private key.
252
+ `sudo chmod 600 /etc/ssh/ssh_host_ed25519_key`, // Set secure permissions for host key.
253
+ `chown -R root:root ~/.ssh`, // Ensure root owns the .ssh directory.
254
+ `/underpost/test.sh`, // Run the test script to verify setup.
255
+ ],
256
+ });
257
+
258
+ break;
259
+ }
260
+ default:
261
+ // Throw an error if an unsupported system provisioning type is provided.
262
+ throw new Error('Invalid system provisioning: ' + systemProvisioning);
263
+ }
264
+ },
265
+
266
+ /**
267
+ * @method configFactory
268
+ * @description Generates the cloud-init configuration file (`90_maas.cfg`)
269
+ * for MAAS integration. This configuration includes hostname, network settings,
270
+ * user accounts, SSH keys, timezone, NTP, and various cloud-init modules.
271
+ * @param {object} params - The parameters for generating the configuration.
272
+ * @param {string} params.controlServerIp - The IP address of the MAAS control server.
273
+ * @param {string} params.hostname - The hostname of the target baremetal machine.
274
+ * @param {string} params.commissioningDeviceIp - The IP address to assign to the commissioning device.
275
+ * @param {string} params.gatewayip - The gateway IP address for the network.
276
+ * @param {boolean} params.auth - Flag indicating whether to include MAAS authentication credentials.
277
+ * @param {string} params.mac - The MAC address of the network interface.
278
+ * @param {string} params.timezone - The timezone to set for the machine.
279
+ * @param {string} params.chronyConfPath - The path to the Chrony configuration file.
280
+ * @param {string} params.networkInterfaceName - The name of the primary network interface.
281
+ * @param {object} [authCredentials={}] - Optional MAAS authentication credentials.
282
+ * @param {string} [path='/etc/cloud/cloud.cfg.d/90_maas.cfg'] - The target path for the cloud-init configuration file.
283
+ * @returns {string} The generated cloud-init configuration content.
284
+ */
285
+ configFactory(
286
+ {
287
+ controlServerIp,
288
+ hostname,
289
+ commissioningDeviceIp,
290
+ gatewayip,
291
+ auth,
292
+ mac,
293
+ timezone,
294
+ chronyConfPath,
295
+ networkInterfaceName,
296
+ },
297
+ authCredentials = { consumer_key: '', consumer_secret: '', token_key: '', token_secret: '' },
298
+ path = '/etc/cloud/cloud.cfg.d/90_maas.cfg',
299
+ ) {
300
+ const { consumer_key, consumer_secret, token_key, token_secret } = authCredentials;
301
+ // Configure cloud-init for MAAS using a heredoc string.
302
+ return `cat <<EOF_MAAS_CFG > ${path}
303
+ #cloud-config
304
+
305
+ hostname: ${hostname}
306
+ fqdn: ${hostname}.maas
307
+ # prefer_fqdn_over_hostname: true
308
+ # metadata_url: http://${controlServerIp}:5240/MAAS/metadata
309
+ # metadata_url: http://${controlServerIp}:5248/MAAS/metadata
310
+
311
+ # Check:
312
+ # /MAAS/metadata/latest/enlist-preseed/?op=get_enlist_preseed
313
+
314
+ # Debug:
315
+ # https://maas.io/docs/how-to-use-logging
316
+
317
+ datasource_list: [ MAAS ]
318
+ datasource:
319
+ MAAS:
320
+ metadata_url: http://${controlServerIp}:5240/MAAS/metadata/
321
+ ${
322
+ // Conditionally include authentication details if 'auth' flag is true.
323
+ !auth
324
+ ? ''
325
+ : `consumer_key: ${consumer_key}
326
+ consumer_secret: ${consumer_secret}
327
+ token_key: ${token_key}
328
+ token_secret: ${token_secret}`
329
+ }
330
+
331
+
332
+ users:
333
+ - name: ${process.env.MAAS_ADMIN_USERNAME}
334
+ sudo: ["ALL=(ALL) NOPASSWD:ALL"]
335
+ shell: /bin/bash
336
+ lock_passwd: false
337
+ groups: sudo,users,admin,wheel,lxd
338
+ plain_text_passwd: '${process.env.MAAS_ADMIN_USERNAME}'
339
+ ssh_authorized_keys:
340
+ - ${fs.readFileSync(`/home/dd/engine/engine-private/deploy/id_rsa.pub`, 'utf8')}
341
+
342
+ # manage_resolv_conf: true
343
+ # resolv_conf:
344
+ # nameservers: [8.8.8.8]
345
+
346
+ # keyboard:
347
+ # layout: es
348
+
349
+ # check timedatectl on hostname
350
+ # timezone: America/Santiago
351
+ timezone: ${timezone}
352
+
353
+ ntp:
354
+ enabled: true
355
+ servers:
356
+ - ${process.env.MAAS_NTP_SERVER}
357
+ ntp_client: chrony
358
+ config:
359
+ confpath: ${chronyConfPath}
360
+
361
+ # ssh:
362
+ # allow-pw: false
363
+ # install-server: true
364
+
365
+ # ssh_pwauth: false
366
+
367
+ package_update: true
368
+ package_upgrade: true
369
+ packages:
370
+ - git
371
+ - htop
372
+ - snapd
373
+ - chrony
374
+ - lldpd
375
+ - lshw
376
+
377
+ resize_rootfs: false
378
+ growpart:
379
+ mode: "off"
380
+ network:
381
+ version: 2
382
+ ethernets:
383
+ ${networkInterfaceName}:
384
+ match:
385
+ macaddress: "${mac}"
386
+ mtu: 1500
387
+ set-name: ${networkInterfaceName}
388
+ dhcp4: false
389
+ addresses:
390
+ - ${commissioningDeviceIp}/24
391
+ routes:
392
+ - to: default
393
+ via: ${gatewayip}
394
+ # gateway4: ${gatewayip}
395
+ nameservers:
396
+ addresses:
397
+ - ${process.env.MAAS_DNS}
398
+
399
+ final_message: "====== Cloud init finished ======"
400
+
401
+ # power_state:
402
+ # mode: reboot
403
+ # message: Rebooting after initial setup
404
+ # timeout: 30
405
+ # condition: True
406
+
407
+ bootcmd:
408
+ - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
409
+ - echo "Init bootcmd"
410
+ - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
411
+ ${UnderpostBaremetal.API.stepsRender(
412
+ [`/underpost/dns.sh`, `/underpost/host.sh`, `/underpost/mac.sh`, `cat /underpost/mac`],
413
+ true,
414
+ )}
415
+ runcmd:
416
+ - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
417
+ - echo "Init runcmd"
418
+ - echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
419
+
420
+ # If this is set, 'root' will not be able to ssh in and they
421
+ # will get a message to login instead as the default $user
422
+ disable_root: true
423
+
424
+ # This will cause the set+update hostname module to not operate (if true)
425
+ preserve_hostname: false
426
+
427
+ # The modules that run in the 'init' stage
428
+ cloud_init_modules:
429
+ - migrator
430
+ - seed_random
431
+ - bootcmd
432
+ - write-files
433
+ - growpart
434
+ - resizefs
435
+ - set_hostname
436
+ - update_hostname
437
+ - update_etc_hosts
438
+ - ca-certs
439
+ - rsyslog
440
+ - users-groups
441
+ - ssh
442
+
443
+ # The modules that run in the 'config' stage
444
+ cloud_config_modules:
445
+ # Emit the cloud config ready event
446
+ # this can be used by upstart jobs for 'start on cloud-config'.
447
+ - emit_upstart
448
+ - disk_setup
449
+ - mounts
450
+ - ssh-import-id
451
+ - locale
452
+ - set-passwords
453
+ - grub-dpkg
454
+ - apt-pipelining
455
+ - apt-configure
456
+ - package-update-upgrade-install
457
+ - landscape
458
+ - timezone
459
+ - puppet
460
+ - chef
461
+ - salt-minion
462
+ - mcollective
463
+ - disable-ec2-metadata
464
+ - runcmd
465
+ - byobu
466
+ - ssh-import-id
467
+ - ntp
468
+
469
+
470
+ # phone_home:
471
+ # url: "http://${controlServerIp}:5240/MAAS/metadata/v1/?op=phone_home"
472
+ # post: all
473
+ # tries: 3
474
+
475
+ # The modules that run in the 'final' stage
476
+ cloud_final_modules:
477
+ - rightscale_userdata
478
+ - scripts-vendor
479
+ - scripts-per-once
480
+ - scripts-per-boot
481
+ # - scripts-per-instance
482
+ - scripts-user
483
+ - ssh-authkey-fingerprints
484
+ - keys-to-console
485
+ # - phone-home
486
+ - final-message
487
+ - power-state-change
488
+ EOF_MAAS_CFG`;
489
+ },
490
+
491
+ /**
492
+ * @method authCredentialsFactory
493
+ * @description Retrieves MAAS API key credentials from the MAAS CLI.
494
+ * This method parses the output of `maas apikey` to extract the consumer key,
495
+ * consumer secret, token key, and token secret.
496
+ * @returns {object} An object containing the MAAS authentication credentials.
497
+ * @throws {Error} If the MAAS API key format is invalid.
498
+ */
499
+ authCredentialsFactory() {
500
+ // Expected formats:
501
+ // <consumer_key>:<consumer_token>:<secret> (older format)
502
+ // <consumer_key>:<consumer_secret>:<token_key>:<token_secret> (newer format)
503
+ // Commands used to generate API keys:
504
+ // maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}
505
+ // maas ${process.env.MAAS_ADMIN_USERNAME} account create-authorisation-token
506
+ // maas apikey --generate --username ${process.env.MAAS_ADMIN_USERNAME}
507
+ // Reference: https://github.com/CanonicalLtd/maas-docs/issues/647
508
+
509
+ const parts = shellExec(`maas apikey --with-names --username ${process.env.MAAS_ADMIN_USERNAME}`, {
510
+ stdout: true,
511
+ })
512
+ .trim()
513
+ .split(`\n`)[0] // Take only the first line of output.
514
+ .split(':'); // Split by colon to get individual parts.
515
+
516
+ let consumer_key, consumer_secret, token_key, token_secret;
517
+
518
+ // Determine the format of the API key and assign parts accordingly.
519
+ if (parts.length === 4) {
520
+ [consumer_key, consumer_secret, token_key, token_secret] = parts;
521
+ } else if (parts.length === 3) {
522
+ // Handle older 3-part format, setting consumer_secret as empty.
523
+ [consumer_key, token_key, token_secret] = parts;
524
+ consumer_secret = '""';
525
+ token_secret = token_secret.split(' MAAS consumer')[0].trim(); // Clean up token secret.
526
+ } else {
527
+ // Throw an error if the format is not recognized.
528
+ throw new Error('Invalid token format');
529
+ }
530
+
531
+ logger.info('Maas api token generated', { consumer_key, consumer_secret, token_key, token_secret });
532
+ return { consumer_key, consumer_secret, token_key, token_secret };
533
+ },
534
+ };
535
+ }
536
+
537
+ export default UnderpostCloudInit;