mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
remove ClusterFeature
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
f550018f39
commit
f47e2a8477
@ -2,3 +2,5 @@ apiVersion: v1
|
|||||||
kind: Namespace
|
kind: Namespace
|
||||||
metadata:
|
metadata:
|
||||||
name: lens-metrics
|
name: lens-metrics
|
||||||
|
labels:
|
||||||
|
app: lens-metrics
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ClusterFeature, Catalog, K8sApi } from "@k8slens/extensions";
|
import { Catalog, K8sApi } from "@k8slens/extensions";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
@ -27,86 +27,67 @@ export interface MetricsConfiguration {
|
|||||||
storageClass: string;
|
storageClass: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MetricsFeature extends ClusterFeature.Feature {
|
export interface MetricsStatus {
|
||||||
|
installed: boolean;
|
||||||
|
canUpgrade: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MetricsFeature {
|
||||||
name = "metrics";
|
name = "metrics";
|
||||||
latestVersion = "v2.19.3-lens1";
|
latestVersion = "v2.19.3-lens1";
|
||||||
|
|
||||||
templateContext: MetricsConfiguration = {
|
protected stack: K8sApi.ResourceStack;
|
||||||
prometheus: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
persistence: {
|
|
||||||
enabled: false,
|
|
||||||
storageClass: null,
|
|
||||||
size: "20G",
|
|
||||||
},
|
|
||||||
nodeExporter: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
retention: {
|
|
||||||
time: "2d",
|
|
||||||
size: "5GB",
|
|
||||||
},
|
|
||||||
kubeStateMetrics: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
alertManagers: null,
|
|
||||||
replicas: 1,
|
|
||||||
storageClass: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
async install(cluster: Catalog.KubernetesCluster): Promise<void> {
|
constructor(protected cluster: Catalog.KubernetesCluster) {
|
||||||
|
this.stack = new K8sApi.ResourceStack(cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
get resourceFolder() {
|
||||||
|
return path.join(__dirname, "../resources/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(config: MetricsConfiguration): Promise<string> {
|
||||||
// Check if there are storageclasses
|
// Check if there are storageclasses
|
||||||
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
const storageClassApi = K8sApi.forCluster(this.cluster, K8sApi.StorageClass);
|
||||||
const scs = await storageClassApi.list();
|
const scs = await storageClassApi.list();
|
||||||
|
|
||||||
this.templateContext.persistence.enabled = scs.some(sc => (
|
config.persistence.enabled = scs.some(sc => (
|
||||||
sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" ||
|
sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" ||
|
||||||
sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
|
sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
|
||||||
));
|
));
|
||||||
|
|
||||||
await super.applyResources(cluster, path.join(__dirname, "../resources/"));
|
return this.stack.kubectlApplyFolder(this.resourceFolder, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgrade(cluster: Catalog.KubernetesCluster): Promise<void> {
|
async upgrade(config: MetricsConfiguration): Promise<string> {
|
||||||
return this.install(cluster);
|
return this.install(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus(cluster: Catalog.KubernetesCluster): Promise<ClusterFeature.FeatureStatus> {
|
async getStatus(): Promise<MetricsStatus> {
|
||||||
|
const status: MetricsStatus = { installed: false, canUpgrade: false};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
const statefulSet = K8sApi.forCluster(this.cluster, K8sApi.StatefulSet);
|
||||||
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
||||||
|
|
||||||
if (prometheus?.kind) {
|
if (prometheus?.kind) {
|
||||||
this.status.installed = true;
|
const currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
||||||
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
|
|
||||||
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
|
|
||||||
|
|
||||||
const pvcTemplate = prometheus.spec.volumeClaimTemplates[0];
|
status.installed = true;
|
||||||
|
status.canUpgrade = semver.lt(currentVersion, this.latestVersion, true);
|
||||||
if (pvcTemplate) {
|
|
||||||
this.templateContext.persistence.enabled = true;
|
|
||||||
this.templateContext.persistence.size = pvcTemplate.spec.resources.requests.storage;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.status.installed = false;
|
status.installed = false;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (e?.error?.code === 404) {
|
if (e?.error?.code === 404) {
|
||||||
this.status.installed = false;
|
status.installed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall(cluster: Catalog.KubernetesCluster): Promise<void> {
|
async uninstall(config: MetricsConfiguration): Promise<string> {
|
||||||
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
return this.stack.kubectlDeleteFolder(this.resourceFolder, config);
|
||||||
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
|
||||||
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
|
||||||
|
|
||||||
await namespaceApi.delete({name: "lens-metrics"});
|
|
||||||
await clusterRoleBindingApi.delete({name: "lens-prometheus"});
|
|
||||||
await clusterRoleApi.delete({name: "lens-prometheus"});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import React from "react";
|
|||||||
import { Component, Catalog, K8sApi } from "@k8slens/extensions";
|
import { Component, Catalog, K8sApi } from "@k8slens/extensions";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { MetricsFeature } from "./metrics-feature";
|
import { MetricsFeature, MetricsConfiguration } from "./metrics-feature";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cluster: Catalog.KubernetesCluster;
|
cluster: Catalog.KubernetesCluster;
|
||||||
}
|
}
|
||||||
@ -17,28 +18,42 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
@observable canUpgrade = false;
|
@observable canUpgrade = false;
|
||||||
@observable upgrading = false;
|
@observable upgrading = false;
|
||||||
|
|
||||||
pvcOptions = [
|
config: MetricsConfiguration = {
|
||||||
{ value: "", label: "Use EmptyDir (no PVC)" },
|
prometheus: {
|
||||||
{ value: "5GB", label: "5GB" },
|
enabled: false
|
||||||
{ value: "10GB", label: "10GB" },
|
},
|
||||||
{ value: "20GB", label: "20GB" },
|
persistence: {
|
||||||
{ value: "40GB", label: "40GB" },
|
enabled: false,
|
||||||
{ value: "80GB", label: "80GB" },
|
storageClass: null,
|
||||||
];
|
size: "20G",
|
||||||
|
},
|
||||||
featureSet: MetricsFeature;
|
nodeExporter: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
retention: {
|
||||||
|
time: "2d",
|
||||||
|
size: "5GB",
|
||||||
|
},
|
||||||
|
kubeStateMetrics: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
alertManagers: null,
|
||||||
|
replicas: 1,
|
||||||
|
storageClass: null,
|
||||||
|
};
|
||||||
|
feature: MetricsFeature;
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.featureSet = new MetricsFeature();
|
this.feature = new MetricsFeature(this.props.cluster);
|
||||||
|
|
||||||
await this.updateFeatureStates();
|
await this.updateFeatureStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFeatureStates() {
|
async updateFeatureStates() {
|
||||||
await this.featureSet.updateStatus(this.props.cluster);
|
const status = await this.feature.getStatus();
|
||||||
this.canUpgrade = this.featureSet.status.canUpgrade;
|
|
||||||
|
|
||||||
this.featureStates.prometheus = this.featureSet.status.installed;
|
this.canUpgrade = status.canUpgrade;
|
||||||
|
this.featureStates.prometheus = status.installed;
|
||||||
|
|
||||||
const deployment = K8sApi.forCluster(this.props.cluster, K8sApi.Deployment);
|
const deployment = K8sApi.forCluster(this.props.cluster, K8sApi.Deployment);
|
||||||
|
|
||||||
@ -68,24 +83,22 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
const ctx = this.featureSet.templateContext;
|
this.config.prometheus.enabled = !!this.featureStates.prometheus;
|
||||||
|
this.config.kubeStateMetrics.enabled = !!this.featureStates.kubeStateMetrics;
|
||||||
|
this.config.nodeExporter.enabled = !!this.featureStates.nodeExporter;
|
||||||
|
|
||||||
ctx.prometheus.enabled = !!this.featureStates.prometheus;
|
console.log(this.config);
|
||||||
ctx.kubeStateMetrics.enabled = !!this.featureStates.kubeStateMetrics;
|
|
||||||
ctx.nodeExporter.enabled = !!this.featureStates.nodeExporter;
|
|
||||||
|
|
||||||
if (!ctx.prometheus.enabled && !ctx.kubeStateMetrics.enabled && !ctx.nodeExporter.enabled) {
|
if (!this.config.prometheus.enabled && !this.config.kubeStateMetrics.enabled && !this.config.nodeExporter.enabled) {
|
||||||
await this.featureSet.uninstall(this.props.cluster);
|
await this.feature.uninstall(this.config);
|
||||||
} else {
|
} else {
|
||||||
await this.featureSet.install(this.props.cluster);
|
await this.feature.install(this.config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async togglePrometheus(enabled: boolean) {
|
async togglePrometheus(enabled: boolean) {
|
||||||
this.featureStates.prometheus = enabled;
|
this.featureStates.prometheus = enabled;
|
||||||
|
|
||||||
this.featureSet.templateContext.prometheus.enabled = this.featureStates.prometheus;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.save();
|
await this.save();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
@ -97,8 +110,6 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
async toggleKubeStateMetrics(enabled: boolean) {
|
async toggleKubeStateMetrics(enabled: boolean) {
|
||||||
this.featureStates.kubeStateMetrics = enabled;
|
this.featureStates.kubeStateMetrics = enabled;
|
||||||
|
|
||||||
this.featureSet.templateContext.kubeStateMetrics.enabled = this.featureStates.kubeStateMetrics;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.save();
|
await this.save();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
@ -110,8 +121,6 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
async toggleNodeExporter(enabled: boolean) {
|
async toggleNodeExporter(enabled: boolean) {
|
||||||
this.featureStates.nodeExporter = enabled;
|
this.featureStates.nodeExporter = enabled;
|
||||||
|
|
||||||
this.featureSet.templateContext.nodeExporter.enabled = this.featureStates.nodeExporter;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.save();
|
await this.save();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
|||||||
export const clusterRefreshHandler = "cluster:refresh";
|
export const clusterRefreshHandler = "cluster:refresh";
|
||||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||||
|
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||||
|
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||||
@ -64,4 +65,23 @@ if (ipcMain) {
|
|||||||
throw `${clusterId} is not a valid cluster id`;
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handleRequest(clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[]) => {
|
||||||
|
appEventBus.emit({name: "cluster", action: "kubectl-delete-all"});
|
||||||
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
|
if (cluster) {
|
||||||
|
const applier = new ResourceApplier(cluster);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stdout = await applier.kubectlApplyAll(resources);
|
||||||
|
|
||||||
|
return { stdout };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { stderr: error };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw `${clusterId} is not a valid cluster id`;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/common/k8s/resource-stack.ts
Normal file
102
src/common/k8s/resource-stack.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import hb from "handlebars";
|
||||||
|
import { ResourceApplier } from "../../main/resource-applier";
|
||||||
|
import { KubernetesCluster } from "../catalog-entities";
|
||||||
|
import logger from "../../main/logger";
|
||||||
|
import { app } from "electron";
|
||||||
|
import { requestMain } from "../ipc";
|
||||||
|
import { clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../cluster-ipc";
|
||||||
|
import { ClusterStore } from "../cluster-store";
|
||||||
|
|
||||||
|
export class ResourceStack {
|
||||||
|
/**
|
||||||
|
* this field sets the template parameters that are to be applied to any templated kubernetes resources that are to be installed.
|
||||||
|
* See the renderTemplates() method for more details
|
||||||
|
*/
|
||||||
|
templateContext: any;
|
||||||
|
|
||||||
|
constructor(protected cluster: KubernetesCluster) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param folderPath folder path that is searched for files defining kubernetes resources.
|
||||||
|
* @param templateContext sets the template parameters that are to be applied to any templated kubernetes resources that are to be applied.
|
||||||
|
*/
|
||||||
|
async kubectlApplyFolder(folderPath: string, templateContext?: any): Promise<string> {
|
||||||
|
const resources = this.renderTemplates(folderPath, templateContext);
|
||||||
|
|
||||||
|
return this.applyResources(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param folderPath folder path that is searched for files defining kubernetes resources.
|
||||||
|
* @param templateContext sets the template parameters that are to be applied to any templated kubernetes resources that are to be applied.
|
||||||
|
*/
|
||||||
|
async kubectlDeleteFolder(folderPath: string, templateContext?: any): Promise<string> {
|
||||||
|
const resources = this.renderTemplates(folderPath, templateContext);
|
||||||
|
|
||||||
|
return this.deleteResources(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async applyResources(resources: string[]): Promise<string> {
|
||||||
|
const clusterModel = ClusterStore.getInstance().getById(this.cluster.metadata.uid);
|
||||||
|
|
||||||
|
if (!clusterModel) {
|
||||||
|
throw new Error(`cluster not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
return await new ResourceApplier(clusterModel).kubectlApplyAll(resources);
|
||||||
|
} else {
|
||||||
|
const response = await requestMain(clusterKubectlApplyAllHandler, this.cluster.metadata.uid, resources);
|
||||||
|
|
||||||
|
if (response.stderr) {
|
||||||
|
throw new Error(response.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async deleteResources(resources: string[]): Promise<string> {
|
||||||
|
const clusterModel = ClusterStore.getInstance().getById(this.cluster.metadata.uid);
|
||||||
|
|
||||||
|
if (!clusterModel) {
|
||||||
|
throw new Error(`cluster not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
return await new ResourceApplier(clusterModel).kubectlDeleteAll(resources);
|
||||||
|
} else {
|
||||||
|
const response = await requestMain(clusterKubectlDeleteAllHandler, this.cluster.metadata.uid, resources);
|
||||||
|
|
||||||
|
if (response.stderr) {
|
||||||
|
throw new Error(response.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderTemplates(folderPath: string, templateContext: any): string[] {
|
||||||
|
const resources: string[] = [];
|
||||||
|
|
||||||
|
logger.info(`[RESOURCE-STACK]: render templates from ${folderPath}`);
|
||||||
|
fs.readdirSync(folderPath).forEach(filename => {
|
||||||
|
const file = path.join(folderPath, filename);
|
||||||
|
const raw = fs.readFileSync(file);
|
||||||
|
|
||||||
|
if (filename.endsWith(".hb")) {
|
||||||
|
const template = hb.compile(raw.toString());
|
||||||
|
|
||||||
|
resources.push(template(templateContext));
|
||||||
|
} else {
|
||||||
|
resources.push(raw.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,135 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import hb from "handlebars";
|
|
||||||
import { observable } from "mobx";
|
|
||||||
import { ResourceApplier } from "../main/resource-applier";
|
|
||||||
import { KubernetesCluster } from "./core-api/catalog";
|
|
||||||
import logger from "../main/logger";
|
|
||||||
import { app } from "electron";
|
|
||||||
import { requestMain } from "../common/ipc";
|
|
||||||
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
|
||||||
import { ClusterStore } from "../common/cluster-store";
|
|
||||||
|
|
||||||
export interface ClusterFeatureStatus {
|
|
||||||
/** feature's current version, as set by the implementation */
|
|
||||||
currentVersion: string;
|
|
||||||
/** feature's latest version, as set by the implementation */
|
|
||||||
latestVersion: string;
|
|
||||||
/** whether the feature is installed or not, as set by the implementation */
|
|
||||||
installed: boolean;
|
|
||||||
/** whether the feature can be upgraded or not, as set by the implementation */
|
|
||||||
canUpgrade: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class ClusterFeature {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this field sets the template parameters that are to be applied to any templated kubernetes resources that are to be installed for the feature.
|
|
||||||
* See the renderTemplates() method for more details
|
|
||||||
*/
|
|
||||||
templateContext: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this field holds the current feature status, is accessed directly by Lens
|
|
||||||
*/
|
|
||||||
@observable status: ClusterFeatureStatus = {
|
|
||||||
currentVersion: null,
|
|
||||||
installed: false,
|
|
||||||
latestVersion: null,
|
|
||||||
canUpgrade: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be installed. The implementation
|
|
||||||
* of this method should install kubernetes resources using the applyResources() method, or by directly accessing the kubernetes api (K8sApi)
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be installed on
|
|
||||||
*/
|
|
||||||
abstract install(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
|
|
||||||
* of this method should upgrade the kubernetes resources already installed, if relevant to the feature
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be upgraded on
|
|
||||||
*/
|
|
||||||
abstract upgrade(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
|
|
||||||
* of this method should uninstall kubernetes resources using the kubernetes api (K8sApi)
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature is to be uninstalled from
|
|
||||||
*/
|
|
||||||
abstract uninstall(cluster: KubernetesCluster): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
|
|
||||||
* of this method should provide the current status information. The currentVersion and latestVersion fields may be displayed by Lens in describing the feature.
|
|
||||||
* The installed field should be set to true if the feature has been installed, otherwise false. Also, Lens relies on the canUpgrade field to determine if the feature
|
|
||||||
* can be upgraded so the implementation should set the canUpgrade field according to specific rules for the feature, if relevant.
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the feature may be installed on
|
|
||||||
*
|
|
||||||
* @return a promise, resolved with the updated ClusterFeatureStatus
|
|
||||||
*/
|
|
||||||
abstract updateStatus(cluster: KubernetesCluster): Promise<ClusterFeatureStatus>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this is a helper method that conveniently applies kubernetes resources to the cluster.
|
|
||||||
*
|
|
||||||
* @param cluster the cluster that the resources are to be applied to
|
|
||||||
* @param resourceSpec as a string type this is a folder path that is searched for files specifying kubernetes resources. The files are read and if any of the resource
|
|
||||||
* files are templated, the template parameters are filled using the templateContext field (See renderTemplate() method). Finally the resources are applied to the
|
|
||||||
* cluster. As a string[] type resourceSpec is treated as an array of fully formed (not templated) kubernetes resources that are applied to the cluster
|
|
||||||
*/
|
|
||||||
protected async applyResources(cluster: KubernetesCluster, resourceSpec: string | string[]) {
|
|
||||||
let resources: string[];
|
|
||||||
|
|
||||||
const clusterModel = ClusterStore.getInstance().getById(cluster.metadata.uid);
|
|
||||||
|
|
||||||
if (!clusterModel) {
|
|
||||||
throw new Error(`cluster not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( typeof resourceSpec === "string" ) {
|
|
||||||
resources = this.renderTemplates(resourceSpec);
|
|
||||||
} else {
|
|
||||||
resources = resourceSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app) {
|
|
||||||
return await new ResourceApplier(clusterModel).kubectlApplyAll(resources);
|
|
||||||
} else {
|
|
||||||
return await requestMain(clusterKubectlApplyAllHandler, cluster.metadata.uid, resources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this is a helper method that conveniently reads kubernetes resource files into a string array. It also fills templated resource files with the template parameter values
|
|
||||||
* specified by the templateContext field. Templated files must end with the extension '.hb' and the template syntax must be compatible with handlebars.js
|
|
||||||
*
|
|
||||||
* @param folderPath this is a folder path that is searched for files defining kubernetes resources.
|
|
||||||
*
|
|
||||||
* @return an array of strings, each string being the contents of a resource file found in the folder path. This can be passed directly to applyResources()
|
|
||||||
*/
|
|
||||||
protected renderTemplates(folderPath: string): string[] {
|
|
||||||
const resources: string[] = [];
|
|
||||||
|
|
||||||
logger.info(`[FEATURE]: render templates from ${folderPath}`);
|
|
||||||
fs.readdirSync(folderPath).forEach(filename => {
|
|
||||||
const file = path.join(folderPath, filename);
|
|
||||||
const raw = fs.readFileSync(file);
|
|
||||||
|
|
||||||
if (filename.endsWith(".hb")) {
|
|
||||||
const template = hb.compile(raw.toString());
|
|
||||||
|
|
||||||
resources.push(template(this.templateContext));
|
|
||||||
} else {
|
|
||||||
resources.push(raw.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export { ClusterFeature as Feature } from "../cluster-feature";
|
|
||||||
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature";
|
|
||||||
@ -7,7 +7,6 @@ import * as App from "./app";
|
|||||||
import * as EventBus from "./event-bus";
|
import * as EventBus from "./event-bus";
|
||||||
import * as Store from "./stores";
|
import * as Store from "./stores";
|
||||||
import * as Util from "./utils";
|
import * as Util from "./utils";
|
||||||
import * as ClusterFeature from "./cluster-feature";
|
|
||||||
import * as Interface from "../interfaces";
|
import * as Interface from "../interfaces";
|
||||||
import * as Catalog from "./catalog";
|
import * as Catalog from "./catalog";
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ export {
|
|||||||
App,
|
App,
|
||||||
EventBus,
|
EventBus,
|
||||||
Catalog,
|
Catalog,
|
||||||
ClusterFeature,
|
|
||||||
Interface,
|
Interface,
|
||||||
Store,
|
Store,
|
||||||
Util,
|
Util,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export { isAllowedResource } from "../../common/rbac";
|
export { isAllowedResource } from "../../common/rbac";
|
||||||
|
export { ResourceStack } from "../../common/k8s/resource-stack";
|
||||||
export { apiManager } from "../../renderer/api/api-manager";
|
export { apiManager } from "../../renderer/api/api-manager";
|
||||||
export { KubeObjectStore } from "../../renderer/kube-object.store";
|
export { KubeObjectStore } from "../../renderer/kube-object.store";
|
||||||
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
|
export { KubeApi, forCluster, IKubeApiCluster } from "../../renderer/api/kube-api";
|
||||||
|
|||||||
@ -53,6 +53,14 @@ export class ResourceApplier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async kubectlApplyAll(resources: string[]): Promise<string> {
|
public async kubectlApplyAll(resources: string[]): Promise<string> {
|
||||||
|
return this.kubectlCmdAll("apply", resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async kubectlDeleteAll(resources: string[]): Promise<string> {
|
||||||
|
return this.kubectlCmdAll("delete", resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async kubectlCmdAll(subCmd: string, resources: string[]): Promise<string> {
|
||||||
const { kubeCtl } = this.cluster;
|
const { kubeCtl } = this.cluster;
|
||||||
const kubectlPath = await kubeCtl.getPath();
|
const kubectlPath = await kubeCtl.getPath();
|
||||||
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
||||||
@ -64,17 +72,16 @@ export class ResourceApplier {
|
|||||||
resources.forEach((resource, index) => {
|
resources.forEach((resource, index) => {
|
||||||
fs.writeFileSync(path.join(tmpDir, `${index}.yaml`), resource);
|
fs.writeFileSync(path.join(tmpDir, `${index}.yaml`), resource);
|
||||||
});
|
});
|
||||||
const cmd = `"${kubectlPath}" apply --kubeconfig "${proxyKubeconfigPath}" -o json -f "${tmpDir}"`;
|
const cmd = `"${kubectlPath}" ${subCmd} --kubeconfig "${proxyKubeconfigPath}" -o json -f "${tmpDir}"`;
|
||||||
|
|
||||||
console.log("shooting manifests with:", cmd);
|
|
||||||
exec(cmd, (error, stdout) => {
|
exec(cmd, (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
const splittedError = error.toString().split(`.yaml": `);
|
const splittedError = error.toString().split(`.yaml": `);
|
||||||
|
|
||||||
if (splittedError[1]) {
|
if (splittedError[1]) {
|
||||||
reject(`Error applying manifests: ${splittedError[1]}`);
|
reject(splittedError[1]);
|
||||||
} else {
|
} else {
|
||||||
reject(`Error applying manifests:${error}`);
|
reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user