UNPKG

@kui-shell/plugin-kubectl

Version:

Kubernetes visualization plugin for kubernetes

274 lines (273 loc) 9.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.NoPrepare = void 0; exports.doExecWithPty = doExecWithPty; exports.doExecWithRadioTable = doExecWithRadioTable; exports.doExecWithStatus = void 0; exports.doExecWithStdout = doExecWithStdout; exports.doExecWithStdoutViaPty = doExecWithStdoutViaPty; exports.doExecWithTable = doExecWithTable; exports.doExecWithoutPty = doExecWithoutPty; exports.exec = exec; exports.reallyNeedsPty = reallyNeedsPty; var _core = require("@kui-shell/core"); var _formatTable = require("../../lib/view/formatTable"); /* * Copyright 2019 The Kubernetes Authors * * 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. */ var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const strings = (0, _core.i18n)('plugin-kubectl'); /** No-op argument preparation */ const NoPrepare = args => args.command; /** Standard status preparation */ exports.NoPrepare = NoPrepare; function DefaultPrepareForStatus(verb, args) { return args.argvNoOptions.slice(args.argvNoOptions.indexOf(verb) + 1); } /** * Execute the given command in the browser; this dispatches to * _kubectl, which runs on the proxy (for electron and headless, these * are the same machine). * */ function doExecWithoutPty(args, prepare = NoPrepare, exec = 'kubectl') { return __awaiter(this, void 0, void 0, function* () { const raw = `_${exec}$1`; const command = prepare(args).replace(new RegExp(`^${exec}(\\s)?`), raw).replace(/^k(\s)?/, raw); const dbl = new RegExp(`_${exec}(\\s)?`); const doubleCheck = dbl.test(command) ? command : `_${exec} ${command}`; return args.REPL.qexec(doubleCheck, undefined, undefined, args.execOptions).catch(err => { if (err.code === 500 || err.statusCode === 500) { err.code = err.statusCode = 500; } throw err; }); }); } /** * Behaves as does `exec`, except that it projects out just the * `stdout` part -- thus ignoring the exit `code` and `stderr`. * */ function doExecWithStdout(args, prepare = NoPrepare, exec) { return doExecWithoutPty(args, prepare, exec).then(_ => _.content.stdout); } /** * Do we see anything in the given command that indicates a PTY is * required? That is, versus a plain nodejs spawn/exec. * */ function reallyNeedsPty({ argvNoOptions, parsedOptions }) { const test1 = _ => /(\$\(|`)/.test(_); // subprocess execution? const test2 = _ => /^\s*(\|\||\||>|>>)\s*$/.test(_) || test1(_); return !!argvNoOptions.find(test2) || !!Object.values(parsedOptions).find(test1); } /** * Execute the given command using a pty * */ function doExecWithPty(args, prepare = NoPrepare, exec) { return __awaiter(this, void 0, void 0, function* () { if (!reallyNeedsPty(args) && (_core.Capabilities.isHeadless() || !_core.Capabilities.inBrowser() && args.execOptions.raw)) { return doExecWithStdout(args, prepare, exec); } else { // // For commands `kubectl (--help/-h)` and `k (--help/-h)`, render usage model; // Otherwise, execute the given command using a pty // const commandToPTY = args.command.replace(/^k(\s)/, 'kubectl$1'); if (args.execOptions.onInit) { args.execOptions.quiet = true; // don't ever emit anything on your own args.execOptions.replSilence = true; // repl: same thing args.execOptions.echo = false; // do not even echo "ok" } // let pty handle redirection args.execOptions.noCoreRedirect = true; return args.REPL.qexec(`sendtopty ${commandToPTY}`, args.block, undefined, args.execOptions.onInit ? args.execOptions : Object.assign({}, args.execOptions, { rawResponse: true, quiet: args.execOptions.quiet === undefined ? args.execOptions.type === _core.ExecType.TopLevel ? false : undefined : args.execOptions.quiet })).catch(err => { if (err.code === 500 || err.statusCode === 500) { err.code = err.statusCode = 500; } throw err; }); } }); } /** * Execute the given command using a pty, but return a string * */ function doExecWithStdoutViaPty(args, prepare = NoPrepare) { return __awaiter(this, void 0, void 0, function* () { // eslint-disable-next-line no-async-promise-executor return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { let stdout = ''; // a bit of plumbing: tell the PTY that we will be handling everything const myExecOptions = Object.assign({}, args.execOptions, { rethrowErrors: true, quiet: true, replSilence: true, echo: false, // the PTY will call this when the PTY process is ready; in // return, we send it back a consumer of streaming output onInit: () => { return chunk => { if (typeof chunk === 'string') { stdout += chunk; } }; } }); const myArgs = Object.assign({}, args, { execOptions: myExecOptions }); yield doExecWithPty(myArgs, prepare).catch(err => { console.error(err); reject(err); }); resolve(stdout); })); }); } /** * Decide whether to use a pty or a raw exec. * */ function exec(args, prepare = NoPrepare, exec = 'kubectl') { return __awaiter(this, void 0, void 0, function* () { if (reallyNeedsPty(args)) { return Promise.resolve({ content: { code: 0, stdout: yield doExecWithPty(args, prepare), stderr: undefined, wasSentToPty: true } }); } else { return doExecWithoutPty(args, prepare, exec); } }); } /** * Behaves as does `exec`, except that it projects out just the * `stdout` part and parses it into a Table model. * */ function doExecWithTable(args, prepare = NoPrepare, command = 'kubectl', { usePty = false, nameColumn, verb, entityType } = {}) { return __awaiter(this, void 0, void 0, function* () { const response = usePty ? { content: { stdout: yield doExecWithStdoutViaPty(args, prepare), stderr: undefined } } : yield doExecWithoutPty(args, prepare, command); const table = yield (0, _formatTable.stringToTable)(response.content.stdout, response.content.stderr, args, command, verb, entityType, nameColumn); if (typeof table === 'string') { throw new Error(strings('Unable to parse table')); } else { return table; } }); } /** * Execute a command, and then execute the status command which will * poll until the given FinalState is reached. * */ const doExecWithStatus = (verb, finalState, command = 'kubectl', prepareForExec = NoPrepare, prepareForStatus = DefaultPrepareForStatus) => args => __awaiter(void 0, void 0, void 0, function* () { const response = yield exec(args, prepareForExec, command); if (response.content.code !== 0) { const err = new Error(response.content.stderr); err.code = response.content.code; throw err; } else if (_core.Capabilities.isHeadless()) { return response.content.stdout; } else { const statusArgs = yield prepareForStatus(verb, args); const initialResponse = response ? response.content.stdout : ''; const { doStatus } = yield Promise.resolve().then(() => require('./status')); return doStatus(args, verb, command, initialResponse, finalState, statusArgs); } }); exports.doExecWithStatus = doExecWithStatus; function doExecWithRadioTable(resources, defaultSelectedIdx, onSelect, { title = resources.length === 0 ? undefined : resources[0].kind, nameColumnTitle = 'NAME' } = {}) { return __awaiter(this, void 0, void 0, function* () { if (resources.length > 0) { return { apiVersion: 'kui-shell/v1', kind: 'RadioTable', title, defaultSelectedIdx, header: { cells: [nameColumnTitle] }, body: resources.map(resource => { const name = resource.metadata.name; return { nameIdx: 0, cells: [name], onSelect: onSelect(name, resource) }; }) }; } }); } var _default = exec; exports.default = _default;