From c0ef6a560180780dc2aa9e90ab17d45b6ab827dc Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Wed, 11 Nov 2020 22:52:15 +0530 Subject: [PATCH 1/4] Provide a way to get expose url from tree view --- package.json | 15 +++ src/extension.ts | 1 + src/tekton/addtrigger.ts | 6 +- src/tekton/triggertemplate.ts | 38 +++++- src/tkn.ts | 8 ++ test/tekton/triggertemplate.test.ts | 182 ++++++++++++++++++++++++++++ 6 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 test/tekton/triggertemplate.test.ts diff --git a/package.json b/package.json index 656738e8..3788f327 100644 --- a/package.json +++ b/package.json @@ -240,6 +240,12 @@ "category": "Tekton", "enablement": "tekton:tkn" }, + { + "command": "tekton.triggerTemplate.url", + "title": "Copy Expose URL", + "category": "Tekton", + "enablement": "tekton:tkn" + }, { "command": "tekton.pipeline.list", "title": "List Pipelines", @@ -535,6 +541,10 @@ "command": "k8s.tekton.pipeline.start", "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/" }, + { + "command": "tekton.triggerTemplate.url", + "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/" + }, { "command": "tekton.openInEditor", "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/" @@ -664,6 +674,11 @@ "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/ && viewItem == pipeline", "group": "c1@2" }, + { + "command": "tekton.triggerTemplate.url", + "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/ && viewItem == triggertemplates", + "group": "c1@2" + }, { "command": "tekton.addTrigger", "when": "view =~ /^tekton(CustomTree|PipelineExplorer)View/ && viewItem == pipeline", diff --git a/src/extension.ts b/src/extension.ts index 733e2f29..1be8b8cb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -73,6 +73,7 @@ export async function activate(context: vscode.ExtensionContext): Promise vscode.commands.registerCommand('tekton.pipelinerun.followLogs.palette', (context) => execute(PipelineRun.followLogs, context)), vscode.commands.registerCommand('tekton.pipelinerun.cancel', (context) => execute(PipelineRun.cancel, context)), vscode.commands.registerCommand('tekton.pipelinerun.cancel.palette', (context) => execute(PipelineRun.cancel, context)), + vscode.commands.registerCommand('tekton.triggerTemplate.url', (context) => execute(TriggerTemplate.copyExposeUrl, context)), vscode.commands.registerCommand('tekton.task.start', (context) => execute(Task.start, context)), vscode.commands.registerCommand('tekton.task.start.palette', (context) => execute(Task.start, context)), vscode.commands.registerCommand('tekton.task.list', (context) => execute(Task.list, context)), diff --git a/src/tekton/addtrigger.ts b/src/tekton/addtrigger.ts index 94c0b788..0e9c4711 100644 --- a/src/tekton/addtrigger.ts +++ b/src/tekton/addtrigger.ts @@ -100,7 +100,11 @@ export async function k8sCreate(trigger: TriggerTemplateKind | EventListenerKind vscode.window.showErrorMessage(`Fail to deploy Resources: ${getStderrString(result.error)}`); return false; } - if (trigger.kind === RouteModel.kind && !result.error) vscode.window.showInformationMessage('Trigger successfully created.'); + if (trigger.kind === RouteModel.kind && !result.error) { + const routeInfo = await cli.execute(Command.getRoute(trigger.metadata.name)); + const routeHost = JSON.parse(routeInfo.stdout).spec.host; + vscode.window.showInformationMessage(`Trigger successfully created. Expose URL: http://${routeHost}`); + } await fs.unlink(fsPath); return true; } diff --git a/src/tekton/triggertemplate.ts b/src/tekton/triggertemplate.ts index bd1365ad..8dab9320 100644 --- a/src/tekton/triggertemplate.ts +++ b/src/tekton/triggertemplate.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See LICENSE file in the project root for license information. *-----------------------------------------------------------------------------------------------*/ - +import * as vscode from 'vscode'; import { TektonItem } from './tektonitem'; -import { TektonNode, Command } from '../tkn'; +import { TektonNode, Command, tkn } from '../tkn'; import { CliCommand } from '../cli'; export class TriggerTemplate extends TektonItem { @@ -13,4 +13,38 @@ export class TriggerTemplate extends TektonItem { static getDeleteCommand(item: TektonNode): CliCommand { return Command.deleteTriggerTemplate(item.getName()); } + + static async copyExposeUrl(trigger: TektonNode): Promise { + if (!trigger) return null; + const result = await tkn.execute(Command.listEventListener()); + const listEventListener = JSON.parse(result.stdout).items; + if (listEventListener.length === 0) { + vscode.window.showInformationMessage('Expose URl not available'); + return null; + } + for (const eventListener of listEventListener) { + for (const triggers of eventListener.spec.triggers) { + if (triggers?.template?.name === trigger.getName()) { + await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); + vscode.window.showInformationMessage('Expose URl successfully copy'); + return; + } else if (triggers.triggerRef) { + const triggerData = await tkn.execute(Command.getTrigger(triggers.triggerRef)); + const triggerName = JSON.parse(triggerData.stdout).spec.template.name; + if (triggerName === trigger.getName()) { + await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); + vscode.window.showInformationMessage('Expose URl successfully copy'); + return; + } + } + } + } + vscode.window.showInformationMessage('Expose URl not available'); + } + + static async getExposeURl(name: string): Promise { + const result = await tkn.execute(Command.getRoute(name)); + const exposeURL = JSON.parse(result.stdout).spec.host; + vscode.env.clipboard.writeText(`http://${exposeURL}`); + } } diff --git a/src/tkn.ts b/src/tkn.ts index 289b9e8a..5bef6569 100644 --- a/src/tkn.ts +++ b/src/tkn.ts @@ -427,6 +427,14 @@ export class Command { return newK8sCommand('apply', '-f', file); } + static getRoute(name: string): CliCommand { + return newK8sCommand('get', 'route', name, '-o', 'json'); + } + + static getTrigger(name: string): CliCommand { + return newK8sCommand('get', 'trigger', name, '-o', 'json'); + } + } const IMAGES = '../../images'; diff --git a/test/tekton/triggertemplate.test.ts b/test/tekton/triggertemplate.test.ts new file mode 100644 index 00000000..c342da3e --- /dev/null +++ b/test/tekton/triggertemplate.test.ts @@ -0,0 +1,182 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + + +'use strict'; + +import * as chai from 'chai'; +import * as vscode from 'vscode'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import { EventListenerKind } from '../../src/tekton'; +import { TriggerTemplate } from '../../src/tekton/triggertemplate'; +import { ContextType, TknImpl } from '../../src/tkn'; +import { TestItem } from './testTektonitem'; + +const expect = chai.expect; +chai.use(sinonChai); + +suite('Tekton/Pipeline', () => { + const sandbox = sinon.createSandbox(); + let exeStub: sinon.SinonStub; + const pipelineNode = new TestItem(TknImpl.ROOT, 'test-pipeline', ContextType.PIPELINENODE, null); + const triggerTemplateItem = new TestItem(pipelineNode, 'trigger-template-sample-pipeline-cluster-task-4-awhmgc', ContextType.TRIGGERTEMPLATES, null); + + setup(() => { + exeStub = sandbox.stub(TknImpl.prototype, 'execute').resolves({ + error: '', + stdout: '' + }); + }); + + teardown(() => { + sandbox.restore(); + }); + + const eventListener: EventListenerKind[] = [{ + apiVersion:'triggers.tekton.dev/v1alpha1', + kind:'EventListener', + metadata: { + name:'event-listener-jwwe6j', + namespace:'pipelines-tutorial' + }, + spec: { + serviceAccountName:'pipeline', + triggers: [{ + bindings: [{ + kind:'TriggerBinding', + name:'vote-app' + }], + template: { + name:'trigger-template-sample-pipeline-cluster-task-4-awhmgc' + } + }] + }, + status: { + configuration: { + generatedName:'el-event-listener-jwwe6j' + } + } + }]; + + const eventListenerTriggerRef: EventListenerKind[] = [{ + apiVersion:'triggers.tekton.dev/v1alpha1', + kind:'EventListener', + metadata: { + name:'event-listener-jwwe6j', + namespace:'pipelines-tutorial' + }, + spec: { + serviceAccountName:'pipeline', + triggers: [{ + triggerRef: 'test' + }] + }, + status: { + configuration: { + generatedName:'el-event-listener-jwwe6j' + } + } + }]; + + const triggerData = { + spec: { + template: { + name: 'trigger-template-sample-pipeline-cluster-task-4-awhmgc' + } + } + } + + const route = { + spec: { + host: 'test.openshift.com' + } + } + + suite('Add trigger', () => { + test('copy expose URL', async () => { + exeStub.onFirstCall().resolves({ + error: '', + stdout: JSON.stringify({items: eventListener}) + }); + exeStub.onSecondCall().resolves({ + error: '', + stdout: JSON.stringify(route) + }); + const writeTextStub = sandbox.stub(vscode.env.clipboard, 'writeText').resolves('http://test.openshift.com'); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copy'); + await TriggerTemplate.copyExposeUrl(triggerTemplateItem); + expect(exeStub).called; + expect(infoMsg).is.calledOnce; + expect(writeTextStub).called; + }); + + test('copy expose URL for triggerRef', async () => { + exeStub.onFirstCall().resolves({ + error: '', + stdout: JSON.stringify({items: eventListenerTriggerRef}) + }); + exeStub.onSecondCall().resolves({ + error: '', + stdout: JSON.stringify(triggerData) + }); + exeStub.onThirdCall().resolves({ + error: '', + stdout: JSON.stringify(route) + }); + const writeTextStub = sandbox.stub(vscode.env.clipboard, 'writeText').resolves('http://test.openshift.com'); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copy'); + await TriggerTemplate.copyExposeUrl(triggerTemplateItem); + expect(exeStub).called; + expect(infoMsg).is.calledOnce; + expect(writeTextStub).called; + }); + + test('return null if no EventListener found', async () => { + exeStub.onFirstCall().resolves({ + error: '', + stdout: JSON.stringify({items: []}) + }); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl not available'); + const result = await TriggerTemplate.copyExposeUrl(triggerTemplateItem); + expect(result).equals(null); + expect(infoMsg).is.calledOnce; + }); + + test('expose URL not found', async () => { + exeStub.onFirstCall().resolves({ + error: '', + stdout: JSON.stringify({items: [{ + apiVersion:'triggers.tekton.dev/v1alpha1', + kind:'EventListener', + metadata: { + name:'event-listener-jwwe6j', + namespace:'pipelines-tutorial' + }, + spec: { + serviceAccountName:'pipeline', + triggers: [{ + bindings: [{ + kind:'TriggerBinding', + name:'vote-app' + }], + template: { + name:'test' + } + }] + }, + status: { + configuration: { + generatedName:'el-event-listener-jwwe6j' + } + } + }]}) + }); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl not available'); + await TriggerTemplate.copyExposeUrl(triggerTemplateItem); + expect(infoMsg).is.calledOnce; + }); + }); +}); From 081531355e4e46628615e2e8e8f90bd9e1108bf6 Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Wed, 11 Nov 2020 22:55:30 +0530 Subject: [PATCH 2/4] rebase --- src/extension.ts | 1 + src/tekton/triggertemplate.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 1be8b8cb..994704a2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -30,6 +30,7 @@ import { deleteFromExplorer, deleteFromCustom } from './commands/delete'; import { addTrigger } from './tekton/trigger'; import { triggerDetection } from './util/detection'; import { showDiagnosticData } from './tekton/diagnostic'; +import { TriggerTemplate } from './tekton/triggertemplate'; export let contextGlobalState: vscode.ExtensionContext; let k8sExplorer: k8s.ClusterExplorerV1 | undefined = undefined; diff --git a/src/tekton/triggertemplate.ts b/src/tekton/triggertemplate.ts index 8dab9320..cf2b57bd 100644 --- a/src/tekton/triggertemplate.ts +++ b/src/tekton/triggertemplate.ts @@ -28,7 +28,7 @@ export class TriggerTemplate extends TektonItem { await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); vscode.window.showInformationMessage('Expose URl successfully copy'); return; - } else if (triggers.triggerRef) { + } else if (triggers?.triggerRef) { const triggerData = await tkn.execute(Command.getTrigger(triggers.triggerRef)); const triggerName = JSON.parse(triggerData.stdout).spec.template.name; if (triggerName === trigger.getName()) { From 7a5a7816d0b88fea2c5fb30a17131684498e6f8f Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Thu, 12 Nov 2020 15:30:12 +0530 Subject: [PATCH 3/4] Fix review changes --- src/tekton/triggertemplate.ts | 14 ++++++++++---- test/tekton/triggertemplate.test.ts | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/tekton/triggertemplate.ts b/src/tekton/triggertemplate.ts index cf2b57bd..3541a8c3 100644 --- a/src/tekton/triggertemplate.ts +++ b/src/tekton/triggertemplate.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { TektonItem } from './tektonitem'; import { TektonNode, Command, tkn } from '../tkn'; import { CliCommand } from '../cli'; +import * as _ from 'lodash'; export class TriggerTemplate extends TektonItem { @@ -26,14 +27,14 @@ export class TriggerTemplate extends TektonItem { for (const triggers of eventListener.spec.triggers) { if (triggers?.template?.name === trigger.getName()) { await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); - vscode.window.showInformationMessage('Expose URl successfully copy'); + vscode.window.showInformationMessage('Expose URl successfully copied'); return; } else if (triggers?.triggerRef) { const triggerData = await tkn.execute(Command.getTrigger(triggers.triggerRef)); const triggerName = JSON.parse(triggerData.stdout).spec.template.name; if (triggerName === trigger.getName()) { await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); - vscode.window.showInformationMessage('Expose URl successfully copy'); + vscode.window.showInformationMessage('Expose URl successfully copied'); return; } } @@ -44,7 +45,12 @@ export class TriggerTemplate extends TektonItem { static async getExposeURl(name: string): Promise { const result = await tkn.execute(Command.getRoute(name)); - const exposeURL = JSON.parse(result.stdout).spec.host; - vscode.env.clipboard.writeText(`http://${exposeURL}`); + const route = JSON.parse(result.stdout); + const scheme = _.get(route, 'spec.tls.termination') ? 'https' : 'http'; + let url = `${scheme}://${route.spec.host}`; + if (route.spec?.path) { + url += route.spec.path; + } + vscode.env.clipboard.writeText(url); } } diff --git a/test/tekton/triggertemplate.test.ts b/test/tekton/triggertemplate.test.ts index c342da3e..6b92faa6 100644 --- a/test/tekton/triggertemplate.test.ts +++ b/test/tekton/triggertemplate.test.ts @@ -106,7 +106,7 @@ suite('Tekton/Pipeline', () => { stdout: JSON.stringify(route) }); const writeTextStub = sandbox.stub(vscode.env.clipboard, 'writeText').resolves('http://test.openshift.com'); - const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copy'); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copied'); await TriggerTemplate.copyExposeUrl(triggerTemplateItem); expect(exeStub).called; expect(infoMsg).is.calledOnce; @@ -127,7 +127,7 @@ suite('Tekton/Pipeline', () => { stdout: JSON.stringify(route) }); const writeTextStub = sandbox.stub(vscode.env.clipboard, 'writeText').resolves('http://test.openshift.com'); - const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copy'); + const infoMsg = sandbox.stub(vscode.window, 'showInformationMessage').resolves('Expose URl successfully copied'); await TriggerTemplate.copyExposeUrl(triggerTemplateItem); expect(exeStub).called; expect(infoMsg).is.calledOnce; From e8e6274d39951e44017630ff05c553103fc003a7 Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Thu, 12 Nov 2020 15:48:17 +0530 Subject: [PATCH 4/4] review changes --- src/tekton/addtrigger.ts | 6 +++--- src/tekton/triggertemplate.ts | 19 +++++-------------- src/util/exposeurl.ts | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 src/util/exposeurl.ts diff --git a/src/tekton/addtrigger.ts b/src/tekton/addtrigger.ts index 0e9c4711..867c1ebb 100644 --- a/src/tekton/addtrigger.ts +++ b/src/tekton/addtrigger.ts @@ -22,6 +22,7 @@ import { Progress } from '../util/progress'; import { cli } from '../cli'; import { TknVersion, version } from '../util/tknversion'; import { NewPvc } from './createpvc'; +import { getExposeURl } from '../util/exposeurl'; export const TriggerTemplateModel = { apiGroup: 'triggers.tekton.dev', @@ -101,9 +102,8 @@ export async function k8sCreate(trigger: TriggerTemplateKind | EventListenerKind return false; } if (trigger.kind === RouteModel.kind && !result.error) { - const routeInfo = await cli.execute(Command.getRoute(trigger.metadata.name)); - const routeHost = JSON.parse(routeInfo.stdout).spec.host; - vscode.window.showInformationMessage(`Trigger successfully created. Expose URL: http://${routeHost}`); + const url = await getExposeURl(trigger.metadata.name); + vscode.window.showInformationMessage(`Trigger successfully created. Expose URL: ${url}`); } await fs.unlink(fsPath); return true; diff --git a/src/tekton/triggertemplate.ts b/src/tekton/triggertemplate.ts index 3541a8c3..6eddaf4d 100644 --- a/src/tekton/triggertemplate.ts +++ b/src/tekton/triggertemplate.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { TektonItem } from './tektonitem'; import { TektonNode, Command, tkn } from '../tkn'; import { CliCommand } from '../cli'; -import * as _ from 'lodash'; +import { getExposeURl } from '../util/exposeurl'; export class TriggerTemplate extends TektonItem { @@ -26,14 +26,16 @@ export class TriggerTemplate extends TektonItem { for (const eventListener of listEventListener) { for (const triggers of eventListener.spec.triggers) { if (triggers?.template?.name === trigger.getName()) { - await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); + const url = await getExposeURl(eventListener.status.configuration.generatedName); + vscode.env.clipboard.writeText(url); vscode.window.showInformationMessage('Expose URl successfully copied'); return; } else if (triggers?.triggerRef) { const triggerData = await tkn.execute(Command.getTrigger(triggers.triggerRef)); const triggerName = JSON.parse(triggerData.stdout).spec.template.name; if (triggerName === trigger.getName()) { - await TriggerTemplate.getExposeURl(eventListener.status.configuration.generatedName); + const url = await getExposeURl(eventListener.status.configuration.generatedName); + vscode.env.clipboard.writeText(url); vscode.window.showInformationMessage('Expose URl successfully copied'); return; } @@ -42,15 +44,4 @@ export class TriggerTemplate extends TektonItem { } vscode.window.showInformationMessage('Expose URl not available'); } - - static async getExposeURl(name: string): Promise { - const result = await tkn.execute(Command.getRoute(name)); - const route = JSON.parse(result.stdout); - const scheme = _.get(route, 'spec.tls.termination') ? 'https' : 'http'; - let url = `${scheme}://${route.spec.host}`; - if (route.spec?.path) { - url += route.spec.path; - } - vscode.env.clipboard.writeText(url); - } } diff --git a/src/util/exposeurl.ts b/src/util/exposeurl.ts new file mode 100644 index 00000000..bb2cb613 --- /dev/null +++ b/src/util/exposeurl.ts @@ -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 * as _ from 'lodash'; +import { Command, tkn } from '../tkn'; + + + +export async function getExposeURl(name: string): Promise { + const result = await tkn.execute(Command.getRoute(name)); + const route = JSON.parse(result.stdout); + const scheme = _.get(route, 'spec.tls.termination') ? 'https' : 'http'; + let url = `${scheme}://${route.spec.host}`; + if (route.spec?.path) { + url += route.spec.path; + } + return url; +}