Skip to content

Commit

Permalink
Fix bug on finding reference resource while deleting (#542)
Browse files Browse the repository at this point in the history
* Fix bug on finding reference resource while deleting
  • Loading branch information
sudhirverma committed Apr 2, 2021
1 parent c2ee9fc commit 28c1364
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 85 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Development of the Tekton Pipelines Extension is largely following development o
### Extension Configuration Settings
* `Tekton Pipelines: Show Channel On Output` - Show Tekton Pipelines output channel when new text added to output stream
* `Tekton Pipelines: Output verbosity level` - Output verbosity level (value between 0 and 9) for Tekton Pipeline Start, Push and Watch commands in output channel and integrated terminal.
* `Tekton Pipelines: Show reference resource notification` - Enable/disable to check Task and ClusterTask Reference Resource.

### Dependencies

Expand Down
24 changes: 15 additions & 9 deletions src/commands/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import { ClusterTriggerBinding } from '../tekton/clustertriggerbunding';
import { telemetryLogCommand, telemetryLogError } from '../telemetry';
import { ContextType } from '../context-type';
import { TektonNode } from '../tree-view/tekton-node';
import { checkRefResource, getTaskRunResourceList, referenceOfTaskAndClusterTaskInCluster } from '../util/check-ref-resource';
import { checkRefResource, referenceOfTaskAndClusterTaskInCluster } from '../util/check-ref-resource';
import { TknPipeline } from '../tekton';
import { getPipelineList } from '../util/list-tekton-resource';

interface Refreshable {
refresh(): void;
Expand Down Expand Up @@ -53,26 +55,30 @@ function getItemToDelete(contextItem: TektonNode, selectedItems: TektonNode[]):

async function doDelete(items: TektonNode[], toRefresh: Refreshable, commandId?: string): Promise<void | string> {
if (items) {
const taskRunList = await getTaskRunResourceList();
let message: string;
let refResource = true;
let hasAnyReference = false;
let pipelineList: TknPipeline[] = [];
if (checkRefResource()) {
const hasAnyTaskOrClusterTask = items.some(item => item.contextValue === ContextType.TASK || item.contextValue === ContextType.CLUSTERTASK);
if (hasAnyTaskOrClusterTask) {
pipelineList = await getPipelineList();
}
}
const toDelete = new Map<TektonNode, CliCommand>();
for (const item of items) {
const deleteCommand = getDeleteCommand(item);
if (deleteCommand) {
toDelete.set(item, deleteCommand);
}
if (refResource && checkRefResource()) {
if (referenceOfTaskAndClusterTaskInCluster(item, taskRunList)) {
refResource = false;
}
if (!hasAnyReference && checkRefResource() && pipelineList.length !== 0 && (item.contextValue === ContextType.TASK || item.contextValue === ContextType.CLUSTERTASK)) {
hasAnyReference = referenceOfTaskAndClusterTaskInCluster(item, pipelineList);
}
}
if (toDelete.size === 0) {
return;
}
if (toDelete.size > 1) {
if (refResource) {
if (!hasAnyReference) {
message = `Do you want to delete ${toDelete.size} items?`;
} else {
message = `Do you want to delete ${toDelete.size} items?. You have selected Task or ClusterTask which is being used in some other resource.`;
Expand Down Expand Up @@ -102,7 +108,7 @@ async function doDelete(items: TektonNode[], toRefresh: Refreshable, commandId?:

} else {
const name = toDelete.keys().next().value.getName();
if (refResource) {
if (!hasAnyReference) {
message = `Do you want to delete the '${name}'?`;
} else {
message = `Do you want to delete the ${toDelete.keys().next().value.contextValue}: '${name}' which is used in some other resource?`;
Expand Down
21 changes: 21 additions & 0 deletions src/tekton.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,31 @@ export interface TknTask {
spec: TknTaskSpec;
}

interface TaskSpec {
name?: string;
params?: Param;
taskRef?: {
kind: string;
name: string;
};
}

interface KubectlPipelineSpec {
params?: Param;
tasks?: TaskSpec[];
}

export interface TknPipeline {
kind: string;
metadata: TknMetadata;
spec: KubectlPipelineSpec;
}

// JSON types

export interface Param {
name?: string;
value?: string;
}

export type VolumeTypeSecret = {
Expand Down
15 changes: 3 additions & 12 deletions src/tkn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { TaskRun } from './tree-view/task-run-node';
import { PipelineRun } from './tree-view/pipelinerun-node';
import { MoreNode } from './tree-view/expand-node';
import { Command } from './cli-command';
import { getPipelineList } from './util/list-tekton-resource';

export const humanizer = humanize.humanizer(createConfig());

Expand Down Expand Up @@ -352,18 +353,8 @@ export class TknImpl implements Tkn {
}

async _getPipelines(pipeline: TektonNode): Promise<TektonNode[]> {
let data: TknTask[] = [];
const result = await this.execute(Command.listPipelines(), process.cwd(), false);
if (result.error) {
console.log(result + ' Std.err when processing pipelines');
return [new TektonNodeImpl(pipeline, getStderrString(result.error), ContextType.PIPELINE, this, TreeItemCollapsibleState.Expanded)];
}
try {
data = JSON.parse(result.stdout).items;
} catch (ignore) {
//show no pipelines if output is not correct json
}
let pipelines: NameId[] = data.map((value) => {
const pipelineList = await getPipelineList();
let pipelines: NameId[] = pipelineList.map((value) => {
return {
name: value.metadata.name,
uid: value.metadata.uid
Expand Down
45 changes: 17 additions & 28 deletions src/util/check-ref-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,28 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { cli } from '../cli';
import { Command } from '../cli-command';
import { ContextType } from '../context-type';
import { PipelineTaskRunData } from '../tekton';
import { TknPipeline } from '../tekton';
import { TektonNode } from '../tree-view/tekton-node';
import * as vscode from 'vscode';

export async function getTaskRunResourceList(): Promise<PipelineTaskRunData[]> {
const result = await cli.execute(Command.listTaskRun());
if (result.error) {
// ignore
}
let data: PipelineTaskRunData[] = [];
try {
const r = JSON.parse(result.stdout);
data = r.items ? r.items : data;
} catch (ignore) {
// ignore
}
return data;
const tektonResource = {
task: 'Task',
clustertask: 'ClusterTask'
}

export function referenceOfTaskAndClusterTaskInCluster(item: TektonNode, taskRunList: PipelineTaskRunData[]): boolean{
if (taskRunList.length !== 0 && (item.contextValue === ContextType.TASK || item.contextValue === ContextType.CLUSTERTASK)) {
const found = taskRunList.some((value) => {
if (value?.spec?.taskRef?.name === item.getName()) {
return true;
}
});
return found;
}

export function referenceOfTaskAndClusterTaskInCluster(item: TektonNode, pipelineList: TknPipeline[]): boolean {
const found = pipelineList.some((value) => {
if (value?.spec?.tasks) {
return value?.spec?.tasks.some((task) => {
if (task?.taskRef?.kind === tektonResource[item.contextValue] && task?.taskRef?.name === item.getName()) {
return true;
}
})
}
});
return found;
}

export function checkRefResource(): boolean {
return vscode.workspace
.getConfiguration('vs-tekton')
Expand Down
20 changes: 20 additions & 0 deletions src/util/list-tekton-resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { cli } from '../cli';
import { Command } from '../cli-command';
import { TknPipeline } from '../tekton';

export async function getPipelineList(): Promise<TknPipeline[]> {
const result = await cli.execute(Command.listPipelines());
let data: TknPipeline[] = [];
try {
const r = JSON.parse(result.stdout);
data = r.items ? r.items : data;
} catch (ignore) {
// ignore
}
return data;
}
2 changes: 2 additions & 0 deletions test/tekton/pipelinerun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { errorMessage, TektonItem } from '../../src/tekton/tektonitem';
import * as logInEditor from '../../src/util/log-in-editor';
import { ContextType } from '../../src/context-type';
import { Command } from '../../src/cli-command';
import { CliImpl } from '../../src/cli';

const expect = chai.expect;
chai.use(sinonChai);
Expand All @@ -31,6 +32,7 @@ suite('Tekton/PipelineRun', () => {

setup(() => {
sandbox.stub(TknImpl.prototype, 'execute').resolves({ error: null, stdout: '', stderr: '' });
sandbox.stub(CliImpl.prototype, 'execute').resolves({ error: null, stdout: '', stderr: '' });
sandbox.stub(TknImpl.prototype, 'getPipelineRuns').resolves([pipelineRunItem]);
sandbox.stub(TknImpl.prototype, 'getPipelineRunsList').resolves([pipelineRunItem]);
showQuickPickStub = sandbox.stub(vscode.window, 'showQuickPick').resolves(undefined);
Expand Down
25 changes: 13 additions & 12 deletions test/tkn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,54 +34,55 @@ suite('tkn', () => {
let startPipelineObj: StartObject;
const sandbox = sinon.createSandbox();
const errorMessage = 'Error';
let execStubCli: sinon.SinonStub;

setup(() => {
sandbox.stub(ToolsConfig, 'getVersion').resolves('0.2.0');
execStubCli = sandbox.stub(CliImpl.prototype, 'execute').resolves();
});

teardown(() => {
sandbox.restore();
});

suite('command execution', () => {
let execStub: sinon.SinonStub, toolsStub: sinon.SinonStub;
let toolsStub: sinon.SinonStub;
const command = createCliCommand('tkn', 'do', 'whatever', 'you', 'do');

setup(() => {
execStub = sandbox.stub(CliImpl.prototype, 'execute');
toolsStub = sandbox.stub(ToolsConfig, 'detectOrDownload').resolves();
});

test('execute calls the given command in shell', async () => {
const data = { stdout: 'done', error: null };
execStub.resolves(data);
execStubCli.resolves(data);
const result = await tknCli.execute(command);

expect(execStub).calledOnceWith(command);
expect(execStubCli).calledOnceWith(command);
expect(result).deep.equals(data);
});

test('execute calls command with its detected location', async () => {
const toolPath = 'path/to/tool/tool';
execStub.resolves({ stdout: 'done', error: null });
execStubCli.resolves({ stdout: 'done', error: null });
toolsStub.resolves(toolPath);
await tknCli.execute(command);
// eslint-disable-next-line require-atomic-updates
command.cliCommand = command.cliCommand.replace('tkn', `"${toolPath}"`);
expect(execStub).calledOnceWith(command);
expect(execStubCli).calledOnceWith(command);
});

test('execute allows to set its working directory', async () => {
execStub.resolves({ stdout: 'done', error: null });
execStubCli.resolves({ stdout: 'done', error: null });
const cwd = 'path/to/some/dir';
await tknCli.execute(command, cwd);

expect(execStub).calledOnceWith(command, { cwd: cwd });
expect(execStubCli).calledOnceWith(command, { cwd: cwd });
});

test('execute rejects if an error occurs in the shell command', async () => {
const err: ExecException = { message: 'ERROR', name: 'err' };
execStub.resolves({ error: err, stdout: '' });
execStubCli.resolves({ error: err, stdout: '' });
try {
await tknCli.execute(command);
expect.fail();
Expand All @@ -92,7 +93,7 @@ suite('tkn', () => {

test('execute can be set to pass errors through exit data', async () => {
const err: ExecException = { message: 'ERROR', name: 'err' };
execStub.resolves({ error: err, stdout: '' });
execStubCli.resolves({ error: err, stdout: '' });
const result = await tknCli.execute(command, null, false);

expect(result).deep.equals({ error: err, stdout: '' });
Expand Down Expand Up @@ -190,7 +191,7 @@ suite('tkn', () => {

test('getPipelines returns items from tkn pipeline list command', async () => {
const tknPipelines = ['pipeline1', 'pipeline2', 'pipeline3'];
execStub.resolves({
execStubCli.onFirstCall().resolves({
error: null, stdout: JSON.stringify({
'items': [{
'kind': 'Pipeline',
Expand All @@ -215,7 +216,7 @@ suite('tkn', () => {
});
const result = await tknCli.getPipelines(pipelineNodeItem);

expect(execStub).calledOnceWith(Command.listPipelines());
expect(execStubCli).calledOnceWith(Command.listPipelines());
expect(result.length).equals(3);
for (let i = 1; i < result.length; i++) {
expect(result[i].getName()).equals(tknPipelines[i]);
Expand Down
Loading

0 comments on commit 28c1364

Please sign in to comment.