mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Release 6.2.2 (#6642)
* Release 6.2.2 Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix: getAllowedResources for all namespaces using SelfSubjectRulesReview (#6614) * fix: getAllowedResources for all namespaces using SelfSubjectRulesReview Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> * fix: refresh accessibility every 15 min Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> * chore: remove unused clusterRefreshHandler Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> * fix: resolve SelfSubjectRulesReview globs Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> Co-authored-by: Andreas Hippler <andreas.hippler@goto.com> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add missing gutter between sections in cluster settings (#6631) Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Adding spacing between Metrics Settings sections (#6632) Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix crash when upgrading release (#6626) * Fix crash when upgrading release Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash when upgrading helm releases - Fixes not being able to upgrade helm releases as well. Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test failures Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Removing big padding after cluster settings avatar (#6634) Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix KubeApi watch retry on timeout (#6640) * fix KubeApi watch retry on timeout Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * Fix tests Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Sebastian Malton <sebastian@malton.name> * Bump electron from 19.1.6 to 19.1.7 (#6637) Bumps [electron](https://github.com/electron/electron) from 19.1.6 to 19.1.7. - [Release notes](https://github.com/electron/electron/releases) - [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md) - [Commits](https://github.com/electron/electron/compare/v19.1.6...v19.1.7) --- updated-dependencies: - dependency-name: electron dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Andreas Hippler <andreas.hippler@goto.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Andreas Hippler <andreas.hippler@logmein.com> Co-authored-by: Andreas Hippler <andreas.hippler@goto.com> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com> Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
da4afc9110
commit
4a13f516f5
@ -190,7 +190,7 @@ export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<section style={{ display: "flex", flexDirection: "column", rowGap: "1.5rem" }}>
|
||||||
{ this.props.cluster.status.phase !== "connected" && (
|
{ this.props.cluster.status.phase !== "connected" && (
|
||||||
<section>
|
<section>
|
||||||
<p style={ { color: "var(--colorError)" } }>
|
<p style={ { color: "var(--colorError)" } }>
|
||||||
@ -270,7 +270,7 @@ export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -390,12 +390,6 @@ const scenarios = [
|
|||||||
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
|
||||||
sidebarItemTestId: "sidebar-item-link-for-roles",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
@ -405,7 +399,7 @@ const scenarios = [
|
|||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
sidebarItemTestId: "sidebar-item-link-for-roles",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -417,7 +411,7 @@ const scenarios = [
|
|||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
sidebarItemTestId: "sidebar-item-link-for-pod-security-policies",
|
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "6.2.1",
|
"version": "6.2.2",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2022 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -375,7 +375,7 @@
|
|||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.1",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.4.1",
|
"dompurify": "^2.4.1",
|
||||||
"electron": "^19.1.6",
|
"electron": "^19.1.7",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.15.14",
|
"esbuild": "^0.15.14",
|
||||||
|
|||||||
@ -195,13 +195,6 @@ export enum ClusterMetricsResourceType {
|
|||||||
*/
|
*/
|
||||||
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
||||||
|
|
||||||
/**
|
|
||||||
* The arguments for requesting to refresh a cluster's metadata
|
|
||||||
*/
|
|
||||||
export interface ClusterRefreshOptions {
|
|
||||||
refreshMetadata?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data representing a cluster's state, for passing between main and renderer
|
* The data representing a cluster's state, for passing between main and renderer
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param namespace The namespace of the resources
|
||||||
|
* @param availableResources List of available resources in the cluster to resolve glob values fir api groups
|
||||||
|
* @returns list of allowed resources names
|
||||||
|
*/
|
||||||
|
export type RequestNamespaceResources = (namespace: string, availableResources: KubeApiResource[]) => Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
|
*/
|
||||||
|
export type AuthorizationNamespaceReview = (proxyConfig: KubeConfig) => RequestNamespaceResources;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizationNamespaceReview = ({ logger }: Dependencies): AuthorizationNamespaceReview => {
|
||||||
|
return (proxyConfig) => {
|
||||||
|
|
||||||
|
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||||
|
|
||||||
|
return async (namespace, availableResources) => {
|
||||||
|
try {
|
||||||
|
const { body } = await api.createSelfSubjectRulesReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectRulesReview",
|
||||||
|
spec: { namespace },
|
||||||
|
});
|
||||||
|
|
||||||
|
const resources = new Set<string>();
|
||||||
|
|
||||||
|
body.status?.resourceRules.forEach(resourceRule => {
|
||||||
|
if (!resourceRule.verbs.some(verb => ["*", "list"].includes(verb)) || !resourceRule.resources) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiGroups = resourceRule.apiGroups;
|
||||||
|
|
||||||
|
if (resourceRule.resources.length === 1 && resourceRule.resources[0] === "*" && apiGroups) {
|
||||||
|
if (apiGroups[0] === "*") {
|
||||||
|
availableResources.forEach(resource => resources.add(resource.apiName));
|
||||||
|
} else {
|
||||||
|
availableResources.forEach((apiResource)=> {
|
||||||
|
if (apiGroups.includes(apiResource.group || "")) {
|
||||||
|
resources.add(apiResource.apiName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resourceRule.resources.forEach(resource => resources.add(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...resources];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace });
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorizationNamespaceReviewInjectable = getInjectable({
|
||||||
|
id: "authorization-namespace-review",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return authorizationNamespaceReview({ logger });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default authorizationNamespaceReviewInjectable;
|
||||||
@ -5,22 +5,30 @@
|
|||||||
|
|
||||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||||
|
* @returns `true` if the actions described are allowed
|
||||||
|
*/
|
||||||
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
*/
|
*/
|
||||||
export function authorizationReview(proxyConfig: KubeConfig): CanI {
|
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
||||||
|
return (proxyConfig) => {
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the permissions for actions on the kube cluster
|
|
||||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
|
||||||
* @returns `true` if the actions described are allowed
|
|
||||||
*/
|
|
||||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const { body } = await api.createSelfSubjectAccessReview({
|
const { body } = await api.createSelfSubjectAccessReview({
|
||||||
@ -36,11 +44,16 @@ export function authorizationReview(proxyConfig: KubeConfig): CanI {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const authorizationReviewInjectable = getInjectable({
|
const authorizationReviewInjectable = getInjectable({
|
||||||
id: "authorization-review",
|
id: "authorization-review",
|
||||||
instantiate: () => authorizationReview,
|
instantiate: (di) => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return authorizationReview({ logger });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default authorizationReviewInjectable;
|
export default authorizationReviewInjectable;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { apiResourceRecord, apiResources } from "../rbac";
|
|||||||
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||||
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
||||||
import plimit from "p-limit";
|
import plimit from "p-limit";
|
||||||
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
import type { ClusterState, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
||||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
||||||
import { disposer, isDefined, isRequestError, toJS } from "../utils";
|
import { disposer, isDefined, isRequestError, toJS } from "../utils";
|
||||||
import type { Response } from "request";
|
import type { Response } from "request";
|
||||||
@ -25,6 +25,8 @@ import assert from "assert";
|
|||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||||
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||||
|
import type { RequestNamespaceResources } from "./authorization-namespace-review.injectable";
|
||||||
|
import type { RequestListApiResources } from "./list-api-resources.injectable";
|
||||||
|
|
||||||
export interface ClusterDependencies {
|
export interface ClusterDependencies {
|
||||||
readonly directoryForKubeConfigs: string;
|
readonly directoryForKubeConfigs: string;
|
||||||
@ -34,6 +36,8 @@ export interface ClusterDependencies {
|
|||||||
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||||
createKubectl: (clusterVersion: string) => Kubectl;
|
createKubectl: (clusterVersion: string) => Kubectl;
|
||||||
createAuthorizationReview: (config: KubeConfig) => CanI;
|
createAuthorizationReview: (config: KubeConfig) => CanI;
|
||||||
|
createAuthorizationNamespaceReview: (config: KubeConfig) => RequestNamespaceResources;
|
||||||
|
createListApiResources: (cluster: Cluster) => RequestListApiResources;
|
||||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||||
broadcastMessage: BroadcastMessage;
|
broadcastMessage: BroadcastMessage;
|
||||||
@ -309,7 +313,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
protected bindEvents() {
|
protected bindEvents() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||||
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventsDisposer.push(
|
this.eventsDisposer.push(
|
||||||
reaction(() => this.getState(), state => this.pushState(state)),
|
reaction(() => this.getState(), state => this.pushState(state)),
|
||||||
@ -439,20 +443,11 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* @param opts refresh options
|
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async refresh(opts: ClusterRefreshOptions = {}) {
|
async refresh() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
|
|
||||||
if (this.accessible) {
|
|
||||||
await this.refreshAccessibility();
|
|
||||||
|
|
||||||
if (opts.refreshMetadata) {
|
|
||||||
this.refreshMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,6 +455,14 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
|
async refreshAccessibilityAndMetadata() {
|
||||||
|
await this.refreshAccessibility();
|
||||||
|
await this.refreshMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async refreshMetadata() {
|
async refreshMetadata() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||||
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
||||||
@ -472,8 +475,11 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
private async refreshAccessibility(): Promise<void> {
|
private async refreshAccessibility(): Promise<void> {
|
||||||
|
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta());
|
||||||
const proxyConfig = await this.getProxyKubeconfig();
|
const proxyConfig = await this.getProxyKubeconfig();
|
||||||
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
||||||
|
const requestNamespaceResources = this.dependencies.createAuthorizationNamespaceReview(proxyConfig);
|
||||||
|
const listApiResources = this.dependencies.createListApiResources(this);
|
||||||
|
|
||||||
this.isAdmin = await canI({
|
this.isAdmin = await canI({
|
||||||
namespace: "kube-system",
|
namespace: "kube-system",
|
||||||
@ -485,7 +491,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
resource: "*",
|
resource: "*",
|
||||||
});
|
});
|
||||||
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
||||||
this.allowedResources = await this.getAllowedResources(canI);
|
this.allowedResources = await this.getAllowedResources(listApiResources, requestNamespaceResources);
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,32 +673,48 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getAllowedResources(canI: CanI) {
|
protected async getAllowedResources(listApiResources:RequestListApiResources, requestNamespaceResources: RequestNamespaceResources) {
|
||||||
try {
|
try {
|
||||||
if (!this.allowedNamespaces.length) {
|
if (!this.allowedNamespaces.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const resources = apiResources.filter((resource) => this.resourceAccessStatuses.get(resource) === undefined);
|
|
||||||
const apiLimit = plimit(5); // 5 concurrent api requests
|
|
||||||
const requests = [];
|
|
||||||
|
|
||||||
for (const apiResource of resources) {
|
const unknownResources = new Map<string, KubeApiResource>(apiResources.map(resource => ([resource.apiName, resource])));
|
||||||
requests.push(apiLimit(async () => {
|
|
||||||
for (const namespace of this.allowedNamespaces.slice(0, 10)) {
|
const availableResources = await listApiResources();
|
||||||
if (!this.resourceAccessStatuses.get(apiResource)) {
|
const availableResourcesNames = new Set(availableResources.map(apiResource => apiResource.apiName));
|
||||||
const result = await canI({
|
|
||||||
resource: apiResource.apiName,
|
[...unknownResources.values()].map(unknownResource => {
|
||||||
group: apiResource.group,
|
if (!availableResourcesNames.has(unknownResource.apiName)) {
|
||||||
verb: "list",
|
this.resourceAccessStatuses.set(unknownResource, false);
|
||||||
namespace,
|
unknownResources.delete(unknownResource.apiName);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resourceAccessStatuses.set(apiResource, result);
|
if (unknownResources.size > 0) {
|
||||||
|
const apiLimit = plimit(5); // 5 concurrent api requests
|
||||||
|
|
||||||
|
await Promise.all(this.allowedNamespaces.map(namespace => apiLimit(async () => {
|
||||||
|
if (unknownResources.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespaceResources = await requestNamespaceResources(namespace, availableResources);
|
||||||
|
|
||||||
|
for (const resourceName of namespaceResources) {
|
||||||
|
const unknownResource = unknownResources.get(resourceName);
|
||||||
|
|
||||||
|
if (unknownResource) {
|
||||||
|
this.resourceAccessStatuses.set(unknownResource, true);
|
||||||
|
unknownResources.delete(resourceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
})));
|
||||||
|
|
||||||
|
for (const forbiddenResource of unknownResources.values()) {
|
||||||
|
this.resourceAccessStatuses.set(forbiddenResource, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(requests);
|
|
||||||
|
|
||||||
return apiResources
|
return apiResources
|
||||||
.filter((resource) => this.resourceAccessStatuses.get(resource))
|
.filter((resource) => this.resourceAccessStatuses.get(resource))
|
||||||
|
|||||||
91
src/common/cluster/list-api-resources.injectable.ts
Normal file
91
src/common/cluster/list-api-resources.injectable.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
V1APIGroupList,
|
||||||
|
V1APIResourceList,
|
||||||
|
V1APIVersions,
|
||||||
|
} from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { K8sRequest } from "../../main/k8s-request.injectable";
|
||||||
|
import k8SRequestInjectable from "../../main/k8s-request.injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||||
|
import type { Cluster } from "./cluster";
|
||||||
|
import plimit from "p-limit";
|
||||||
|
|
||||||
|
export type RequestListApiResources = () => Promise<KubeApiResource[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
|
*/
|
||||||
|
export type ListApiResources = (cluster: Cluster) => RequestListApiResources;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
logger: Logger;
|
||||||
|
k8sRequest: K8sRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listApiResources = ({ k8sRequest, logger }: Dependencies): ListApiResources => {
|
||||||
|
return (cluster) => {
|
||||||
|
const clusterRequest = (path: string) => k8sRequest(cluster, path);
|
||||||
|
const apiLimit = plimit(5);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
const resources: KubeApiResource[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resourceListGroups:{ group:string;path:string }[] = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
[
|
||||||
|
clusterRequest("/api").then((response:V1APIVersions)=>response.versions.forEach(version => resourceListGroups.push({ group:version, path:`/api/${version}` }))),
|
||||||
|
clusterRequest("/apis").then((response:V1APIGroupList) => response.groups.forEach(group => {
|
||||||
|
const preferredVersion = group.preferredVersion?.groupVersion;
|
||||||
|
|
||||||
|
if (preferredVersion) {
|
||||||
|
resourceListGroups.push({ group:group.name, path:`/apis/${preferredVersion}` });
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
resourceListGroups.map(({ group, path }) => apiLimit(async () => {
|
||||||
|
const apiResources:V1APIResourceList = await clusterRequest(path);
|
||||||
|
|
||||||
|
if (apiResources.resources) {
|
||||||
|
resources.push(
|
||||||
|
...apiResources.resources.filter(resource => resource.verbs.includes("list")).map((resource) => ({
|
||||||
|
apiName: resource.name as KubeResource,
|
||||||
|
kind: resource.kind,
|
||||||
|
group,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listApiResourcesInjectable = getInjectable({
|
||||||
|
id: "list-api-resources",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const k8sRequest = di.inject(k8SRequestInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return listApiResources({ k8sRequest, logger });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default listApiResourcesInjectable;
|
||||||
@ -6,7 +6,6 @@
|
|||||||
export const clusterActivateHandler = "cluster:activate";
|
export const clusterActivateHandler = "cluster:activate";
|
||||||
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
||||||
export const clusterVisibilityHandler = "cluster:visibility";
|
export const clusterVisibilityHandler = "cluster:visibility";
|
||||||
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";
|
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||||
|
|||||||
@ -592,7 +592,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("requests the watch", () => {
|
it("requests the watch", () => {
|
||||||
expect(fetchMock.mock.lastCall).toMatchObject([
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
||||||
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=",
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600",
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
@ -606,7 +606,7 @@ describe("KubeApi", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await fetchMock.resolveSpecific(
|
await fetchMock.resolveSpecific(
|
||||||
([url, init]) => {
|
([url, init]) => {
|
||||||
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=";
|
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600";
|
||||||
|
|
||||||
if (isMatch) {
|
if (isMatch) {
|
||||||
init?.signal?.addEventListener("abort", () => {
|
init?.signal?.addEventListener("abort", () => {
|
||||||
@ -616,7 +616,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
return isMatch;
|
return isMatch;
|
||||||
},
|
},
|
||||||
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", stream),
|
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600", stream),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -688,7 +688,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("requests the watch", () => {
|
it("requests the watch", () => {
|
||||||
expect(fetchMock.mock.lastCall).toMatchObject([
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
||||||
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=",
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600",
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
@ -702,7 +702,7 @@ describe("KubeApi", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await fetchMock.resolveSpecific(
|
await fetchMock.resolveSpecific(
|
||||||
([url, init]) => {
|
([url, init]) => {
|
||||||
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=";
|
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600";
|
||||||
|
|
||||||
if (isMatch) {
|
if (isMatch) {
|
||||||
init?.signal?.addEventListener("abort", () => {
|
init?.signal?.addEventListener("abort", () => {
|
||||||
@ -712,7 +712,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
return isMatch;
|
return isMatch;
|
||||||
},
|
},
|
||||||
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", stream),
|
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600", stream),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import yaml from "js-yaml";
|
|
||||||
import { apiBaseInjectionToken } from "../../api-base";
|
import { apiBaseInjectionToken } from "../../api-base";
|
||||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||||
|
import type { AsyncResult } from "../../../utils/async-result";
|
||||||
|
|
||||||
interface HelmReleaseUpdatePayload {
|
interface HelmReleaseUpdatePayload {
|
||||||
repo: string;
|
repo: string;
|
||||||
@ -18,7 +18,7 @@ export type RequestHelmReleaseUpdate = (
|
|||||||
name: string,
|
name: string,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
payload: HelmReleaseUpdatePayload
|
payload: HelmReleaseUpdatePayload
|
||||||
) => Promise<{ updateWasSuccessful: true } | { updateWasSuccessful: false; error: unknown }>;
|
) => Promise<AsyncResult<void, unknown>>;
|
||||||
|
|
||||||
const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
||||||
|
|
||||||
@ -28,20 +28,20 @@ const requestHelmReleaseUpdateInjectable = getInjectable({
|
|||||||
instantiate: (di): RequestHelmReleaseUpdate => {
|
instantiate: (di): RequestHelmReleaseUpdate => {
|
||||||
const apiBase = di.inject(apiBaseInjectionToken);
|
const apiBase = di.inject(apiBaseInjectionToken);
|
||||||
|
|
||||||
return async (name, namespace, { repo, chart, values, ...data }) => {
|
return async (name, namespace, { repo, chart, values, version }) => {
|
||||||
try {
|
try {
|
||||||
await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), {
|
await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), {
|
||||||
data: {
|
data: {
|
||||||
chart: `${repo}/${chart}`,
|
chart: `${repo}/${chart}`,
|
||||||
values: yaml.load(values),
|
values,
|
||||||
...data,
|
version,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { updateWasSuccessful: false, error: e };
|
return { callWasSuccessful: false, error: e };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { updateWasSuccessful: true };
|
return { callWasSuccessful: true };
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import byline from "byline";
|
|||||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||||
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
|
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
|
||||||
import type { Disposer } from "../utils";
|
import type { Disposer } from "../utils";
|
||||||
import { setTimeoutFor, isDefined, noop, WrappedAbortController } from "../utils";
|
import { isDefined, noop, WrappedAbortController } from "../utils";
|
||||||
import type { RequestInit, Response } from "node-fetch";
|
import type { RequestInit, Response } from "node-fetch";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
@ -639,7 +639,7 @@ export class KubeApi<
|
|||||||
namespace,
|
namespace,
|
||||||
callback = noop as KubeApiWatchCallback<Data>,
|
callback = noop as KubeApiWatchCallback<Data>,
|
||||||
retry = false,
|
retry = false,
|
||||||
timeout,
|
timeout = 600,
|
||||||
watchId = `${this.kind.toLowerCase()}-${this.watchId++}`,
|
watchId = `${this.kind.toLowerCase()}-${this.watchId++}`,
|
||||||
} = opts ?? {};
|
} = opts ?? {};
|
||||||
|
|
||||||
@ -651,8 +651,6 @@ export class KubeApi<
|
|||||||
clearTimeout(timedRetry);
|
clearTimeout(timedRetry);
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeoutFor(abortController, 600 * 1000);
|
|
||||||
|
|
||||||
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
|
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
|
||||||
const watchUrl = this.getWatchUrl(namespace);
|
const watchUrl = this.getWatchUrl(namespace);
|
||||||
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
const responsePromise = this.request.getResponse(watchUrl, requestParams, {
|
||||||
@ -695,8 +693,10 @@ export class KubeApi<
|
|||||||
}, timeout * 1000 * 1.1);
|
}, timeout * 1000 * 1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!response.body || !response.body.readable) {
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
this.dependencies.logger.error(`[KUBE-API]: watch (${watchId}) did not return a body`);
|
this.dependencies.logger.warn(`[KUBE-API]: watch (${watchId}) did not return a body`);
|
||||||
|
}
|
||||||
requestRetried = true;
|
requestRetried = true;
|
||||||
|
|
||||||
clearTimeout(timedRetry);
|
clearTimeout(timedRetry);
|
||||||
|
|||||||
@ -169,7 +169,10 @@ describe("add custom helm repository in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "add", "some-custom-repository", "http://some.url"],
|
["repo", "add", "some-custom-repository", "http://some.url"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -373,7 +376,10 @@ describe("add custom helm repository in preferences", () => {
|
|||||||
"--cert-file",
|
"--cert-file",
|
||||||
"some-cert-file",
|
"some-cert-file",
|
||||||
],
|
],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -118,7 +118,10 @@ describe("add helm repository from list in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "add", "Some to be added repository", "some-other-url"],
|
["repo", "add", "Some to be added repository", "some-other-url"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,7 +235,10 @@ describe("add helm repository from list in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "remove", "Some already active repository"],
|
["repo", "remove", "Some already active repository"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -69,7 +69,10 @@ describe("listing active helm repositories in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["env"],
|
["env"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +80,10 @@ describe("listing active helm repositories in preferences", () => {
|
|||||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "update"],
|
["repo", "update"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,7 +228,10 @@ describe("listing active helm repositories in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "update"],
|
["repo", "update"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -289,7 +298,10 @@ describe("listing active helm repositories in preferences", () => {
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -434,7 +446,10 @@ describe("listing active helm repositories in preferences", () => {
|
|||||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -85,7 +85,10 @@ describe("remove helm repository from list of active repositories in preferences
|
|||||||
expect(execFileMock).toHaveBeenCalledWith(
|
expect(execFileMock).toHaveBeenCalledWith(
|
||||||
"some-helm-binary-path",
|
"some-helm-binary-path",
|
||||||
["repo", "remove", "some-active-repository"],
|
["repo", "remove", "some-active-repository"],
|
||||||
{ "maxBuffer": 34359738368 },
|
{
|
||||||
|
maxBuffer: 34359738368,
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -552,7 +552,7 @@ describe("showing details for helm release", () => {
|
|||||||
requestHelmReleaseConfigurationMock.mockClear();
|
requestHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
await requestHelmReleaseUpdateMock.resolve({
|
await requestHelmReleaseUpdateMock.resolve({
|
||||||
updateWasSuccessful: true,
|
callWasSuccessful: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -591,7 +591,7 @@ describe("showing details for helm release", () => {
|
|||||||
requestHelmReleaseConfigurationMock.mockClear();
|
requestHelmReleaseConfigurationMock.mockClear();
|
||||||
|
|
||||||
await requestHelmReleaseUpdateMock.resolve({
|
await requestHelmReleaseUpdateMock.resolve({
|
||||||
updateWasSuccessful: false,
|
callWasSuccessful: false,
|
||||||
error: "some-error",
|
error: "some-error",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
|||||||
import type { CreateCluster } from "../../common/cluster/create-cluster-injection-token";
|
import type { CreateCluster } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
||||||
|
import authorizationNamespaceReviewInjectable from "../../common/cluster/authorization-namespace-review.injectable";
|
||||||
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||||
@ -19,6 +20,8 @@ import directoryForTempInjectable from "../../common/app-paths/directory-for-tem
|
|||||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||||
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
|
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
|
||||||
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
|
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
|
||||||
|
import { apiResourceRecord, apiResources } from "../../common/rbac";
|
||||||
|
import listApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable";
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
@ -39,6 +42,8 @@ describe("create clusters", () => {
|
|||||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
di.override(broadcastMessageInjectable, () => async () => {});
|
di.override(broadcastMessageInjectable, () => async () => {});
|
||||||
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
||||||
|
di.override(authorizationNamespaceReviewInjectable, () => () => () => Promise.resolve(Object.keys(apiResourceRecord)));
|
||||||
|
di.override(listApiResourcesInjectable, () => () => () => Promise.resolve(apiResources));
|
||||||
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||||
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
||||||
restartServer: jest.fn(),
|
restartServer: jest.fn(),
|
||||||
|
|||||||
@ -11,7 +11,9 @@ import createKubectlInjectable from "../kubectl/create-kubectl.injectable";
|
|||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
||||||
|
import createAuthorizationNamespaceReview from "../../common/cluster/authorization-namespace-review.injectable";
|
||||||
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
|
import createListApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
||||||
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
||||||
@ -28,6 +30,8 @@ const createClusterInjectable = getInjectable({
|
|||||||
createKubectl: di.inject(createKubectlInjectable),
|
createKubectl: di.inject(createKubectlInjectable),
|
||||||
createContextHandler: di.inject(createContextHandlerInjectable),
|
createContextHandler: di.inject(createContextHandlerInjectable),
|
||||||
createAuthorizationReview: di.inject(authorizationReviewInjectable),
|
createAuthorizationReview: di.inject(authorizationReviewInjectable),
|
||||||
|
createAuthorizationNamespaceReview: di.inject(createAuthorizationNamespaceReview),
|
||||||
|
createListApiResources: di.inject(createListApiResourcesInjectable),
|
||||||
createListNamespaces: di.inject(listNamespacesInjectable),
|
createListNamespaces: di.inject(listNamespacesInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
detectorRegistry: di.inject(detectorRegistryInjectable),
|
detectorRegistry: di.inject(detectorRegistryInjectable),
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import type { IpcMainInvokeEvent } from "electron";
|
import type { IpcMainInvokeEvent } from "electron";
|
||||||
import { BrowserWindow, Menu } from "electron";
|
import { BrowserWindow, Menu } from "electron";
|
||||||
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
||||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster";
|
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
||||||
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
|
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
|
||||||
@ -68,12 +68,6 @@ export const setupIpcMainHandlers = ({
|
|||||||
clusterManager.visibleCluster = clusterId;
|
clusterManager.visibleCluster = clusterId;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMainHandle(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
|
||||||
return ClusterStore.getInstance()
|
|
||||||
.getById(clusterId)
|
|
||||||
?.refresh({ refreshMetadata: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMainHandle(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
ipcMainHandle(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
||||||
emitAppEvent({ name: "cluster", action: "stop" });
|
emitAppEvent({ name: "cluster", action: "stop" });
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||||
|
import execHelmEnvInjectable from "./exec-env.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(execHelmEnvInjectable, () => computed(() => ({})));
|
||||||
26
src/main/helm/exec-helm/exec-env.injectable.ts
Normal file
26
src/main/helm/exec-helm/exec-env.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
||||||
|
|
||||||
|
const execHelmEnvInjectable = getInjectable({
|
||||||
|
id: "exec-helm-env",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const {
|
||||||
|
HTTPS_PROXY = userStore.httpsProxy,
|
||||||
|
...env
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
return { HTTPS_PROXY, ...env } as Partial<Record<string, string>>;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default execHelmEnvInjectable;
|
||||||
@ -7,6 +7,7 @@ import type { ExecFileException } from "child_process";
|
|||||||
import execFileInjectable from "../../../common/fs/exec-file.injectable";
|
import execFileInjectable from "../../../common/fs/exec-file.injectable";
|
||||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||||
import helmBinaryPathInjectable from "../helm-binary-path.injectable";
|
import helmBinaryPathInjectable from "../helm-binary-path.injectable";
|
||||||
|
import execHelmEnvInjectable from "./exec-env.injectable";
|
||||||
|
|
||||||
export type ExecHelm = (args: string[]) => Promise<AsyncResult<string, ExecFileException & { stderr: string }>>;
|
export type ExecHelm = (args: string[]) => Promise<AsyncResult<string, ExecFileException & { stderr: string }>>;
|
||||||
|
|
||||||
@ -15,10 +16,12 @@ const execHelmInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di): ExecHelm => {
|
instantiate: (di): ExecHelm => {
|
||||||
const execFile = di.inject(execFileInjectable);
|
const execFile = di.inject(execFileInjectable);
|
||||||
|
const execHelmEnv = di.inject(execHelmEnvInjectable);
|
||||||
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
||||||
|
|
||||||
return async (args) => execFile(helmBinaryPath, args, {
|
return async (args) => execFile(helmBinaryPath, args, {
|
||||||
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
||||||
|
env: execHelmEnv.get(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,16 +5,15 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { Cluster } from "../../../common/cluster/cluster";
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import type { JsonObject } from "type-fest";
|
|
||||||
import { execHelm } from "../exec";
|
|
||||||
import tempy from "tempy";
|
import tempy from "tempy";
|
||||||
import fse from "fs-extra";
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import getHelmReleaseInjectable from "./get-helm-release.injectable";
|
import getHelmReleaseInjectable from "./get-helm-release.injectable";
|
||||||
|
import writeFileInjectable from "../../../common/fs/write-file.injectable";
|
||||||
|
import removePathInjectable from "../../../common/fs/remove-path.injectable";
|
||||||
|
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
|
||||||
|
|
||||||
export interface UpdateChartArgs {
|
export interface UpdateChartArgs {
|
||||||
chart: string;
|
chart: string;
|
||||||
values: JsonObject;
|
values: string;
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,17 +23,20 @@ const updateHelmReleaseInjectable = getInjectable({
|
|||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const getHelmRelease = di.inject(getHelmReleaseInjectable);
|
const getHelmRelease = di.inject(getHelmReleaseInjectable);
|
||||||
|
const writeFile = di.inject(writeFileInjectable);
|
||||||
|
const removePath = di.inject(removePathInjectable);
|
||||||
|
const execHelm = di.inject(execHelmInjectable);
|
||||||
|
|
||||||
return async (cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) => {
|
return async (cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) => {
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
logger.debug("Upgrade release");
|
|
||||||
|
|
||||||
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||||
|
|
||||||
await fse.writeFile(valuesFilePath, yaml.dump(data.values));
|
logger.debug(`[HELM]: upgrading "${releaseName}" in "${namespace}" to ${data.version}`);
|
||||||
|
|
||||||
const args = [
|
try {
|
||||||
|
await writeFile(valuesFilePath, data.values);
|
||||||
|
|
||||||
|
const result = await execHelm([
|
||||||
"upgrade",
|
"upgrade",
|
||||||
releaseName,
|
releaseName,
|
||||||
data.chart,
|
data.chart,
|
||||||
@ -42,22 +44,21 @@ const updateHelmReleaseInjectable = getInjectable({
|
|||||||
"--values", valuesFilePath,
|
"--values", valuesFilePath,
|
||||||
"--namespace", namespace,
|
"--namespace", namespace,
|
||||||
"--kubeconfig", proxyKubeconfig,
|
"--kubeconfig", proxyKubeconfig,
|
||||||
];
|
]);
|
||||||
|
|
||||||
try {
|
if (result.callWasSuccessful === false) {
|
||||||
const output = await execHelm(args);
|
throw result.error; // keep the same interface
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
log: output,
|
log: result.response,
|
||||||
release: await getHelmRelease(cluster, releaseName, namespace),
|
release: await getHelmRelease(cluster, releaseName, namespace),
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
await fse.unlink(valuesFilePath);
|
await removePath(valuesFilePath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default updateHelmReleaseInjectable;
|
export default updateHelmReleaseInjectable;
|
||||||
|
|||||||
@ -17,8 +17,8 @@ const updateChartArgsValidator = Joi.object<UpdateChartArgs, true, UpdateChartAr
|
|||||||
.string()
|
.string()
|
||||||
.required(),
|
.required(),
|
||||||
values: Joi
|
values: Joi
|
||||||
.object()
|
.string()
|
||||||
.unknown(true),
|
.required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateReleaseRouteInjectable = getRouteInjectable({
|
const updateReleaseRouteInjectable = getRouteInjectable({
|
||||||
|
|||||||
@ -16,3 +16,9 @@
|
|||||||
.settingsAvatar {
|
.settingsAvatar {
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatarAndName {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--margin);
|
||||||
|
}
|
||||||
@ -86,7 +86,7 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center pb-8">
|
<div className={styles.avatarAndName}>
|
||||||
<Avatar
|
<Avatar
|
||||||
title={entity.getName()}
|
title={entity.getName()}
|
||||||
colorHash={`${entity.getName()}-${entity.metadata.source}`}
|
colorHash={`${entity.getName()}-${entity.metadata.source}`}
|
||||||
|
|||||||
@ -14,10 +14,7 @@ const helmChartVersionsInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di, release) => {
|
instantiate: (di, release) => {
|
||||||
const helmCharts = di.inject(helmChartsInjectable);
|
const helmCharts = di.inject(helmChartsInjectable);
|
||||||
|
const requestVersionsOfHelmChart = di.inject(requestVersionsOfHelmChartInjectable);
|
||||||
const requestVersionsOfHelmChart = di.inject(
|
|
||||||
requestVersionsOfHelmChartInjectable,
|
|
||||||
);
|
|
||||||
|
|
||||||
return asyncComputed({
|
return asyncComputed({
|
||||||
getValueFromObservedPromise: async () => {
|
getValueFromObservedPromise: async () => {
|
||||||
@ -25,8 +22,6 @@ const helmChartVersionsInjectable = getInjectable({
|
|||||||
|
|
||||||
return requestVersionsOfHelmChart(release, helmCharts.value.get());
|
return requestVersionsOfHelmChart(release, helmCharts.value.get());
|
||||||
},
|
},
|
||||||
|
|
||||||
valueWhenPending: [],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -128,7 +128,7 @@ export class ReleaseDetailsModel {
|
|||||||
this.configuration.isSaving.set(false);
|
this.configuration.isSaving.set(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.updateWasSuccessful) {
|
if (!result.callWasSuccessful) {
|
||||||
this.dependencies.showCheckedErrorNotification(
|
this.dependencies.showCheckedErrorNotification(
|
||||||
result.error,
|
result.error,
|
||||||
"Unknown error occured while updating release",
|
"Unknown error occured while updating release",
|
||||||
|
|||||||
@ -12,14 +12,14 @@ const updateReleaseInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di): RequestHelmReleaseUpdate => {
|
instantiate: (di): RequestHelmReleaseUpdate => {
|
||||||
const releases = di.inject(releasesInjectable);
|
const releases = di.inject(releasesInjectable);
|
||||||
const callForHelmReleaseUpdate = di.inject(requestHelmReleaseUpdateInjectable);
|
const requestHelmReleaseUpdate = di.inject(requestHelmReleaseUpdateInjectable);
|
||||||
|
|
||||||
return async (
|
return async (
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
payload,
|
payload,
|
||||||
) => {
|
) => {
|
||||||
const result = await callForHelmReleaseUpdate(name, namespace, payload);
|
const result = await requestHelmReleaseUpdate(name, namespace, payload);
|
||||||
|
|
||||||
releases.invalidate();
|
releases.invalidate();
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import type { ValidateDirectory } from "../../../../common/fs/validate-directory
|
|||||||
import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable";
|
import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable";
|
||||||
import type { ResolveTilde } from "../../../../common/path/resolve-tilde.injectable";
|
import type { ResolveTilde } from "../../../../common/path/resolve-tilde.injectable";
|
||||||
import resolveTildeInjectable from "../../../../common/path/resolve-tilde.injectable";
|
import resolveTildeInjectable from "../../../../common/path/resolve-tilde.injectable";
|
||||||
|
import Gutter from "../../gutter/gutter";
|
||||||
|
|
||||||
export interface ClusterLocalTerminalSettingProps {
|
export interface ClusterLocalTerminalSettingProps {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
@ -139,6 +140,7 @@ const NonInjectedClusterLocalTerminalSetting = observer(({
|
|||||||
this is used as the current working directory (cwd) for the shell process.
|
this is used as the current working directory (cwd) for the shell process.
|
||||||
</small>
|
</small>
|
||||||
</section>
|
</section>
|
||||||
|
<Gutter />
|
||||||
<section className="default-namespace">
|
<section className="default-namespace">
|
||||||
<SubTitle title="Default Namespace"/>
|
<SubTitle title="Default Namespace"/>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { Input } from "../../input/input";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Icon } from "../../icon/icon";
|
import { Icon } from "../../icon/icon";
|
||||||
import { initialNodeShellImage } from "../../../../common/cluster-types";
|
import { initialNodeShellImage } from "../../../../common/cluster-types";
|
||||||
|
import Gutter from "../../gutter/gutter";
|
||||||
|
|
||||||
export interface ClusterNodeShellSettingProps {
|
export interface ClusterNodeShellSettingProps {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
@ -59,6 +60,7 @@ export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSet
|
|||||||
Node shell image. Used for creating node shell pod.
|
Node shell image. Used for creating node shell pod.
|
||||||
</small>
|
</small>
|
||||||
</section>
|
</section>
|
||||||
|
<Gutter />
|
||||||
<section>
|
<section>
|
||||||
<SubTitle title="Image pull secret" id="image-pull-secret"/>
|
<SubTitle title="Image pull secret" id="image-pull-secret"/>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import releasesInjectable from "../../+helm-releases/releases.injectable";
|
|||||||
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
|
||||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import requestHelmReleaseConfigurationInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
|
import requestHelmReleaseConfigurationInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
|
||||||
|
import type { AsyncResult } from "../../../../common/utils/async-result";
|
||||||
import { waitUntilDefined } from "../../../utils";
|
import { waitUntilDefined } from "../../../utils";
|
||||||
import type { SelectOption } from "../../select";
|
import type { SelectOption } from "../../select";
|
||||||
import type { DockTab } from "../dock/store";
|
import type { DockTab } from "../dock/store";
|
||||||
@ -31,11 +32,7 @@ export interface UpgradeChartModel {
|
|||||||
readonly value: IComputedValue<HelmChartVersion | undefined>;
|
readonly value: IComputedValue<HelmChartVersion | undefined>;
|
||||||
set: (value: SingleValue<SelectOption<HelmChartVersion>>) => void;
|
set: (value: SingleValue<SelectOption<HelmChartVersion>>) => void;
|
||||||
};
|
};
|
||||||
submit: () => Promise<UpgradeChartSubmitResult>;
|
submit: () => Promise<AsyncResult<void, string>>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpgradeChartSubmitResult {
|
|
||||||
completedSuccessfully: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgradeChartModelInjectable = getInjectable({
|
const upgradeChartModelInjectable = getInjectable({
|
||||||
@ -105,13 +102,23 @@ const upgradeChartModelInjectable = getInjectable({
|
|||||||
submit: async () => {
|
submit: async () => {
|
||||||
const selectedVersion = version.value.get();
|
const selectedVersion = version.value.get();
|
||||||
|
|
||||||
if (!selectedVersion || configrationEditError.get()) {
|
if (!selectedVersion) {
|
||||||
return {
|
return {
|
||||||
completedSuccessfully: false,
|
callWasSuccessful: false,
|
||||||
|
error: "No selected version",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateRelease(
|
const editError = configrationEditError.get();
|
||||||
|
|
||||||
|
if (editError) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: editError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateRelease(
|
||||||
release.getName(),
|
release.getName(),
|
||||||
release.getNs(),
|
release.getNs(),
|
||||||
{
|
{
|
||||||
@ -120,10 +127,16 @@ const upgradeChartModelInjectable = getInjectable({
|
|||||||
...selectedVersion,
|
...selectedVersion,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (result.callWasSuccessful === true) {
|
||||||
storedConfiguration.invalidate();
|
storedConfiguration.invalidate();
|
||||||
|
|
||||||
|
return { callWasSuccessful: true };
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
completedSuccessfully: true,
|
callWasSuccessful: false,
|
||||||
|
error: String(result.error),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -33,20 +33,20 @@ interface Dependencies {
|
|||||||
export class NonInjectedUpgradeChart extends React.Component<UpgradeChartProps & Dependencies> {
|
export class NonInjectedUpgradeChart extends React.Component<UpgradeChartProps & Dependencies> {
|
||||||
upgrade = async () => {
|
upgrade = async () => {
|
||||||
const { model } = this.props;
|
const { model } = this.props;
|
||||||
const { completedSuccessfully } = await model.submit();
|
const result = await model.submit();
|
||||||
|
|
||||||
if (completedSuccessfully) {
|
if (result.callWasSuccessful) {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{"Release "}
|
{"Release "}
|
||||||
<b>{model.release.getName()}</b>
|
<b>{model.release.getName()}</b>
|
||||||
{" successfully upgraded to version "}
|
{" successfully upgraded to version "}
|
||||||
<b>{model.version.value.get()}</b>
|
<b>{model.version.value.get()?.version}</b>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
throw result.error;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -27,7 +27,9 @@ const createClusterInjectable = getInjectable({
|
|||||||
createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");},
|
createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");},
|
||||||
createContextHandler: () => undefined as never,
|
createContextHandler: () => undefined as never,
|
||||||
createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
||||||
|
createAuthorizationNamespaceReview: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
||||||
createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
||||||
|
createListApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); },
|
||||||
detectorRegistry: undefined as never,
|
detectorRegistry: undefined as never,
|
||||||
createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); },
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5100,10 +5100,10 @@ electron-window-state@^5.0.3:
|
|||||||
jsonfile "^4.0.0"
|
jsonfile "^4.0.0"
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
|
|
||||||
electron@^19.1.6:
|
electron@^19.1.7:
|
||||||
version "19.1.6"
|
version "19.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-19.1.6.tgz#32443cd293d3d877cd3d224e45880e3fbf264e49"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-19.1.7.tgz#35036a510d9ca943d271e1d1a12463547ed5cd12"
|
||||||
integrity sha512-bT6Mr7JbHbONpr/U7R47lwTkMUvuAyOfnoLlbDqvGocQyZCCN3JB436wtf2+r3/IpMEz3T+dHLweFDY5i2wuxw==
|
integrity sha512-U5rCktIm/EeRjfg/9QFo29jzvZVV2z8Xw7r2NdGTpljmjd+7kySHvUHthO2hk8HETILJivL4+R5lF9zxcJ2J9w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get" "^1.14.1"
|
"@electron/get" "^1.14.1"
|
||||||
"@types/node" "^16.11.26"
|
"@types/node" "^16.11.26"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user