Skip to content

Commit

Permalink
Slow loading of the OpenShift explorer caused by duplicate oc commands
Browse files Browse the repository at this point in the history
…#4289

Fixes: #4289

Signed-off-by: Victor Rubezhny <[email protected]>
  • Loading branch information
vrubezhny committed Jul 19, 2024
1 parent deb9d79 commit 767f770
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 142 deletions.
33 changes: 29 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
*-----------------------------------------------------------------------------------------------*/

import { VSCodeSettings } from '@redhat-developer/vscode-redhat-telemetry/lib/common/vscode/settings';
import * as vscode from 'vscode';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import { CommandText } from './base/command';
import { ToolsConfig } from './tools';
import { ChildProcessUtil, CliExitData } from './util/childProcessUtil';
import { VsCommandError } from './vscommand';

export class ExecutionContext extends Map<string, any> {
}

export class CliChannel {

private static telemetrySettings = new VSCodeSettings();
Expand Down Expand Up @@ -52,13 +55,23 @@ export class CliChannel {
return optsCopy;
}

async executeTool(command: CommandText, opts?: cp.ExecOptions, fail = true): Promise<CliExitData> {
async executeTool(command: CommandText, opts?: cp.ExecOptions, fail = true, executionContext?: ExecutionContext): Promise<CliExitData> {
const commandActual = command.toString();
const commandPrivacy = command.privacyMode(true).toString();
const [cmd] = commandActual.split(' ');
const toolLocation = await ToolsConfig.detect(cmd);
const optsCopy = CliChannel.applyEnv(opts, CliChannel.createTelemetryEnv())

if (executionContext && executionContext.has(commandActual)) {
return executionContext.get(commandActual);
}

const result: CliExitData = await ChildProcessUtil.Instance.execute(toolLocation ? commandActual.replace(cmd, `"${toolLocation}"`) : commandActual, optsCopy);

if (executionContext) {
executionContext.set(commandActual, result);
}

if (result.error && fail) {
if (result.error.code && result.error.code.toString() === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
void vscode.window.showErrorMessage('Do you want to change the maximum \'stdout\' buffer size by modifying the \'openshiftToolkit.execMaxBufferLength\' preference value?', 'Yes', 'Cancel')
Expand Down Expand Up @@ -86,7 +99,12 @@ export class CliChannel {
* @param opts Execution options that may be used to specify operation timeout, maximun buffer size, etc.
* @returns Output of from the command execution
*/
async executeSyncTool(cmd: CommandText, opts: cp.ExecFileOptions): Promise<string> {
async executeSyncTool(cmd: CommandText, opts: cp.ExecFileOptions, executionContext?: ExecutionContext): Promise<string> {
const key = cmd.toString();
if (executionContext && executionContext.has(key)) {
return executionContext.get(key);
}

const options = {
...opts,
} as cp.ExecFileOptionsWithStringEncoding
Expand All @@ -106,6 +124,13 @@ export class CliChannel {

const toolLocation = await ToolsConfig.detect(cmd.command);
const optWithTelemetryEnv = CliChannel.applyEnv(options, CliChannel.createTelemetryEnv()) as cp.ExecFileSyncOptionsWithStringEncoding;
return cp.execFileSync(toolLocation, [cmd.parameter, ...cmd.options.map((o)=>o.toString())], optWithTelemetryEnv);

const result: string = cp.execFileSync(toolLocation, [cmd.parameter, ...cmd.options.map((o)=>o.toString())], optWithTelemetryEnv);

if (executionContext) {
executionContext.set(key, result);
}

return result;
}
}
47 changes: 28 additions & 19 deletions src/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,22 @@ import {
workspace
} from 'vscode';
import { CommandOption, CommandText } from './base/command';
import { ExecutionContext } from './cli';
import * as Helm from './helm/helm';
import { HelmRepo } from './helm/helmChartType';
import { getOutputFormat, helmfsUri, kubefsUri } from './k8s/vfs/kuberesources.virtualfs';
import { Oc } from './oc/ocWrapper';
import { Component } from './openshift/component';
import { getServiceKindStubs, getServices } from './openshift/serviceHelpers';
import { PortForward } from './port-forward';
import { KubeConfigUtils, getKubeConfigFiles, getNamespaceKind } from './util/kubeUtils';
import { KubeConfigUtils, getKubeConfigFiles, getNamespaceKind, isOpenShiftCluster } from './util/kubeUtils';
import { LoginUtil } from './util/loginUtil';
import { Platform } from './util/platform';
import { Progress } from './util/progress';
import { FileContentChangeNotifier, WatchUtil } from './util/watch';
import { vsCommand } from './vscommand';
import { CustomResourceDefinitionStub, K8sResourceKind } from './webview/common/createServiceTypes';
import { OpenShiftTerminalManager } from './webview/openshift-terminal/openShiftTerminal';
import { getOutputFormat, helmfsUri, kubefsUri } from './k8s/vfs/kuberesources.virtualfs';

type ExplorerItem = KubernetesObject | Helm.HelmRelease | Context | TreeItem | OpenShiftObject | HelmRepo;

Expand Down Expand Up @@ -67,8 +68,8 @@ type PackageJSON = {
bugs: string;
};

async function createOrSetProjectItem(projectName: string): Promise<ExplorerItem> {
const kind = await getNamespaceKind();
async function createOrSetProjectItem(projectName: string, executionContext?: ExecutionContext): Promise<ExplorerItem> {
const kind = await getNamespaceKind(executionContext);
return {
label: `${projectName}`,
description: `Missing ${kind}. Create new or set active ${kind}`,
Expand Down Expand Up @@ -97,6 +98,8 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
private kubeContext: Context;
private kubeConfig: KubeConfigUtils;

private executionContext: ExecutionContext = new ExecutionContext();

private eventEmitter: EventEmitter<ExplorerItem | undefined> =
new EventEmitter<ExplorerItem | undefined>();

Expand Down Expand Up @@ -148,13 +151,13 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
return OpenShiftExplorer.instance;
}

private static generateOpenshiftProjectContextValue(namespace: string): Thenable<string> {
private generateOpenshiftProjectContextValue(namespace: string): Thenable<string> {
const contextValue = `openshift.project.${namespace}`;
const allTrue = arr => arr.every(Boolean);

return Promise.all([
Oc.Instance.canDeleteNamespace(namespace),
Oc.Instance.getProjects(true)
Oc.Instance.getProjects(true, this.executionContext)
.then((clusterProjects) => {
const existing = clusterProjects.find((project) => project.name === namespace);
return existing !== undefined;
Expand Down Expand Up @@ -229,7 +232,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
// otherwise it is a KubernetesObject instance
if ('kind' in element) {
if (element.kind === 'project') {
return OpenShiftExplorer.generateOpenshiftProjectContextValue(element.metadata.name)
return this.generateOpenshiftProjectContextValue(element.metadata.name)
.then(namespace => {
return {
contextValue: namespace,
Expand Down Expand Up @@ -428,7 +431,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
async getChildren(element?: ExplorerItem): Promise<ExplorerItem[]> {
let result: ExplorerItem[] = [];
// don't show Open In Developer Dashboard if not openshift cluster
const isOpenshiftCluster = await Oc.Instance.isOpenShiftCluster();
const isOpenshiftCluster = await isOpenShiftCluster(this.executionContext);
if (!element) {
try {
if (!await LoginUtil.Instance.requireLogin()) {
Expand All @@ -455,7 +458,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
// * example is sandbox context created when login to sandbox first time
// (3) there is namespace set in context and namespace exists in the cluster
// (4) there is namespace set in context and namespace does not exist in the cluster
const namespaces = await Oc.Instance.getProjects();
const namespaces = await Oc.Instance.getProjects(false, this.executionContext);
// Actually 'Oc.Instance.getProjects()' takes care of setting up at least one project as
// an active project, so here after it's enough just to search the array for it.
// The only case where there could be no active project set is empty projects array.
Expand All @@ -472,14 +475,14 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
} as KubernetesObject]
} else {
const projectName = this.kubeConfig.extractProjectNameFromCurrentContext() || 'default';
result = [await createOrSetProjectItem(projectName)];
result = [await createOrSetProjectItem(projectName, this.executionContext)];
}

// The 'Create Service' menu visibility
let serviceKinds: CustomResourceDefinitionStub[] = [];
try {
if (await Oc.Instance.canGetKubernetesObjects('csv')) {
serviceKinds = await getServiceKindStubs();
if (await Oc.Instance.canGetKubernetesObjects('csv', this.executionContext)) {
serviceKinds = await getServiceKindStubs(this.executionContext);
}
} catch (_) {
// operator framework is not installed on cluster; do nothing
Expand All @@ -489,7 +492,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
// The 'Create Route' menu visibility
let services: K8sResourceKind[] = [];
try {
services = await getServices();
services = await getServices(this.executionContext);
}
catch (_) {
// operator framework is not installed on cluster; do nothing
Expand Down Expand Up @@ -617,7 +620,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
break;
default:
try {
collections = await Oc.Instance.getKubernetesObjects(element.kind);
collections = await Oc.Instance.getKubernetesObjects(element.kind, undefined, undefined, this.executionContext);
} catch {
collections = [ couldNotGetItem(element.kind, this.kubeConfig.getCluster(this.kubeContext.cluster)?.server) ];
}
Expand All @@ -626,7 +629,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
const toCollect = [
collections,
...collectableServices
.map(serviceKind => Oc.Instance.getKubernetesObjects(serviceKind.name))
.map(serviceKind => Oc.Instance.getKubernetesObjects(serviceKind.name, undefined, undefined, this.executionContext))
];
result = await Promise.all(toCollect).then(listOfLists => listOfLists.flatMap(a => a as ExplorerItem[]));
}
Expand All @@ -641,27 +644,33 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
private async getServiceKinds() {
let serviceKinds: CustomResourceDefinitionStub[] = [];
try {
if (await Oc.Instance.canGetKubernetesObjects('csv')) {
serviceKinds = await getServiceKindStubs();
if (await Oc.Instance.canGetKubernetesObjects('csv', this.executionContext)) {
serviceKinds = await getServiceKindStubs(this.executionContext);
}
} catch (_) {
// operator framework is not installed on cluster; do nothing
}

const collectableServices: CustomResourceDefinitionStub[] = [];
await Promise.all(serviceKinds.map(async (serviceKind) => {
if (await Oc.Instance.canGetKubernetesObjects(serviceKind.name)) {
if (await Oc.Instance.canGetKubernetesObjects(serviceKind.name, this.executionContext)) {
collectableServices.push(serviceKind);
}
}));
return collectableServices;
}

public async getPods(element: KubernetesObject | OpenShiftObject) {
return await Oc.Instance.getKubernetesObjects('pods', undefined, element.metadata.name);
return await Oc.Instance.getKubernetesObjects('pods', undefined, element.metadata.name, this.executionContext);
}

refresh(target?: ExplorerItem): void {
// Create new Execution Context before refreshing
if (this.executionContext) {
this.executionContext.clear();
}
this.executionContext = new ExecutionContext();

this.eventEmitter.fire(target);
}

Expand Down
26 changes: 13 additions & 13 deletions src/k8s/clusterExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import * as vscode from 'vscode';
import * as k8s from 'vscode-kubernetes-tools-api';
import { CommandText } from '../base/command';
import { CliChannel } from '../cli';
import { isOpenShiftCluster } from '../util/kubeUtils';
import { Build } from './build';
import { ClusterServiceVersion } from './csv';
import { DeploymentConfig } from './deploymentConfig';
import path = require('path');
import { ClusterServiceVersion } from './csv';
import { isOpenShift } from '../util/kubeUtils';
import { CommandText } from '../base/command';
import { CliChannel } from '../cli';

let clusterExplorer: k8s.ClusterExplorerV1 | undefined;

Expand All @@ -30,7 +30,7 @@ async function initNamespaceName(node: k8s.ClusterExplorerV1.ClusterExplorerReso
async function customizeAsync(node: k8s.ClusterExplorerV1.ClusterExplorerResourceNode, treeItem: vscode.TreeItem): Promise<void> {
if ((node as any).nodeType === 'context') {
lastNamespace = await initNamespaceName(node);
if (await isOpenShift()) {
if (await isOpenShiftCluster()) {
treeItem.iconPath = vscode.Uri.file(path.join(__dirname, '../../../images/context/cluster-node.png'));
}
}
Expand Down Expand Up @@ -59,14 +59,14 @@ export async function extendClusterExplorer(): Promise<void> {
if (clusterExplorerAPI.available) {
clusterExplorer = clusterExplorerAPI.api;
const nodeContributors = [
clusterExplorer.nodeSources.resourceFolder('Projects', 'Projects', 'Project', 'project').if(isOpenShift).at(undefined),
clusterExplorer.nodeSources.resourceFolder('Templates', 'Templates', 'Template', 'template').if(isOpenShift).at(undefined),
clusterExplorer.nodeSources.resourceFolder('ImageStreams', 'ImageStreams', 'ImageStream', 'ImageStream').if(isOpenShift).at('Workloads'),
clusterExplorer.nodeSources.resourceFolder('Routes', 'Routes', 'Route', 'route').if(isOpenShift).at('Network'),
clusterExplorer.nodeSources.resourceFolder('DeploymentConfigs', 'DeploymentConfigs', 'DeploymentConfig', 'dc').if(isOpenShift).at('Workloads'),
clusterExplorer.nodeSources.resourceFolder('BuildConfigs', 'BuildConfigs', 'BuildConfig', 'bc').if(isOpenShift).at('Workloads'),
clusterExplorer.nodeSources.groupingFolder('Operators', 'Operators').if(isOpenShift).at(undefined),
clusterExplorer.nodeSources.resourceFolder('ClusterServiceVersion', 'ClusterServiceVersions', 'ClusterServiceVersion', 'csv').if(isOpenShift).at('Operators'),
clusterExplorer.nodeSources.resourceFolder('Projects', 'Projects', 'Project', 'project').if(isOpenShiftCluster).at(undefined),
clusterExplorer.nodeSources.resourceFolder('Templates', 'Templates', 'Template', 'template').if(isOpenShiftCluster).at(undefined),
clusterExplorer.nodeSources.resourceFolder('ImageStreams', 'ImageStreams', 'ImageStream', 'ImageStream').if(isOpenShiftCluster).at('Workloads'),
clusterExplorer.nodeSources.resourceFolder('Routes', 'Routes', 'Route', 'route').if(isOpenShiftCluster).at('Network'),
clusterExplorer.nodeSources.resourceFolder('DeploymentConfigs', 'DeploymentConfigs', 'DeploymentConfig', 'dc').if(isOpenShiftCluster).at('Workloads'),
clusterExplorer.nodeSources.resourceFolder('BuildConfigs', 'BuildConfigs', 'BuildConfig', 'bc').if(isOpenShiftCluster).at('Workloads'),
clusterExplorer.nodeSources.groupingFolder('Operators', 'Operators').if(isOpenShiftCluster).at(undefined),
clusterExplorer.nodeSources.resourceFolder('ClusterServiceVersion', 'ClusterServiceVersions', 'ClusterServiceVersion', 'csv').if(isOpenShiftCluster).at('Operators'),
Build.getNodeContributor(),
DeploymentConfig.getNodeContributor(),
ClusterServiceVersion.getNodeContributor()
Expand Down
Loading

0 comments on commit 767f770

Please sign in to comment.