kui-shell
Version:
This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool
330 lines • 13.7 kB
JavaScript
;
var __awaiter = (this && this.__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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = require("debug");
const util_1 = require("../util/util");
const repl_util_1 = require("@kui-shell/core/api/repl-util");
const debug = debug_1.default('k8s/states');
exports.States = {
Active: 'Active',
Online: 'Online',
Ready: 'Ready',
Running: 'Running',
Completed: 'Completed',
Succeeded: 'Succeeded',
ProvisionedSuccessfully: 'ProvisionedSuccessfully',
Deployed: 'Deployed',
ChannelReady: 'ChannelReady',
Addressable: 'Addressable',
Offline: 'Offline',
Undeployed: 'Offline',
Failed: 'Failed',
Disparity: 'Disparity',
NotProvisioned: 'NotProvisioned',
Unschedulable: 'Unschedulable',
ErrImagePull: 'ErrImagePull',
ImagePullBackOff: 'ImagePullBackOff',
PodInitializing: 'PodInitializing',
Retrying: 'Retrying',
Deleting: 'Deploying',
Deploying: 'Deploying',
Pending: 'Pending',
Waiting: 'Waiting',
Provisioning: 'Provisioning',
Conflict: 'Conflict'
};
var FinalState;
(function (FinalState) {
FinalState[FinalState["NotPendingLike"] = 0] = "NotPendingLike";
FinalState[FinalState["OnlineLike"] = 1] = "OnlineLike";
FinalState[FinalState["OfflineLike"] = 2] = "OfflineLike";
})(FinalState = exports.FinalState || (exports.FinalState = {}));
const stateGroups = {};
const groupOf = (A) => A.reduce((group, state) => {
group[state] = true;
return group;
}, {});
stateGroups[FinalState.OnlineLike] = groupOf([
exports.States.Active,
exports.States.Online,
exports.States.Ready,
exports.States.Running,
exports.States.ProvisionedSuccessfully,
exports.States.Deployed,
exports.States.ChannelReady,
exports.States.Addressable,
exports.States.Completed,
exports.States.Succeeded
]);
const isOnlineLike = (state) => stateGroups[FinalState.OnlineLike][state];
stateGroups[FinalState.OfflineLike] = groupOf([
exports.States.Offline,
exports.States.Undeployed,
exports.States.Failed,
exports.States.Disparity,
exports.States.NotProvisioned,
exports.States.Unschedulable,
exports.States.ErrImagePull
]);
const isOfflineLike = (state) => stateGroups[FinalState.OfflineLike][state];
const isPendingLike = (state) => !isOnlineLike(state) && !isOfflineLike(state);
var TrafficLight;
(function (TrafficLight) {
TrafficLight["Gray"] = "gray-background";
TrafficLight["Red"] = "red-background";
TrafficLight["Yellow"] = "yellow-background";
TrafficLight["Green"] = "green-background";
})(TrafficLight = exports.TrafficLight || (exports.TrafficLight = {}));
exports.state2Traffic = (state) => {
return state === exports.States.Conflict
? TrafficLight.Gray
: isOnlineLike(state)
? TrafficLight.Green
: isOfflineLike(state)
? TrafficLight.Red
: TrafficLight.Yellow;
};
exports.trafficMerge = (t1, t2) => {
if (t1 === TrafficLight.Red || t2 === TrafficLight.Red) {
return TrafficLight.Red;
}
else if (t1 === TrafficLight.Yellow || t2 === TrafficLight.Yellow) {
return TrafficLight.Yellow;
}
else {
return TrafficLight.Green;
}
};
exports.rendering = {
cssForState: (state) => {
return `min-width-6em ${exports.state2Traffic(state).toString()}`;
},
outerCSS: 'no-wrap'
};
const contextOption = (context) => (context ? '--context "' + context + '"' : '');
const ns = (namespace) => namespace && namespace !== 'default' ? `--namespace ${repl_util_1.encodeComponent(namespace)}` : '';
const genericOnlineMessage = {
state: exports.States.Online,
message: 'installed successfully'
};
const kindForQuery = (apiVersion, kind) => {
debug('apiVersion', apiVersion);
const api = apiVersion.replace(/^(.*)\/.*$/, '$1').replace(/^v.*/, '');
return `${kind}${api.length > 0 ? '.' + api : ''}`;
};
const getStatusFromConditions = (response) => {
if (response.status && !response.status.state && response.status.conditions) {
const conditions = response.status.conditions;
conditions.sort((highIndex, lowIndex) => {
let swap = -(new Date(highIndex.lastTransitionTime).getTime() - new Date(lowIndex.lastTransitionTime).getTime());
if (swap === 0) {
if (!isPendingLike(highIndex.type) && isPendingLike(lowIndex.type)) {
swap = -1;
}
else if (isPendingLike(highIndex.type) && !isPendingLike(lowIndex.type)) {
swap = 1;
}
}
return swap;
});
const conditionForMessage = conditions.find(_ => _.message) || conditions[0];
return {
state: conditions[0].reason || conditions[0].type,
message: (conditionForMessage && conditionForMessage.message) || conditions[0].lastTransitionTime
};
}
};
const getStatusOfDeployment = (kubeEntity, desiredFinalState) => {
const desireIsOffline = desiredFinalState === FinalState.OfflineLike;
if (!kubeEntity) {
return {
state: desireIsOffline ? exports.States.Offline : exports.States.Pending,
message: 'resource not yet available'
};
}
else {
const readyCondition = kubeEntity.status &&
kubeEntity.status.conditions &&
kubeEntity.status.conditions.find(({ reason, type, status }) => {
return reason === 'MinimumReplicasAvailable' || type === 'Ready' || status === 'True';
});
if (readyCondition) {
return {
state: desireIsOffline ? exports.States.Pending : exports.States.Online,
message: readyCondition.lastUpdateTime
};
}
else {
const desiredReplicas = kubeEntity.spec.replicas;
const currentReplicas = (kubeEntity.status && kubeEntity.status.readyReplicas) || 0;
const maybeCondition = kubeEntity.status && kubeEntity.status.conditions && kubeEntity.status.conditions[0];
const message = maybeCondition && maybeCondition.message;
if (currentReplicas >= desiredReplicas) {
return {
state: exports.States.Online,
message
};
}
else if (currentReplicas === 0) {
return {
state: desireIsOffline ? exports.States.Offline : exports.States.Pending,
message
};
}
else {
return {
state: exports.States.Pending,
message
};
}
}
}
};
const getStatus = (desiredFinalState, apiVersion, kind, name, namespace, context) => __awaiter(void 0, void 0, void 0, function* () {
try {
const cmd = `kubectl get ${contextOption(context)} ${kindForQuery(apiVersion, kind)} ${name} ${ns(namespace)} -o json`;
const { REPL } = yield Promise.resolve().then(() => require('@kui-shell/core/api/repl'));
const response = yield REPL.rexec(cmd);
if (!response.status ||
kind === 'Secret' ||
kind === 'Ingress' ||
kind === 'ConfigMap' ||
kind === 'PodSecurityPolicy' ||
kind === 'ClusterRole' ||
kind === 'CustomResourceDefinition' ||
kind === 'HorizontalPodAutoscaler' ||
kind === 'ClusterRoleBinding' ||
kind === 'VirtualService' ||
kind === 'ServiceAccount') {
return genericOnlineMessage;
}
else if (apiVersion && apiVersion.match(/^v/) && kind === 'Service' && response.status) {
return genericOnlineMessage;
}
else if (kind === 'ReplicaSet') {
const hasReplicas = response.status.readyReplicas > 0;
return {
state: desiredFinalState === FinalState.NotPendingLike ||
(desiredFinalState === FinalState.OfflineLike && !hasReplicas) ||
hasReplicas
? exports.States.Online
: exports.States.Pending
};
}
else if (kind === 'Deployment') {
return getStatusOfDeployment(response, desiredFinalState);
}
else {
const status = getStatusFromConditions(response) ||
(response.status && (response.status.state || response.status.phase)
? response.status
: response.apiVersion.match(/istio\.io/)
? {
state: exports.States.Online,
message: new Date().toUTCString()
}
: undefined);
if (!status) {
throw new util_1.TryLaterError('Status not yet available');
}
else {
return status;
}
}
}
catch (err) {
if (err.code === 404) {
return {
state: exports.States.Offline
};
}
else if (err.message.match(/You must be logged in to the server/)) {
return {
state: exports.States.Pending
};
}
else {
throw err;
}
}
});
exports.watchStatus = (watch, finalStateStr, count = 120) => __awaiter(void 0, void 0, void 0, function* () {
const finalState = typeof finalStateStr === 'string' ? FinalState[finalStateStr] : finalStateStr;
const { kind, name, namespace, context } = watch;
try {
const [status] = yield Promise.all([
getStatus(finalState, watch.apiVersion, kind, name, namespace, context)
]);
const state = status.state || status.phase;
const isDisparity = false;
const newState = (isDisparity ? exports.States.Disparity : state) || exports.States.Offline;
const others = newState === exports.States.Disparity
? [{ key: 'message', value: 'Underlying resource has disappeared' }]
: status.message
? [{ key: 'message', value: util_1.maybeAsDate(status.message) }]
: status.startTime
? [{ key: 'message', value: util_1.maybeAsDate(status.startTime) }]
: newState === exports.States.Offline
? [
{
key: 'message',
value: finalState === FinalState.OnlineLike ? 'resource not yet available' : 'resource is offline'
}
]
: newState === exports.States.Pending || newState === exports.States.Deploying || newState === exports.States.Deleting
? [{ key: 'message', value: 'in progress' }]
: undefined;
const done = newState === exports.States.Failed ||
(finalState === FinalState.NotPendingLike && !isPendingLike(newState)) ||
(finalState === FinalState.OnlineLike && isOnlineLike(newState)) ||
(finalState === FinalState.OfflineLike && isOfflineLike(newState));
const getKubernetesResource = `kubectl get ${kindForQuery(watch.apiVersion, kind)} ${repl_util_1.encodeComponent(name)} ${contextOption(context)} ${ns(namespace)} -o yaml`;
const onclick = getKubernetesResource;
const slowPoll = done && finalState === FinalState.NotPendingLike;
if (done) {
debug('watchStatus done', slowPoll, newState, watch.kind, watch.name, FinalState[finalState]);
}
return {
outerCSS: done ? '' : exports.rendering.outerCSS,
value: newState,
onclick,
slowPoll: slowPoll && 5000,
done: done && !slowPoll,
css: exports.rendering.cssForState(newState),
others
};
}
catch (err) {
if (err instanceof util_1.TryLaterError) {
if (count < 20) {
debug('trying later', kind, name);
return {
value: exports.States.Pending,
others: [{ key: 'message', value: 'not found, rechecking' }]
};
}
else {
debug('giving up, considering as offline', kind, name);
return {
done: true,
value: exports.States.Offline,
css: exports.rendering.cssForState(exports.States.Offline),
others: [{ key: 'message', value: 'resource does not exist' }]
};
}
}
else {
console.error('error in watch update', kind, name, err);
return { done: true };
}
}
});
//# sourceMappingURL=states.js.map