Skip to content

Commit

Permalink
Show installed tasks (#497)
Browse files Browse the repository at this point in the history
* #485 show instlalled tasks

Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>

* fix refreshing tasks lists, fix as ClusterTask installation

Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
  • Loading branch information
evidolob authored Feb 8, 2021
1 parent 95608d5 commit 53b6b2c
Show file tree
Hide file tree
Showing 23 changed files with 773 additions and 122 deletions.
18 changes: 18 additions & 0 deletions src/hub/hub-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,21 @@ export async function getTaskByVersion(taskId: number): Promise<hubApi.ResourceV
const result = await restApi.resourceByVersionId(taskId);
return result.data.data;
}

export async function getTaskByNameAndVersion(catalog: string, name: string, version: string): Promise<hubApi.ResourceVersionData> {
const restApi = new hubApi.ResourceApi();
const result = await restApi.resourceByCatalogKindNameVersion(catalog, 'task', name, version);
return result.data.data;
}

export async function getTaskById(taskId: number): Promise<hubApi.ResourceData> {
const restApi = new hubApi.ResourceApi();
const result = await restApi.resourceById(taskId);
return result.data.data;
}

export async function getTopRatedTasks(limit: number): Promise<hubApi.ResourceData[]> {
const restApi = new hubApi.ResourceApi();
const result = await restApi.resourceList(limit);
return result.data.data;
}
31 changes: 31 additions & 0 deletions src/hub/hub-common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { ResourceData, ResourceVersionData } from '../tekton-hub-client/api';

export interface HubTaskInstallation {
url: string;
name: string;
tknVersion?: string;
minPipelinesVersion?: string;
asClusterTask: boolean;
taskVersion?: ResourceVersionData;
}

export interface HubTaskUninstall {
name: string;
clusterTask: boolean;
}

export interface InstalledTask extends ResourceData {
installedVersion?: ResourceVersionData;
clusterTask?: boolean;
}

export type HubTask = InstalledTask | ResourceData

export function isInstalledTask(task: HubTask): task is InstalledTask {
return (task as InstalledTask).installedVersion !== undefined;
}
99 changes: 91 additions & 8 deletions src/hub/hub-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,39 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { Disposable } from '../util/disposable';
import { getTektonHubStatus, searchTask, TektonHubStatusEnum } from './hub-client';
import { getTaskById, getTaskByNameAndVersion, getTektonHubStatus, getTopRatedTasks, searchTask, TektonHubStatusEnum } from './hub-client';
import { ResourceData } from '../tekton-hub-client';
import { taskPageManager } from './task-page-manager';
import { installTask } from './install-task';
import { installTask, installEvent } from './install-task';
import { version } from '../util/tknversion';
import { getRawTasks } from '../yaml-support/tkn-tasks-provider';
import { InstalledTask, isInstalledTask } from './hub-common';
import { uninstallTaskEvent } from './uninstall-task';


export class TektonHubTasksViewProvider extends Disposable implements vscode.WebviewViewProvider {

private webviewView: vscode.WebviewView;
private tknVersion: string | undefined;
private installedTasks: InstalledTask[] | undefined;

constructor(
private readonly extensionUri: vscode.Uri,
) {
super();

installEvent(async () => {
if (this.webviewView?.visible) {
await this.loadInstalledTasks();
await this.loadRecommendedTasks();
}
});

uninstallTaskEvent(() => {
if (this.webviewView?.visible) {
this.loadInstalledTasks();
}
});
}

resolveWebviewView(webviewView: vscode.WebviewView): void | Thenable<void> {
Expand All @@ -42,7 +59,7 @@ export class TektonHubTasksViewProvider extends Disposable implements vscode.Web
this.register(webviewView.webview.onDidReceiveMessage(e => {
switch (e.type) {
case 'ready':
this.doCheckHub();
this.handleReady();
break;
case 'search':
this.doSearch(e.data);
Expand Down Expand Up @@ -70,8 +87,7 @@ export class TektonHubTasksViewProvider extends Disposable implements vscode.Web
<div id="header">
<input type="text" placeholder="Search Tasks in TektonHub" id="taskInput" />
</div>
<div class="listContainer">
<div id="tasksList" />
<div class="listContainer" id="mainContainer">
</div>
</div>
<script type="text/javascript" src="{{init}}"> </script>
Expand All @@ -84,15 +100,25 @@ export class TektonHubTasksViewProvider extends Disposable implements vscode.Web
this.webviewView?.webview?.postMessage(message);
}

private async doCheckHub(): Promise<void> {
private async handleReady(): Promise<void> {
const hubAvailable = await this.doCheckHub();
if (hubAvailable){
await this.loadInstalledTasks();
await this.loadRecommendedTasks();
}
}

private async doCheckHub(): Promise<boolean> {
const status = await getTektonHubStatus();
const tknVersions = await version();
this.tknVersion = tknVersions.pipeline;

if (status.status !== TektonHubStatusEnum.Ok){
this.sendMessage({type: 'error', data: status.error});
return false;
} else {
this.sendMessage({type: 'tknVersion', data: tknVersions.pipeline})
this.sendMessage({type: 'tknVersion', data: tknVersions.pipeline});
return true;
}
}

Expand All @@ -106,8 +132,65 @@ export class TektonHubTasksViewProvider extends Disposable implements vscode.Web

}

private openTaskPage(task: ResourceData): void {
private async loadInstalledTasks(): Promise<void> {
const rawTasks = await getRawTasks(true);
const installedTasksRaw = rawTasks.filter( task => {
if (!task.metadata?.labels){
return false;
}
return task.metadata?.labels['hub.tekton.dev/catalog'] !== undefined;
});

const installedTasks: InstalledTask[] = [];
for (const installedTask of installedTasksRaw) {
try {
const installedVersion = await getTaskByNameAndVersion(installedTask.metadata.labels['hub.tekton.dev/catalog'], installedTask.metadata.name, installedTask.metadata.labels['app.kubernetes.io/version']);
const task: InstalledTask = installedVersion.resource;
task.installedVersion = installedVersion;
const tmpTask = Object.assign({}, task);
tmpTask.installedVersion.resource = undefined;
tmpTask.clusterTask = installedTask.kind === 'ClusterTask';
installedTasks.push(tmpTask);
} catch (err) {
// ignore errors
}
}
this.installedTasks = installedTasks;
this.sendMessage({type: 'installedTasks', data: installedTasks});
return;
}

private async openTaskPage(task: ResourceData | InstalledTask): Promise<void> {
if (isInstalledTask(task)) {
const taskData: InstalledTask = await getTaskById(task.id);
taskData.installedVersion = task.installedVersion;
task = taskData;
}
taskPageManager.showTaskPageView(task, this.tknVersion);
}

private async loadRecommendedTasks(): Promise<void> {
try {
const recommendedTasks = await getTopRatedTasks(7);
let result = [];
if (this.installedTasks) {
const installedId = this.installedTasks.map((it) => it.id);

for (const task of recommendedTasks) {
if (installedId.indexOf(task.id) === -1) {
result.push(task);
}
}
} else {
result = recommendedTasks;
}

this.sendMessage({type: 'recommendedTasks', data: result});
} catch (err){
console.error(err);
}

}

}

13 changes: 0 additions & 13 deletions src/hub/install-common.ts

This file was deleted.

57 changes: 37 additions & 20 deletions src/hub/install-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,85 @@

import { Command, getStderrString, tkn } from '../tkn';
import * as vscode from 'vscode';
import { HubTaskInstallation } from './install-common';
import { HubTaskInstallation } from './hub-common';
import * as semver from 'semver';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs-extra';
import { DownloadUtil } from '../util/download';
import * as jsYaml from 'js-yaml';

export async function installTask(task: HubTaskInstallation): Promise<void> {
const installEventEmitter = new vscode.EventEmitter<HubTaskInstallation>()
export const installEvent = installEventEmitter.event;

export async function installTask(task: HubTaskInstallation): Promise<boolean> {
try {
if (task.tknVersion && task.minPipelinesVersion){
if (semver.lt(task.tknVersion, task.minPipelinesVersion)){
const result = await vscode.window.showWarningMessage(`This task requires Tekton Pipelines >= ${task.minPipelinesVersion} and is incompatible with the version of Tekton Pipelines installed on your cluster. Install anyway?`, 'Install', 'Cancel')
if (result === 'Install'){
await doInstall(task);
return doInstall(task);
} else {
return;
return false;
}
}
}
await doInstall(task);
return doInstall(task);

} catch (err) {
vscode.window.showErrorMessage(err.toString());
return false;
}
}

async function doInstall(task: HubTaskInstallation): Promise<void> {
async function doInstall(task: HubTaskInstallation): Promise<boolean> {
if (task.asClusterTask) {
await doInstallClusterTask(task);
return doInstallClusterTask(task);
} else {
doInstallTask(task);
return doInstallTask(task);
}

}

function doInstallTask(task: HubTaskInstallation): void {
vscode.window.withProgress({title: `Installing ${task.name}`,location: vscode.ProgressLocation.Notification}, async () => {
async function doInstallTask(task: HubTaskInstallation): Promise<boolean> {
return await vscode.window.withProgress({title: `Installing ${task.name}`,location: vscode.ProgressLocation.Notification}, async () => {
try {
const tasks = await tkn.getRawTasks();
if (tasks) {
for (const rawTask of tasks) {
if (rawTask.metadata.name === task.name) {
const overwriteResult = await vscode.window.showWarningMessage(`You already has Task '${task.name}'. Do you want to overwrite it?`, 'Overwrite', 'Cancel');
if (overwriteResult !== 'Overwrite') {
return;
return false;
}
}
}
}
const result = await tkn.execute(Command.hubInstall(task.name, task.taskVersion));
const result = await tkn.execute(Command.hubInstall(task.name, task.taskVersion.version));
if (result.error){
vscode.window.showWarningMessage(`Task installation failed: ${getStderrString(result.error)}`);
} else {
vscode.window.showInformationMessage(`Task ${task.name} installed.`);
installEventEmitter.fire(task);
return true;
}
} catch (err) {
vscode.window.showErrorMessage(err.toString());
}
return false;
});
}
const taskRegexp = /^kind:[\t ]*Task[\t ]*$/m;
async function doInstallClusterTask(task: HubTaskInstallation): Promise<void> {
await vscode.window.withProgress({title: `Installing ${task.name}`,location: vscode.ProgressLocation.Notification}, async () => {

async function doInstallClusterTask(task: HubTaskInstallation): Promise<boolean> {
return await vscode.window.withProgress({title: `Installing ${task.name}`,location: vscode.ProgressLocation.Notification}, async () => {
try {
const tasks = await tkn.getRawClusterTasks();
if (tasks) {
for (const rawTask of tasks) {
if (rawTask.metadata.name === task.name) {
const overwriteResult = await vscode.window.showWarningMessage(`You already has ClusterTask '${task.name}'. Do you want to overwrite it?`, 'Overwrite', 'Cancel');
if (overwriteResult !== 'Overwrite') {
return;
return false;
}
}
}
Expand All @@ -84,20 +92,29 @@ async function doInstallClusterTask(task: HubTaskInstallation): Promise<void> {
const tempFile = path.join(os.tmpdir(), path.basename(url.fsPath));
await DownloadUtil.downloadFile(task.url, tempFile);
if (await fs.pathExists(tempFile)) {
let content = await fs.readFile(tempFile, {encoding : 'UTF8'});
content = content.replace(taskRegexp, 'kind: ClusterTask');
await fs.writeFile(tempFile, content);

const content = await fs.readFile(tempFile, {encoding : 'UTF8'});
const yaml = jsYaml.safeLoadAll(content);
if (yaml[0]) {
yaml[0].kind = 'ClusterTask';
if (!yaml[0].metadata) {
yaml[0].metadata = [];
}
yaml[0].metadata.labels['hub.tekton.dev/catalog'] = 'tekton';
await fs.writeFile(tempFile, jsYaml.dump(yaml[0]));
}
}
const result = await tkn.execute(Command.updateYaml(tempFile));
await fs.unlink(tempFile);
if (result.error){
vscode.window.showWarningMessage(`ClusterTask installation failed: ${getStderrString(result.error)}`);
} else {
vscode.window.showInformationMessage(`ClusterTask ${task.name} installed.`);
installEventEmitter.fire(task);
return true;
}
} catch (err) {
vscode.window.showErrorMessage(err.toString());
}
return false;
});
}
6 changes: 3 additions & 3 deletions src/hub/task-page-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/
import { ViewColumn } from 'vscode';
import { ResourceData } from '../tekton-hub-client';
import { Disposable } from '../util/disposable';
import { HubTask } from './hub-common';
import { TaskPageView } from './task-page-view';


Expand All @@ -18,7 +18,7 @@ export class TaskPageManager extends Disposable {
super();
}

showTaskPageView(task: ResourceData, tknVersion: string): void {
showTaskPageView(task: HubTask, tknVersion: string): void {
if (!this.activePreview){
this.createTaskPageView(task, tknVersion);

Expand All @@ -27,7 +27,7 @@ export class TaskPageManager extends Disposable {
}
}

createTaskPageView(task: ResourceData, tknVersion: string): TaskPageView {
createTaskPageView(task: HubTask, tknVersion: string): TaskPageView {
const preview = TaskPageView.create(task, tknVersion, ViewColumn.One);

this.activePreview = preview;
Expand Down
Loading

0 comments on commit 53b6b2c

Please sign in to comment.