/** * @license * Copyright 2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Command from '@oclif/command'; import { InsufficientPrivilegesError, NotAvailableInOfflineModeError, } from './errors'; import { stripIndent } from './utils/lazy'; import * as output from './framework/output'; export default abstract class BalenaCommand extends Command { /** * When set to true, command will be listed in `help`, * otherwise listed in `help --verbose` with secondary commands. */ public static primary = false; /** * Require elevated privileges to run. * When set to true, command will exit with an error * if executed without root on Mac/Linux * or if executed by non-Administrator on Windows. */ public static root = false; /** * Require authentication to run. * When set to true, command will exit with an error * if user is not already logged in. */ public static authenticated = false; /** * Require an internet connection to run. * When set to true, command will exit with an error * if user is running in offline mode (BALENARC_OFFLINE_MODE). */ public static offlineCompatible = false; /** * Accept piped input. * When set to true, command will read from stdin during init * and make contents available on member `stdin`. */ public static readStdin = false; public stdin: string; /** * Throw InsufficientPrivilegesError if not root on Mac/Linux * or non-Administrator on Windows. * * Called automatically if `root=true`. * Can be called explicitly by command implementation, if e.g.: * - check should only be done conditionally * - other code needs to execute before check */ protected static async checkElevatedPrivileges() { const isElevated = await (await import('is-elevated'))(); if (!isElevated) { throw new InsufficientPrivilegesError( 'You need root/admin privileges to run this command', ); } } /** * Throw NotLoggedInError if not logged in. * * Called automatically if `authenticated=true`. * Can be called explicitly by command implementation, if e.g.: * - check should only be done conditionally * - other code needs to execute before check * * Note, currently public to allow use outside of derived commands * (as some command implementations require this. Can be made protected * if this changes). * * @throws {NotLoggedInError} */ public static async checkLoggedIn() { await (await import('./utils/patterns')).checkLoggedIn(); } /** * Throw NotLoggedInError if not logged in when condition true. * * @param {boolean} doCheck - will check if true. * @throws {NotLoggedInError} */ public static async checkLoggedInIf(doCheck: boolean) { if (doCheck) { await this.checkLoggedIn(); } } /** * Throw NotAvailableInOfflineModeError if in offline mode. * * Called automatically if `onlineOnly=true`. * Can be called explicitly by command implementation, if e.g.: * - check should only be done conditionally * - other code needs to execute before check * * Note, currently public to allow use outside of derived commands * (as some command implementations require this. Can be made protected * if this changes). * * @throws {NotAvailableInOfflineModeError} */ public static checkNotUsingOfflineMode() { if (process.env.BALENARC_OFFLINE_MODE) { throw new NotAvailableInOfflineModeError(stripIndent` This command requires an internet connection, and cannot be used in offline mode. To leave offline mode, unset the BALENARC_OFFLINE_MODE environment variable. `); } } /** * Read stdin contents and make available to command. * * This approach could be improved in the future to automatically set argument * values from stdin based in configuration, minimising command implementation. */ protected async getStdin() { this.stdin = await (await import('get-stdin'))(); } /** * Get a logger instance. */ protected static async getLogger() { return (await import('./utils/logger')).getLogger(); } protected async init() { const ctr = this.constructor as typeof BalenaCommand; if (ctr.root) { await BalenaCommand.checkElevatedPrivileges(); } if (ctr.authenticated) { await BalenaCommand.checkLoggedIn(); } if (!ctr.offlineCompatible) { BalenaCommand.checkNotUsingOfflineMode(); } if (ctr.readStdin) { await this.getStdin(); } } protected outputMessage = output.outputMessage; protected outputData = output.outputData; }