Skip to content

Commit

Permalink
Get rid of using k8s extension provided 'kubectl' binary
Browse files Browse the repository at this point in the history
The 'oc' binary is used instead in non-blocking manner, especially when getting 'api-versions' values.
Partially fixes #3987.

Issue: #3987

Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
  • Loading branch information
vrubezhny authored and datho7561 committed Apr 15, 2024
1 parent 74279d3 commit c3a9b91
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 78 deletions.
8 changes: 4 additions & 4 deletions src/k8s/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export class Build {
}
if (buildName) {
result = Progress.execFunctionWithProgress('Starting build', () =>
Build.cli.executeTool(Build.command.startBuild(buildName)),
)
Build.cli.executeTool(Build.command.startBuild(buildName)),
)
.then(() => `Build '${buildName}' successfully started`)
.catch((err) =>
Promise.reject(
Expand Down Expand Up @@ -186,8 +186,8 @@ export class Build {
const build = await Build.selectBuild(context, 'Select a build to delete');
if (build) {
result = Progress.execFunctionWithProgress('Deleting build', () =>
Build.cli.executeTool(Build.command.delete(build)),
)
Build.cli.executeTool(Build.command.delete(build))
)
.then(() => `Build '${build}' successfully deleted`)
.catch((err) =>
Promise.reject(
Expand Down
19 changes: 9 additions & 10 deletions src/k8s/clusterExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@ 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;

let lastNamespace = '';

async function initNamespaceName(node: k8s.ClusterExplorerV1.ClusterExplorerResourceNode): Promise<string | undefined> {
const kubectl = await k8s.extension.kubectl.v1;
if (kubectl.available) {
const result = await kubectl.api.invokeCommand('config view -o json');
const config = JSON.parse(result.stdout);
const currentContext = (config.contexts || []).find((ctx) => ctx.name === node.name);
if (!currentContext) {
return '';
}
return currentContext.context.namespace || 'default';
}
const result = await CliChannel.getInstance().executeTool(new CommandText('oc', 'config view -o json'));
const config = JSON.parse(result.stdout);
const currentContext = (config.contexts || []).find((ctx) => ctx.name === node.name);
if (!currentContext) {
return '';
}
return currentContext.context.namespace || 'default';
}

async function customizeAsync(node: k8s.ClusterExplorerV1.ClusterExplorerResourceNode, treeItem: vscode.TreeItem): Promise<void> {
Expand Down
12 changes: 6 additions & 6 deletions src/k8s/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ function convertItemToQuickPick(item: any): QuickPickItem {

export async function getQuickPicks(cmd: CommandText, errorMessage: string, converter: (item: any) => QuickPickItem = convertItemToQuickPick): Promise<QuickPickItem[]> {
const result = await CliChannel.getInstance().executeTool(cmd);
const json = JSON.parse(result.stdout);
if (json.items.length === 0) {
const json = result ? JSON.parse(result.stdout) : undefined;
if (!json || json.items.length === 0) {
throw new VsCommandError(errorMessage);
}
return json.items.map(converter);
Expand All @@ -32,13 +32,13 @@ export async function selectResourceByName(config: Promise<QuickPickItem[]> | Qu
}

export async function getChildrenNode(command: CommandText, kind: string, abbreviation: string): Promise<k8s.ClusterExplorerV1.Node[]> {
const kubectl = await k8s.extension.kubectl.v1;
if (kubectl.available) {
const result = await kubectl.api.invokeCommand(`${command}`);
try {
const result = await CliChannel.getInstance().executeTool(command);
const builds = result.stdout.split('\n')
.filter((value) => value !== '')
.map<Node>((item: string) => new Node(item.split(',')[0], item.split(',')[1], Number.parseInt(item.split(',')[2], 10), kind, abbreviation));
return builds;
} catch (error) {
return [];
}
return [];
}
21 changes: 9 additions & 12 deletions src/serverlessFunction/knative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import * as k8s from 'vscode-kubernetes-tools-api';
import { CommandText } from '../base/command';
import { CliChannel } from '../cli';

/**
* Returns true if the cluster has the Knative Serving CRDs, and false otherwise.
*
* @returns true if the cluster has the Knative Serving CRDs, and false otherwise
*/
export async function isKnativeServingAware(): Promise<boolean> {
const kubectl = await k8s.extension.kubectl.v1;
let isKnative = false;
if (kubectl.available) {
const sr = await kubectl.api.invokeCommand('api-versions');
isKnative =
sr &&
sr.code === 0 &&
(sr.stdout.includes('serving.knative.dev/v1') ||
sr.stdout.includes('serving.knative.dev/v1alpha1') ||
sr.stdout.includes('serving.knative.dev/v1beta1'));
try {
const stdout = await CliChannel.getInstance().executeSyncTool(new CommandText('oc', 'api-versions'), { timeout: 5000 });
return stdout.includes('serving.knative.dev/v1') ||
stdout.includes('serving.knative.dev/v1alpha1') ||
stdout.includes('serving.knative.dev/v1beta1')
} catch(error) {
return false;
}
return isKnative;
}
14 changes: 7 additions & 7 deletions src/tekton/tekton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import * as k8s from 'vscode-kubernetes-tools-api';
import { CommandText } from '../base/command';
import { CliChannel } from '../cli';

/**
* Returns true if the cluster has the Tekton CRDs, and false otherwise.
*
* @returns true if the cluster has the Tekton CRDs, and false otherwise
*/
export async function isTektonAware(): Promise<boolean> {
const kubectl = await k8s.extension.kubectl.v1;
let isTekton = false;
if (kubectl.available) {
const sr = await kubectl.api.invokeCommand('api-versions');
isTekton = sr && sr.code === 0 && sr.stdout.includes('tekton.dev/v1beta1');
try {
const stdout = await CliChannel.getInstance().executeSyncTool(new CommandText('oc', 'api-versions'), { timeout: 5000 });
return stdout.includes('tekton.dev/v1beta1');
} catch(error) {
return false;
}
return isTekton;
}
14 changes: 7 additions & 7 deletions src/util/kubeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import * as fs from 'fs';
import * as path from 'path';
import { QuickPickItem, window } from 'vscode';
import { Platform } from './platform';
import * as k8s from 'vscode-kubernetes-tools-api';
import { CommandText } from '../base/command';
import { CliChannel } from '../cli';

function fileExists(file: string): boolean {
try {
Expand Down Expand Up @@ -184,13 +185,12 @@ export async function setKubeConfig(): Promise<void> {
}

export async function isOpenShift(): Promise<boolean> {
const kubectl = await k8s.extension.kubectl.v1;
let isOS = false;
if (kubectl.available) {
const sr = await kubectl.api.invokeCommand('api-versions');
isOS = sr && sr.code === 0 && sr.stdout.includes('apps.openshift.io/v1');
try {
const stdout = await CliChannel.getInstance().executeSyncTool(new CommandText('oc', 'api-versions'), { timeout: 5000 });
return stdout.includes('apps.openshift.io/v1');
} catch(error) {
return false;
}
return isOS;
}

export async function getNamespaceKind(): Promise<string> {
Expand Down
30 changes: 13 additions & 17 deletions test/unit/k8s/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as k8s from 'vscode-kubernetes-tools-api';
import { Build } from '../../../src/k8s/build';
import { ChildProcessUtil } from '../../../src/util/childProcessUtil';
import { OpenShiftTerminalManager } from '../../../src/webview/openshift-terminal/openShiftTerminal';
import { CliChannel } from '../../../src/cli';

const {expect} = chai;
chai.use(sinonChai);
Expand All @@ -20,6 +21,7 @@ suite('K8s/build', () => {
let sandbox: sinon.SinonSandbox;
let termStub: sinon.SinonStub;
let execStub: sinon.SinonStub;
let execToolStub: sinon.SinonStub;
const errorMessage = 'FATAL ERROR';
const context = {
id: 'dummy',
Expand Down Expand Up @@ -64,6 +66,7 @@ suite('K8s/build', () => {
sandbox = sinon.createSandbox();
termStub = sandbox.stub(OpenShiftTerminalManager.prototype, 'executeInTerminal');
execStub = sandbox.stub(ChildProcessUtil.prototype, 'execute').resolves({ stdout: '', stderr: undefined, error: undefined });
execToolStub = sandbox.stub(CliChannel.prototype, 'executeTool');
// sandbox.stub(Progress, 'execFunctionWithProgress').yields();
});

Expand All @@ -88,14 +91,7 @@ suite('K8s/build', () => {
} as k8s.ClusterExplorerV1.ClusterExplorerNode;

setup(() => {
const api: k8s.API<k8s.KubectlV1> = {
available: true,
api: {
invokeCommand: sandbox.stub().resolves({ stdout: 'namespace, name, 1', stderr: '', code: 0}),
portForward: sandbox.stub()
}
};
sandbox.stub(k8s.extension.kubectl, 'v1').value(api);
execToolStub.resolves({ stdout: 'namespace, name, 1', stderr: '', error: undefined});
});

test('should able to get the children node of build Config', async () => {
Expand Down Expand Up @@ -155,23 +151,23 @@ suite('K8s/build', () => {
}
}`;
setup(() => {
execStub.resolves({ stdout: mockData, stderr: undefined, error: undefined });
quickPickStub = sandbox.stub(vscode.window, 'showQuickPick');
quickPickStub.resolves({label: 'nodejs-comp-nodejs-app'});
execToolStub.resolves({ stdout: mockData, stderr: '', error: undefined });
});

test('works from context menu', async () => {
const result = await Build.startBuild(startBuildCtx);

expect(result).equals(`Build '${startBuildCtx.name}' successfully started`);
expect(execStub).calledWith(Build.command.startBuild(startBuildCtx.name).toString());
expect(execToolStub).calledWith(Build.command.startBuild(startBuildCtx.name));
});

test('works with no context', async () => {
const result = await Build.startBuild(null);

expect(result).equals(`Build '${startBuildCtx.name}' successfully started`);
expect(execStub).calledWith(Build.command.startBuild(startBuildCtx.name).toString());
expect(execToolStub).calledWith(Build.command.startBuild(startBuildCtx.name));
});

test('returns null when no BuildConfig selected', async () => {
Expand All @@ -181,7 +177,7 @@ suite('K8s/build', () => {
});

test('wraps errors in additional info', async () => {
execStub.rejects(errorMessage);
execToolStub.rejects(errorMessage);

try {
await Build.startBuild(startBuildCtx);
Expand All @@ -192,7 +188,7 @@ suite('K8s/build', () => {

test('throws error if there is no BuildConfigs to select', async () => {
quickPickStub.restore();
execStub.resolves({ error: undefined, stdout: noBcData, stderr: '' });
execToolStub.resolves({ error: undefined, stdout: noBcData, stderr: '' });
let checkError: Error;
try {
await Build.startBuild(null);
Expand Down Expand Up @@ -290,7 +286,7 @@ suite('K8s/build', () => {

suite('Delete', ()=> {
setup(() => {
execStub.resolves({ error: null, stdout: buildData, stderr: '' });
execToolStub.resolves({ error: null, stdout: buildData, stderr: '' });
sandbox.stub<any, any>(Build, 'getBuildNames').resolves('nodejs-copm-nodejs-comp');
quickPickStub = sandbox.stub(vscode.window, 'showQuickPick');
quickPickStub.resolves({label: 'nodejs-copm-nodejs-comp-8'});
Expand All @@ -300,14 +296,14 @@ suite('K8s/build', () => {
const result = await Build.delete(context);

expect(result).equals(`Build '${context.impl.name}' successfully deleted`);
expect(execStub).calledWith(Build.command.delete(context.impl.name).toString());
expect(execToolStub).calledWith(Build.command.delete(context.impl.name));
});

test('works with no context', async () => {
const result = await Build.delete(null);

expect(result).equals('Build \'nodejs-copm-nodejs-comp-8\' successfully deleted');
expect(execStub).calledWith(Build.command.delete('nodejs-copm-nodejs-comp-8').toString());
expect(execToolStub).calledWith(Build.command.delete('nodejs-copm-nodejs-comp-8'));
});

test('returns null when no build selected to delete', async () => {
Expand All @@ -317,7 +313,7 @@ suite('K8s/build', () => {
});

test('wraps errors in additional info', async () => {
execStub.rejects(errorMessage);
execToolStub.rejects(errorMessage);

try {
await Build.delete(context);
Expand Down
23 changes: 8 additions & 15 deletions test/unit/k8s/deployment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DeploymentConfig } from '../../../src/k8s/deploymentConfig';
import { ChildProcessUtil } from '../../../src/util/childProcessUtil';
import { Progress } from '../../../src/util/progress';
import { OpenShiftTerminalManager } from '../../../src/webview/openshift-terminal/openShiftTerminal';
import { VsCommandError } from '../../../src/vscommand';

const {expect} = chai;
chai.use(sinonChai);
Expand Down Expand Up @@ -91,7 +92,7 @@ suite('K8s/deployment', () => {
setup(() => {
sandbox = sinon.createSandbox();
termStub = sandbox.stub(OpenShiftTerminalManager.prototype, 'executeInTerminal');
execStub = sandbox.stub(CliChannel.prototype, 'executeTool').resolves({ stdout: '', stderr: undefined, error: undefined});
execStub = sandbox.stub(CliChannel.prototype, 'executeTool').resolves({ stdout: '', stderr: '', error: undefined});
sandbox.stub(Progress, 'execFunctionWithProgress').yields();
});

Expand All @@ -101,7 +102,6 @@ suite('K8s/deployment', () => {

suite('DeploymentConfigNodeContributor', () => {
let dcnc;
let kubectlV1Stub: sinon.SinonStub<any[], any>;
const parent = {
metadata: undefined,
name: 'comp1-app',
Expand All @@ -119,26 +119,20 @@ suite('K8s/deployment', () => {

setup(() => {
dcnc = DeploymentConfig.getNodeContributor();
const api: k8s.API<k8s.KubectlV1> = {
available: true,
api: {
invokeCommand: sandbox.stub().resolves({ stdout: 'namespace, name, 1', stderr: '', code: 0}),
portForward: sandbox.stub()
}
};
kubectlV1Stub = sandbox.stub(k8s.extension.kubectl, 'v1').value(api);
});

test('should able to get the children node of deployment Config', async () => {
// Set 'executeTool` stub to return a normal response
execStub.resolves({ stdout: 'namespace, name, 1', stderr: '', error: undefined});

const result = await dcnc.getChildren(parent);
expect(result.length).equals(1);
});

test('returns empty children node of deployment Config', async () => {
const api = {
available: false
};
kubectlV1Stub.onFirstCall().value(api);
// Set 'executeTool` stub to return a random error
execStub.resolves({ stdout: '', stderr: 'Not available', error: new VsCommandError('Not available')});

const result = await dcnc.getChildren(parent);
expect(result.length).equals(0);
});
Expand All @@ -155,7 +149,6 @@ suite('K8s/deployment', () => {
};

setup(() => {
// execStub = sandbox.stub(CliChannel.prototype, 'execute').resolves({ stdout: mockData, stderr: undefined, error: undefined });
execStub.resolves({ error: undefined, stdout: mockData, stderr: '' });
quickPickStub = sandbox.stub(vscode.window, 'showQuickPick');
quickPickStub.resolves({label: 'nodejs-comp-nodejs-app'});
Expand Down

0 comments on commit c3a9b91

Please sign in to comment.