Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way to get expose url from tree view #447

Merged
merged 4 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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/"
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
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)),
Expand Down
6 changes: 5 additions & 1 deletion src/tekton/addtrigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -100,7 +101,10 @@ 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 url = await getExposeURl(trigger.metadata.name);
vscode.window.showInformationMessage(`Trigger successfully created. Expose URL: ${url}`);
}
await fs.unlink(fsPath);
return true;
}
Expand Down
35 changes: 33 additions & 2 deletions src/tekton/triggertemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,45 @@
* 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';
import { getExposeURl } from '../util/exposeurl';

export class TriggerTemplate extends TektonItem {

static getDeleteCommand(item: TektonNode): CliCommand {
return Command.deleteTriggerTemplate(item.getName());
}

static async copyExposeUrl(trigger: TektonNode): Promise<string> {
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()) {
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()) {
const url = await getExposeURl(eventListener.status.configuration.generatedName);
vscode.env.clipboard.writeText(url);
vscode.window.showInformationMessage('Expose URl successfully copied');
return;
}
}
}
}
vscode.window.showInformationMessage('Expose URl not available');
}
}
8 changes: 8 additions & 0 deletions src/tkn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
20 changes: 20 additions & 0 deletions src/util/exposeurl.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 * as _ from 'lodash';
import { Command, tkn } from '../tkn';



export async function getExposeURl(name: string): Promise<string> {
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;
}
182 changes: 182 additions & 0 deletions test/tekton/triggertemplate.test.ts
Original file line number Diff line number Diff line change
@@ -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 copied');
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 copied');
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;
});
});
});