UNPKG

@kui-shell/plugin-kubectl

Version:

Kubernetes visualization plugin for kubernetes

279 lines (278 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = registerForKubectl; exports.doEdit = doEdit; exports.editFlags = void 0; exports.editSpec = editSpec; exports.editable = editable; exports.formatToolbarText = formatToolbarText; exports.isEditable = isEditable; exports.register = register; exports.viewTransformer = viewTransformer; var _core = require("@kui-shell/core"); var _pluginKubectlCore = require("@kui-shell/plugin-kubectl-core"); var _flags = _interopRequireDefault(require("./flags")); var _options = require("./options"); var _yaml = require("../../lib/view/modes/yaml"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* * Copyright 2020 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'); const strings2 = (0, _core.i18n)('plugin-client-common', 'editor'); function isEditable(resource) { const editable = resource; const editableMode = editable.modes.find(mode => (0, _core.isStringWithOptionalContentType)(mode) && mode.spec); return editableMode && (0, _core.isStringWithOptionalContentType)(editableMode) && typeof editableMode.spec === 'object' && typeof editableMode.spec.readOnly === 'boolean' && typeof editableMode.spec.clearable === 'boolean' && typeof editableMode.spec.save === 'object' && typeof editableMode.spec.revert === 'object'; } function saveError(err) { return err; } /** * Reformat the apply error. * * @param tmp the temporary file we used to stage the apply -f ${tmp} * @param data the raw data we attempted to apply * */ function reportErrorToUser(tmp, data, err) { console.error('error in apply for edit', err); // was this a validation error? const msg = err.message.match(/ValidationError\(.*\): ([^]+) in/); if (msg && msg.length === 2) { const unknownField = err.message.match(/unknown field "(.*)"/); const error = saveError(new Error(msg[1])); if (unknownField) { const regexp = new RegExp(`${unknownField[1]}:`); const lineNumber = data.split(/\n/).findIndex(line => regexp.test(line)); if (lineNumber >= 0) { // monaco indexes from 1 error.revealLine = lineNumber + 1; } } throw error; } else { // maybe this was a syntax error? const msg = err.message.match(/error parsing.*(line .*)/); if (msg && msg.length === 2) { const hasLineNumber = err.message.match(/line (\d+):/); const error = saveError(new Error(msg[1])); if (hasLineNumber) { // not sure why, but this line number is off by + 1 error.revealLine = parseInt(hasLineNumber[1]) - 1; } throw error; } else { // maybe this was a conflict error if (err.message.indexOf('Error from server (Conflict)') !== -1) { const errorForFile = `for: "${tmp}":`; const forFile = err.message.indexOf(errorForFile); const messageForFile = err.message.substring(forFile).replace(errorForFile, ''); throw saveError(new Error(messageForFile)); } // hmm, some other random error const msg = err.message.replace(tmp, ''); const newLines = msg.split('\n'); if (newLines[0].charAt(newLines[0].length - 2) === ':') { throw saveError(new Error(newLines.slice(0, 2).join('\n'))); } else { throw saveError(new Error(newLines[0])); } } } } function editSpec(cmd, namespace, args, resource, applySubCommand = '') { return { readOnly: false, clearable: false, save: { label: strings('Apply Changes'), onSave: data => __awaiter(this, void 0, void 0, function* () { const tmp = (yield args.REPL.rexec(`fwriteTemp`, { data })).content; const argv = [cmd === 'k' ? 'kubectl' : cmd, 'apply', applySubCommand, '-n', namespace, '-f', tmp].filter(x => x); const applyArgs = Object.assign({}, args, { command: argv.join(' '), argv, argvNoOptions: [cmd, 'apply', applySubCommand].filter(x => x), parsedOptions: { n: namespace, f: tmp } }); // execute the apply command, making sure to report any // validation or parse errors to the user const { doExecWithStdout } = yield Promise.resolve().then(() => require('./exec')); yield doExecWithStdout(applyArgs, undefined, cmd).catch(reportErrorToUser.bind(undefined, tmp, data)); return { // to show the updated resource after apply, // we re-execute the original edit command after applying the changes. command: args.command, // disable editor's auto toolbar update, // since this command will handle the toolbarText by itself noToolbarUpdate: true }; }) }, revert: { onRevert: () => resource.kuiRawData } }; } function editMode(spec, resource, mode = 'edit', label = strings2('Edit'), order = undefined, priority = undefined) { return { mode, label, order, priority, spec, contentType: 'yaml', content: resource.kuiRawData }; } function doEdit(cmd, args) { return __awaiter(this, void 0, void 0, function* () { const { isUsage } = yield Promise.resolve().then(() => require('../../lib/util/help')); if (isUsage(args)) { // special case: get --help/-h const { doHelp } = yield Promise.resolve().then(() => require('../../lib/util/help')); return doHelp(cmd, args); } const idx = args.argvNoOptions.indexOf('edit'); const kindForQuery = args.argvNoOptions[idx + 1] || ''; const nameForQuery = args.argvNoOptions[idx + 2] || ''; const ns = yield (0, _options.getNamespace)(args); const getCommand = `${cmd} get ${kindForQuery} ${nameForQuery} -n ${ns} -o yaml`; const resource = yield args.REPL.qexec(getCommand); // isKubeItems: did the user ask to edit a collection of resources? const kind = (0, _pluginKubectlCore.isKubeItems)(resource) ? resource.items[0].kind : resource.kind; const metadata = (0, _pluginKubectlCore.isKubeItems)(resource) ? resource.items[0].metadata : resource.metadata; const name = !(0, _pluginKubectlCore.isKubeItems)(resource) || resource.items.length === 1 ? metadata.name : strings('nItems', resource.items.length); const namespace = metadata.namespace; const spec = editSpec(cmd, namespace, args, resource); if ((0, _pluginKubectlCore.isKubeItems)(resource)) { const response = { apiVersion: 'kui-shell/v1', kind, metadata: { name, namespace }, spec, modes: [editMode(spec, resource)] }; return response; } else { // the viewTransformer below will create the View out of this Model return resource; } }); } function isEditAfterApply(options) { const opts = options; return opts && opts.data && opts.data.partOfApply !== undefined; } function formatToolbarText(args, response) { const baseEditToolbar = { type: 'info', text: strings2('isUpToDate') }; return !isEditAfterApply(args.execOptions) ? response.toolbarText : Object.assign(baseEditToolbar, { alerts: [{ type: 'success', title: strings('Successfully Applied') }] }); } /** * Variant of `resource` enhanced with an `Editable` impl that saves * via kubectl apply. * */ function editable(cmd, args, response) { return __awaiter(this, void 0, void 0, function* () { const spec = editSpec(cmd, response.metadata.namespace, args, response); const { doGetAsMMR: getView } = yield Promise.resolve().then(() => require('./get')); const baseView = yield getView(args, response); const view = Object.assign({}, baseView, { modes: [editMode(spec, response, _yaml.mode, _yaml.label, _yaml.order, 100)], toolbarText: formatToolbarText(args, response) }); return view; }); } /** Variant of `resource` that shows the given `defaultMode` upon open */ function showingMode(defaultMode, resource) { return Object.assign(resource, { defaultMode }); } /** KubeResource -> MultiModalResponse view transformer */ function viewTransformer(cmd, args, response) { return __awaiter(this, void 0, void 0, function* () { if (!(0, _pluginKubectlCore.isKubeItems)(response) && (0, _pluginKubectlCore.isKubeResource)(response)) { return showingMode(_yaml.mode, yield editable(cmd, args, response)); } }); } const editFlags = cmd => Object.assign({}, _flags.default, { viewTransformer: viewTransformer.bind(undefined, cmd) }); exports.editFlags = editFlags; function register(registrar, cmd) { registrar.listen(`/${cmd}/edit`, doEdit.bind(undefined, cmd), editFlags(cmd)); } function registerForKubectl(registrar) { register(registrar, 'kubectl'); register(registrar, 'k'); }