@kui-shell/plugin-kubectl
Version:
Kubernetes visualization plugin for kubernetes
279 lines (278 loc) • 10.2 kB
JavaScript
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');
}
;