From 7a7e4eba5b07ac01982d4c2c21397952655abde0 Mon Sep 17 00:00:00 2001 From: Mohit Suman Date: Fri, 19 Jul 2019 16:37:10 +0530 Subject: [PATCH 01/18] fix project loading if no data present --- src/cli.ts | 17 +++++++------- src/odo.ts | 53 ++++++++++++------------------------------ test/extension.test.ts | 1 - 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 80688c93d..5ade0d946 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,14 @@ export interface CliExitData { readonly stdout: string; readonly stderr: string; } +export interface ICli { + execute(cmd: string, opts?: ExecOptions): Promise; +} + +export interface OdoChannel { + print(text: string): void; + show(): void; +} export class Cli implements ICli { private static instance: Cli; @@ -49,15 +57,6 @@ export class Cli implements ICli { } } -export interface ICli { - execute(cmd: string, opts?: ExecOptions): Promise; -} - -export interface OdoChannel { - print(text: string): void; - show(): void; -} - class OdoChannelImpl implements OdoChannel { private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel("OpenShift"); diff --git a/src/odo.ts b/src/odo.ts index 39b9a34bf..49a0dac2e 100644 --- a/src/odo.ts +++ b/src/odo.ts @@ -572,12 +572,7 @@ export class OdoImpl implements Odo { public async _getProjects(cluster: OpenShiftObject): Promise { return this.execute(Command.listProjects()).then((result) => { - let data: any[] = []; - try { - data = JSON.parse(result.stdout).items; - } catch (ignore) { - } - const projs = data.map((value) => value.metadata.name); + const projs = this.loadItems(result).map((value) => value.metadata.name); return projs.map((value) => new OpenShiftObjectImpl(cluster, value, ContextType.PROJECT, false, OdoImpl.instance)); // TODO: load projects form workspace folders and add missing ones to the model even they @@ -598,14 +593,7 @@ export class OdoImpl implements Odo { public async _getApplications(project: OpenShiftObject): Promise { const result: cliInstance.CliExitData = await this.execute(Command.listApplications(project.getName())); - let data: any[] = []; - try { - data = JSON.parse(result.stdout).items; - } catch (ignore) { - // show no apps if output is not correct json - // see https://github.com/redhat-developer/odo/issues/1327 - } - let apps: string[] = data.map((value) => value.metadata.name); + let apps: string[] = this.loadItems(result).map((value) => value.metadata.name); apps = [...new Set(apps)]; // remove duplicates form array // extract apps from local not yet deployed components OdoImpl.data.getSettings().forEach((component) => { @@ -634,14 +622,7 @@ export class OdoImpl implements Odo { public async _getComponents(application: OpenShiftObject): Promise { const result: cliInstance.CliExitData = await this.execute(Command.listComponents(application.getParent().getName(), application.getName()), Platform.getUserHomePath()); - let data: any[] = []; - try { - data = JSON.parse(result.stdout).items; - } catch (ignore) { - // show no apps if output is not correct json - // see https://github.com/openshift/odo/issues/1521 - } - const componentObject = data.map(value => ({ name: value.metadata.name, source: value.spec.source })); + const componentObject = this.loadItems(result).map(value => ({ name: value.metadata.name, source: value.spec.source })); const deployedComponents = componentObject.map((value) => { let compSource: string = ''; @@ -701,15 +682,7 @@ export class OdoImpl implements Odo { public async _getRoutes(component: OpenShiftObject): Promise { const app = component.getParent(); const result: cliInstance.CliExitData = await this.execute(Command.getComponentUrl(app.getParent().getName(), app.getName(), component.getName()), component.contextPath ? component.contextPath.fsPath : Platform.getUserHomePath(), false); - - let data: any[] = []; - try { - const items = JSON.parse(result.stdout).items; - if (items) data = items; - } catch (ignore) { - } - - return data.map((value) => new OpenShiftObjectImpl(component, value.metadata.name, ContextType.COMPONENT_ROUTE, false, OdoImpl.instance, TreeItemCollapsibleState.None)); + return this.loadItems(result).map((value) => new OpenShiftObjectImpl(component, value.metadata.name, ContextType.COMPONENT_ROUTE, false, OdoImpl.instance, TreeItemCollapsibleState.None)); } async getStorageNames(component: OpenShiftObject): Promise { @@ -717,17 +690,11 @@ export class OdoImpl implements Odo { } public async _getStorageNames(component: OpenShiftObject): Promise { - let data: any[] = []; const app = component.getParent(); const appName = app.getName(); const projName = app.getParent().getName(); const result: cliInstance.CliExitData = await this.execute(Command.listStorageNames(projName, appName, component.getName()), component.contextPath ? component.contextPath.fsPath : Platform.getUserHomePath()); - try { - const items = JSON.parse(result.stdout).items; - if (items) data = items; - } catch (ignore) { - } - return data.map((value) => new OpenShiftObjectImpl(component, value.metadata.name, ContextType.STORAGE, false, OdoImpl.instance, TreeItemCollapsibleState.None)); + return this.loadItems(result).map((value) => new OpenShiftObjectImpl(component, value.metadata.name, ContextType.STORAGE, false, OdoImpl.instance, TreeItemCollapsibleState.None)); } public async getComponentTypeVersions(componentName: string) { @@ -970,4 +937,14 @@ export class OdoImpl implements Odo { }); } } + + loadItems(result: cliInstance.CliExitData) { + let data: any[] = []; + try { + const items = JSON.parse(result.stdout).items; + if (items) data = items; + } catch (ignore) { + } + return data; + } } diff --git a/test/extension.test.ts b/test/extension.test.ts index 38921b6ec..f8ecc3106 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -25,7 +25,6 @@ import packagejson = require('../package.json'); import { OpenShiftExplorer } from '../src/explorer'; import path = require('path'); import { OdoImpl, ContextType, OpenShiftObjectImpl } from '../src/odo'; -import { TestItem } from './openshift/testOSItem'; const expect = chai.expect; chai.use(sinonChai); From 7c870dc6f395718be30ce6644a4a4f2cea5f0881 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Fri, 19 Jul 2019 17:10:14 -0700 Subject: [PATCH 02/18] Show menu 'Start Build' only for BuildConfig resources --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 587511586..90b2df4b8 100644 --- a/package.json +++ b/package.json @@ -510,7 +510,7 @@ { "command": "clusters.openshift.build.start", "group": "2@0", - "when": "view == extension.vsKubernetesExplorer && viewItem =~ /vsKubernetes\\.resource(?!\\.namespace).*/i" + "when": "view == extension.vsKubernetesExplorer && viewItem == vsKubernetes.resource.bc" }, { "command": "openshift.catalog.listComponents", From 72bfaa4f1908c1e63741582d63631d1dc80c5058 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Thu, 18 Jul 2019 22:38:58 -0700 Subject: [PATCH 03/18] Stub for 'Show Log' and 'Follow Log' commands --- package.json | 10 ++++++++++ src/extension.ts | 2 ++ 2 files changed, 12 insertions(+) diff --git a/package.json b/package.json index 90b2df4b8..f905ffa46 100644 --- a/package.json +++ b/package.json @@ -373,6 +373,11 @@ { "command": "clusters.openshift.openProjectConsole", "title": "Open Project in Console" + }, + { + "command": "clusters.openshift.build.showLog", + "title": "Show Log", + "category": "OpenShift" } ], "keybindings": [ @@ -512,6 +517,11 @@ "group": "2@0", "when": "view == extension.vsKubernetesExplorer && viewItem == vsKubernetes.resource.bc" }, + { + "command": "clusters.openshift.build.showLog", + "group": "2@0", + "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" + }, { "command": "openshift.catalog.listComponents", "when": "view == openshiftProjectExplorer && viewItem == cluster && isLoggedIn", diff --git a/src/extension.ts b/src/extension.ts index 7a32592f8..9c950bb92 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import * as k8s from 'vscode-kubernetes-tools-api'; import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; import { DeploymentConfigNodeContributor } from './k8s/deployment'; import open = require("open"); +import * as Build from './k8s/build'; let clusterExplorer: k8s.ClusterExplorerV1 | undefined = undefined; import { Odo, OdoImpl } from './odo'; @@ -55,6 +56,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('openshift.component.describe.palette', (context) => execute(Component.describe, context)), vscode.commands.registerCommand('openshift.component.create', (context) => execute(Component.create, context)), vscode.commands.registerCommand('clusters.openshift.build.start', (context) => execute(Component.startBuild, context)), + vscode.commands.registerCommand('clusters.openshift.build.showLog', (context) => execute(Build.showLog, context)), vscode.commands.registerCommand('openshift.component.createFromLocal', (context) => execute(Component.createFromLocal, context)), vscode.commands.registerCommand('openshift.component.createFromGit', (context) => execute(Component.createFromGit, context)), vscode.commands.registerCommand('openshift.component.createFromBinary', (context) => execute(Component.createFromBinary, context)), From 9fe463b8d2d267f301ae3596449eec8184b08c14 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Thu, 18 Jul 2019 22:41:04 -0700 Subject: [PATCH 04/18] Add src/build.ts file --- src/k8s/build.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/k8s/build.ts diff --git a/src/k8s/build.ts b/src/k8s/build.ts new file mode 100644 index 000000000..913be283f --- /dev/null +++ b/src/k8s/build.ts @@ -0,0 +1,9 @@ +import { window } from "vscode"; + +export function showLog(context: any) { + window.showInformationMessage(`oc log ${context.impl.id}`); +} + +export function followLog(context: any) { + window.showInformationMessage(`oc log -f ${context.impl.id}`); +} \ No newline at end of file From 6d15502065242394eb165cd7aaa764eac0ebeaee Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Fri, 19 Jul 2019 15:54:26 +0530 Subject: [PATCH 05/18] Add commands `Show Log` and 'Rebuild' for Build resources in Clusters view --- src/extension.ts | 2 +- src/k8s/build.ts | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 9c950bb92..ff32ddc59 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,7 +22,7 @@ import * as k8s from 'vscode-kubernetes-tools-api'; import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; import { DeploymentConfigNodeContributor } from './k8s/deployment'; import open = require("open"); -import * as Build from './k8s/build'; +import { Build } from './k8s/build'; let clusterExplorer: k8s.ClusterExplorerV1 | undefined = undefined; import { Odo, OdoImpl } from './odo'; diff --git a/src/k8s/build.ts b/src/k8s/build.ts index 913be283f..13cc4ca4f 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -1,9 +1,38 @@ import { window } from "vscode"; +import { OdoImpl, Odo } from "../odo"; -export function showLog(context: any) { - window.showInformationMessage(`oc log ${context.impl.id}`); +export class Command { + + static getBuild(build: string) { + return `oc get build -l buildconfig=${build} -o json`; + } + static showLog(text: string, build: string) { + return `oc logs ${text}${build}`; + } + + static rebuild(build, build1) { + return `oc start-build ${build} --from-build ${build1}`; + } + + static followLog(text: string) { + return `oc logs -f ${text}`; + } + + static delete(build: String) { + return `oc delete ${this.delete}`; + } } -export function followLog(context: any) { - window.showInformationMessage(`oc log -f ${context.impl.id}`); +export class Build { + protected static readonly odo: Odo = OdoImpl.Instance; + + static showLog(context: any) { + let buildName: string; + if (context) { + buildName = context.impl.name; + } else { + + } + Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); + } } \ No newline at end of file From 79a0856a87ccf02bd7dc6a47055e02f045ec7b91 Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Fri, 19 Jul 2019 18:04:13 +0530 Subject: [PATCH 06/18] move startBuild in build.ts --- src/extension.ts | 2 +- src/k8s/build.ts | 51 +++++ src/odo.ts | 6 - src/openshift/component.ts | 21 -- test/openshift/component.test.ts | 334 +++++++++++++++---------------- 5 files changed, 219 insertions(+), 195 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index ff32ddc59..deda2d754 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -55,7 +55,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('openshift.component.describe', (context) => execute(Component.describe, context)), vscode.commands.registerCommand('openshift.component.describe.palette', (context) => execute(Component.describe, context)), vscode.commands.registerCommand('openshift.component.create', (context) => execute(Component.create, context)), - vscode.commands.registerCommand('clusters.openshift.build.start', (context) => execute(Component.startBuild, context)), + vscode.commands.registerCommand('clusters.openshift.build.start', (context) => execute(Build.startBuild, context)), vscode.commands.registerCommand('clusters.openshift.build.showLog', (context) => execute(Build.showLog, context)), vscode.commands.registerCommand('openshift.component.createFromLocal', (context) => execute(Component.createFromLocal, context)), vscode.commands.registerCommand('openshift.component.createFromGit', (context) => execute(Component.createFromGit, context)), diff --git a/src/k8s/build.ts b/src/k8s/build.ts index 13cc4ca4f..567afc4ac 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -1,11 +1,17 @@ import { window } from "vscode"; import { OdoImpl, Odo } from "../odo"; +import { Progress } from "../util/progress"; export class Command { + static startBuild(comp: string) { + return `oc start-build ${comp}`; + } + static getBuild(build: string) { return `oc get build -l buildconfig=${build} -o json`; } + static showLog(text: string, build: string) { return `oc logs ${text}${build}`; } @@ -21,11 +27,40 @@ export class Command { static delete(build: String) { return `oc delete ${this.delete}`; } + + static buildConfig() { + return `oc get buildConfig -o json`; + } } export class Build { protected static readonly odo: Odo = OdoImpl.Instance; + static async getBuild(text: string) { + const buildConfigName = []; + const buildConfigData = await Build.odo.execute(Command.buildConfig()); + const buildConfigJson: JSON = JSON.parse(buildConfigData.stdout); + buildConfigJson['items'].forEach((key: any) => { + buildConfigName.push(key.metadata.name); + }); + if (buildConfigName.length === 0) throw Error('You have no build available to start'); + return await window.showQuickPick(buildConfigName, {placeHolder: text}); + } + + static async startBuild(context: any) { + let buildName: string; + if (context) { + buildName = context.id; + } else { + buildName = await Build.getBuild("Select the build to start"); + } + if (!buildName) return null; + return Progress.execFunctionWithProgress(`Starting build`, async () => { + return Build.odo.execute(Command.startBuild(buildName)); + }).then(() => `Build '${buildName}' successfully started`) + .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); + } + static showLog(context: any) { let buildName: string; if (context) { @@ -35,4 +70,20 @@ export class Build { } Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); } + + static rebuild(context) { + let buildName: string; + if (context) { + buildName = context.impl.name; + } + Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); + } + + static followLog() { + + } + + static delete() { + + } } \ No newline at end of file diff --git a/src/odo.ts b/src/odo.ts index 49a0dac2e..0665befaa 100644 --- a/src/odo.ts +++ b/src/odo.ts @@ -153,12 +153,6 @@ export class Command { static listComponentPorts(project: string, app: string, component: string) { return `oc get service ${component}-${app} --namespace ${project} -o jsonpath="{range .spec.ports[*]}{.port}{','}{end}"`; } - static buildConfig() { - return `oc get buildConfig -o json`; - } - static startBuild(comp: string) { - return `oc start-build ${comp}`; - } static linkComponentTo(project: string, app: string, component: string, componentToLink: string, port?: string) { return `odo project set ${project} && odo application set ${app} && odo component set ${component} && odo link ${componentToLink} --wait${port ? ' --port ' + port : ''}`; } diff --git a/src/openshift/component.ts b/src/openshift/component.ts index 0c4813350..d36ac957d 100644 --- a/src/openshift/component.ts +++ b/src/openshift/component.ts @@ -59,27 +59,6 @@ export class Component extends OpenShiftItem { return command.catch((err) => Promise.reject(`Failed to create Component with error '${err}'`)); } - static async startBuild(context: any) { - let buildName: string; - if (context) { - buildName = context.id; - } else { - const buildConfigName = []; - const buildConfigData = await Component.odo.execute(Command.buildConfig()); - const buildConfigJson: JSON = JSON.parse(buildConfigData.stdout); - buildConfigJson['items'].forEach((key: any) => { - buildConfigName.push(key.metadata.name); - }); - if (buildConfigName.length === 0) throw Error('You have no build available to start'); - buildName = await window.showQuickPick(buildConfigName, {placeHolder: "Select the build to start"}); - } - if (!buildName) return null; - return Progress.execFunctionWithProgress(`Starting build`, async () => { - return Component.odo.execute(Command.startBuild(buildName)); - }).then(() => `Build '${buildName}' successfully started`) - .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); - } - static async del(treeItem: OpenShiftObject): Promise { const component = await Component.getOpenShiftCmdData(treeItem, "From which Project do you want to delete Component", diff --git a/test/openshift/component.test.ts b/test/openshift/component.test.ts index 8a0ec3c16..32fad753c 100644 --- a/test/openshift/component.test.ts +++ b/test/openshift/component.test.ts @@ -389,173 +389,173 @@ suite('OpenShift/Component', () => { }); }); - suite('start build', () => { - const context = { - id: "nodejs-comp-nodejs-app", - metadata: undefined, - namespace: null, - nodeCategory: "Kubernetes-explorer-node", - nodeType: "resource", - resourceId: "bc/nodejs-comp-nodejs-app" - }; - - const mockData = `{ - "apiVersion": "v1", - "items": [ - { - "apiVersion": "build.openshift.io/v1", - "kind": "BuildConfig", - "metadata": { - "annotations": { - "app.kubernetes.io/component-source-type": "git", - "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" - }, - "creationTimestamp": "2019-07-15T09:18:43Z", - "labels": { - "app": "nodejs-app", - "app.kubernetes.io/component-name": "nodejs-comp", - "app.kubernetes.io/component-type": "nodejs", - "app.kubernetes.io/component-version": "latest", - "app.kubernetes.io/name": "nodejs-app" - }, - "name": "nodejs-comp-nodejs-app", - "namespace": "myproject", - "resourceVersion": "116630", - "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-comp-nodejs-app", - "uid": "8a66b3ff-a6e1-11e9-8dbe-22967c349399" - }, - "spec": { - "failedBuildsHistoryLimit": 5, - "nodeSelector": null, - "output": { - "to": { - "kind": "ImageStreamTag", - "name": "nodejs-comp-nodejs-app:latest" - } - }, - "postCommit": {}, - "resources": {}, - "runPolicy": "Serial", - "source": { - "git": { - "ref": "master", - "uri": "https://github.com/sclorg/nodejs-ex" - }, - "type": "Git" - }, - "strategy": { - "sourceStrategy": { - "from": { - "kind": "ImageStreamTag", - "name": "nodejs:latest", - "namespace": "openshift" - } - }, - "type": "Source" - }, - "successfulBuildsHistoryLimit": 5, - "triggers": [] - }, - "status": { - "lastVersion": 8 - } - }, - { - "apiVersion": "build.openshift.io/v1", - "kind": "BuildConfig", - "metadata": { - "annotations": { - "app.kubernetes.io/component-source-type": "git", - "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" - }, - "creationTimestamp": "2019-07-15T10:00:53Z", - "labels": { - "app": "nodejs-app", - "app.kubernetes.io/component-name": "", - "app.kubernetes.io/component-type": "nodejs", - "app.kubernetes.io/component-version": "latest", - "app.kubernetes.io/name": "nodejs-app" - }, - "name": "nodejs-app", - "namespace": "myproject", - "resourceVersion": "135879", - "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-app", - "uid": "6e7a00dd-a6e7-11e9-8dbe-22967c349399" - }, - "spec": { - "failedBuildsHistoryLimit": 5, - "nodeSelector": null, - "output": { - "to": { - "kind": "ImageStreamTag", - "name": "nodejs-app:latest" - } - }, - "postCommit": {}, - "resources": {}, - "runPolicy": "Serial", - "source": { - "git": { - "ref": "master", - "uri": "https://github.com/sclorg/nodejs-ex" - }, - "type": "Git" - }, - "strategy": { - "sourceStrategy": { - "from": { - "kind": "ImageStreamTag", - "name": "nodejs:latest", - "namespace": "openshift" - } - }, - "type": "Source" - }, - "successfulBuildsHistoryLimit": 5, - "triggers": [] - }, - "status": { - "lastVersion": 5 - } - } - ], - "kind": "List", - "metadata": { - "resourceVersion": "", - "selfLink": "" - } - }`; - - setup(() => { - execStub.resolves({ error: undefined, stdout: mockData, stderr: '' }); - quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.onFirstCall().resolves("nodejs-comp-nodejs-app"); - }); - - test('works from context menu', async () => { - const result = await Component.startBuild(context); - - expect(result).equals(`Build '${context.id}' successfully started`); - expect(execStub).calledWith(Command.startBuild(context.id)); - }); - - test('works with no context', async () => { - const result = await Component.startBuild(null); - - expect(result).equals(`Build '${context.id}' successfully started`); - expect(execStub).calledWith(Command.startBuild(context.id)); - }); - - test('wraps errors in additional info', async () => { - execStub.rejects(errorMessage); - - try { - await Component.startBuild(context); - } catch (err) { - expect(err).equals(`Failed to start build with error '${errorMessage}'`); - } - }); - }); + // suite('start build', () => { + // const context = { + // id: "nodejs-comp-nodejs-app", + // metadata: undefined, + // namespace: null, + // nodeCategory: "Kubernetes-explorer-node", + // nodeType: "resource", + // resourceId: "bc/nodejs-comp-nodejs-app" + // }; + + // const mockData = `{ + // "apiVersion": "v1", + // "items": [ + // { + // "apiVersion": "build.openshift.io/v1", + // "kind": "BuildConfig", + // "metadata": { + // "annotations": { + // "app.kubernetes.io/component-source-type": "git", + // "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" + // }, + // "creationTimestamp": "2019-07-15T09:18:43Z", + // "labels": { + // "app": "nodejs-app", + // "app.kubernetes.io/component-name": "nodejs-comp", + // "app.kubernetes.io/component-type": "nodejs", + // "app.kubernetes.io/component-version": "latest", + // "app.kubernetes.io/name": "nodejs-app" + // }, + // "name": "nodejs-comp-nodejs-app", + // "namespace": "myproject", + // "resourceVersion": "116630", + // "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-comp-nodejs-app", + // "uid": "8a66b3ff-a6e1-11e9-8dbe-22967c349399" + // }, + // "spec": { + // "failedBuildsHistoryLimit": 5, + // "nodeSelector": null, + // "output": { + // "to": { + // "kind": "ImageStreamTag", + // "name": "nodejs-comp-nodejs-app:latest" + // } + // }, + // "postCommit": {}, + // "resources": {}, + // "runPolicy": "Serial", + // "source": { + // "git": { + // "ref": "master", + // "uri": "https://github.com/sclorg/nodejs-ex" + // }, + // "type": "Git" + // }, + // "strategy": { + // "sourceStrategy": { + // "from": { + // "kind": "ImageStreamTag", + // "name": "nodejs:latest", + // "namespace": "openshift" + // } + // }, + // "type": "Source" + // }, + // "successfulBuildsHistoryLimit": 5, + // "triggers": [] + // }, + // "status": { + // "lastVersion": 8 + // } + // }, + // { + // "apiVersion": "build.openshift.io/v1", + // "kind": "BuildConfig", + // "metadata": { + // "annotations": { + // "app.kubernetes.io/component-source-type": "git", + // "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" + // }, + // "creationTimestamp": "2019-07-15T10:00:53Z", + // "labels": { + // "app": "nodejs-app", + // "app.kubernetes.io/component-name": "", + // "app.kubernetes.io/component-type": "nodejs", + // "app.kubernetes.io/component-version": "latest", + // "app.kubernetes.io/name": "nodejs-app" + // }, + // "name": "nodejs-app", + // "namespace": "myproject", + // "resourceVersion": "135879", + // "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-app", + // "uid": "6e7a00dd-a6e7-11e9-8dbe-22967c349399" + // }, + // "spec": { + // "failedBuildsHistoryLimit": 5, + // "nodeSelector": null, + // "output": { + // "to": { + // "kind": "ImageStreamTag", + // "name": "nodejs-app:latest" + // } + // }, + // "postCommit": {}, + // "resources": {}, + // "runPolicy": "Serial", + // "source": { + // "git": { + // "ref": "master", + // "uri": "https://github.com/sclorg/nodejs-ex" + // }, + // "type": "Git" + // }, + // "strategy": { + // "sourceStrategy": { + // "from": { + // "kind": "ImageStreamTag", + // "name": "nodejs:latest", + // "namespace": "openshift" + // } + // }, + // "type": "Source" + // }, + // "successfulBuildsHistoryLimit": 5, + // "triggers": [] + // }, + // "status": { + // "lastVersion": 5 + // } + // } + // ], + // "kind": "List", + // "metadata": { + // "resourceVersion": "", + // "selfLink": "" + // } + // }`; + + // setup(() => { + // execStub.resolves({ error: undefined, stdout: mockData, stderr: '' }); + // quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + // quickPickStub.onFirstCall().resolves("nodejs-comp-nodejs-app"); + // }); + + // test('works from context menu', async () => { + // const result = await Component.startBuild(context); + + // expect(result).equals(`Build '${context.id}' successfully started`); + // expect(execStub).calledWith(Command.startBuild(context.id)); + // }); + + // test('works with no context', async () => { + // const result = await Component.startBuild(null); + + // expect(result).equals(`Build '${context.id}' successfully started`); + // expect(execStub).calledWith(Command.startBuild(context.id)); + // }); + + // test('wraps errors in additional info', async () => { + // execStub.rejects(errorMessage); + + // try { + // await Component.startBuild(context); + // } catch (err) { + // expect(err).equals(`Failed to start build with error '${errorMessage}'`); + // } + // }); + // }); suite('del', () => { From bb77f6acd616e159278e907d8b835882b2c60c73 Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Fri, 19 Jul 2019 19:32:57 +0530 Subject: [PATCH 07/18] Add commands `Show Log`, `Follow Log`, `Delete` and 'Rebuild' for Build resources in Clusters view --- package.json | 44 ++++++++++++++++++++++++++++++ src/extension.ts | 4 +++ src/k8s/build.ts | 70 +++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index f905ffa46..60a59aed3 100644 --- a/package.json +++ b/package.json @@ -378,6 +378,31 @@ "command": "clusters.openshift.build.showLog", "title": "Show Log", "category": "OpenShift" + }, + { + "command": "clusters.openshift.build.followLog", + "title": "Follow Log", + "category": "OpenShift" + }, + { + "command": "clusters.openshift.build.delete.palette", + "title": "Delete Build", + "category": "OpenShift" + }, + { + "command": "clusters.openshift.build.delete", + "title": "Delete", + "category": "OpenShift" + }, + { + "command": "clusters.openshift.build.delete", + "title": "Delete build", + "category": "OpenShift" + }, + { + "command": "clusters.openshift.build.rebuild", + "title": "Rebuild", + "category": "OpenShift" } ], "keybindings": [ @@ -428,6 +453,10 @@ "command": "openshift.component.create", "when": "view == openshiftProjectExplorer" }, + { + "command": "clusters.openshift.build.delete", + "when": "view == openshiftProjectExplorer" + }, { "command": "openshift.app.describe", "when": "view == openshiftProjectExplorer" @@ -519,6 +548,21 @@ }, { "command": "clusters.openshift.build.showLog", + "group": "2@1", + "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" + }, + { + "command": "clusters.openshift.build.followLog", + "group": "2@2", + "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" + }, + { + "command": "clusters.openshift.build.delete", + "group": "2@3", + "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" + }, + { + "command": "clusters.openshift.build.rebuild", "group": "2@0", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, diff --git a/src/extension.ts b/src/extension.ts index deda2d754..8328209f2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -57,6 +57,10 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('openshift.component.create', (context) => execute(Component.create, context)), vscode.commands.registerCommand('clusters.openshift.build.start', (context) => execute(Build.startBuild, context)), vscode.commands.registerCommand('clusters.openshift.build.showLog', (context) => execute(Build.showLog, context)), + vscode.commands.registerCommand('clusters.openshift.build.followLog', (context) => execute(Build.followLog, context)), + vscode.commands.registerCommand('clusters.openshift.build.delete', (context) => execute(Build.delete, context)), + vscode.commands.registerCommand('clusters.openshift.build.delete.palette', (context) => execute(Build.delete, context)), + vscode.commands.registerCommand('clusters.openshift.build.rebuild', (context) => execute(Build.rebuild, context)), vscode.commands.registerCommand('openshift.component.createFromLocal', (context) => execute(Component.createFromLocal, context)), vscode.commands.registerCommand('openshift.component.createFromGit', (context) => execute(Component.createFromGit, context)), vscode.commands.registerCommand('openshift.component.createFromBinary', (context) => execute(Component.createFromBinary, context)), diff --git a/src/k8s/build.ts b/src/k8s/build.ts index 567afc4ac..aa322aa4d 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -25,12 +25,16 @@ export class Command { } static delete(build: String) { - return `oc delete ${this.delete}`; + return `oc delete build ${build}`; } static buildConfig() { return `oc get buildConfig -o json`; } + + static getParentBuild(childBuild: string) { + return `oc get build ${childBuild} -o jsonpath="{.metadata.labels['buildconfig']}"`; + } } export class Build { @@ -49,11 +53,8 @@ export class Build { static async startBuild(context: any) { let buildName: string; - if (context) { - buildName = context.id; - } else { - buildName = await Build.getBuild("Select the build to start"); - } + if (context) buildName = context.id; + else buildName = await Build.getBuild("Select the build to start"); if (!buildName) return null; return Progress.execFunctionWithProgress(`Starting build`, async () => { return Build.odo.execute(Command.startBuild(buildName)); @@ -61,29 +62,72 @@ export class Build { .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); } - static showLog(context: any) { + static async getAllBuild(text: string) { + const buildName = []; + const build = await Build.getBuild("select the build"); + if (!build) return null; + const getBuild = await Build.odo.execute(Command.getBuild(build)); + const buildJson: JSON = JSON.parse(getBuild.stdout); + buildJson['items'].forEach(element => { + buildName.push(element.metadata.name); + }); + if (buildName.length === 0) throw Error('You have no build available'); + return await window.showQuickPick(buildName, {placeHolder: text}); + } + + static async showLog(context: any) { let buildName: string; if (context) { buildName = context.impl.name; } else { - + const build = await Build.getAllBuild("select the build too see the logs"); + if (!build) return null; + else buildName = build; } Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); } - static rebuild(context) { + static async rebuild(context) { let buildName: string; + let parentBuild: string; if (context) { buildName = context.impl.name; + const getParentBuild = await Build.odo.execute(Command.getParentBuild(buildName)); + parentBuild = getParentBuild.stdout; + } else { + buildName = await Build.getAllBuild("select too rebuild"); + if (!buildName) return null; + const getParentBuild = await Build.odo.execute(Command.getParentBuild(buildName)); + parentBuild = getParentBuild.stdout; } - Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); + Build.odo.executeInTerminal(Command.rebuild(parentBuild, buildName)); } - static followLog() { - + static async followLog(context) { + let buildName: string; + if (context) { + buildName = context.impl.name; + } else { + const build = await Build.getAllBuild("select the build too see the logs"); + if (!build) return null; + else buildName = build; + } + Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); } - static delete() { + static async delete(context) { + let buildName; + if (context) { + buildName = context.impl.name; + } else { + const build = await Build.getAllBuild("select the build too delete"); + if (!build) return null; + else buildName = build; + } + return Progress.execFunctionWithProgress(`Starting build`, async () => { + return Build.odo.execute(Command.delete(buildName)); + }).then(() => `Build '${buildName}' successfully deleted`) + .catch((err) => Promise.reject(`Failed to delete build with error '${err}'`)); } } \ No newline at end of file From a0c8440a9896f9e9b95be43fe8c52237550ccd1d Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Sat, 20 Jul 2019 01:21:20 +0530 Subject: [PATCH 08/18] Fix unit test --- src/k8s/build.ts | 26 +-- test/k8s/build.test.ts | 274 +++++++++++++++++++++++++++++++ test/openshift/component.test.ts | 168 ------------------- 3 files changed, 287 insertions(+), 181 deletions(-) create mode 100644 test/k8s/build.test.ts diff --git a/src/k8s/build.ts b/src/k8s/build.ts index aa322aa4d..ec68777e5 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -12,16 +12,16 @@ export class Command { return `oc get build -l buildconfig=${build} -o json`; } - static showLog(text: string, build: string) { - return `oc logs ${text}${build}`; + static showLog(build: string, text: string) { + return `oc logs ${build}${text}`; } - static rebuild(build, build1) { + static rebuild(build: String, build1: String) { return `oc start-build ${build} --from-build ${build1}`; } - static followLog(text: string) { - return `oc logs -f ${text}`; + static followLog(build: string, text: string) { + return `oc logs -f ${build}${text}`; } static delete(build: String) { @@ -40,7 +40,7 @@ export class Command { export class Build { protected static readonly odo: Odo = OdoImpl.Instance; - static async getBuild(text: string) { + static async getBuild(text: string): Promise { const buildConfigName = []; const buildConfigData = await Build.odo.execute(Command.buildConfig()); const buildConfigJson: JSON = JSON.parse(buildConfigData.stdout); @@ -51,7 +51,7 @@ export class Build { return await window.showQuickPick(buildConfigName, {placeHolder: text}); } - static async startBuild(context: any) { + static async startBuild(context: { id: any; metadata?: any; namespace?: any; nodeCategory?: string; nodeType?: string; resourceId?: string; }): Promise { let buildName: string; if (context) buildName = context.id; else buildName = await Build.getBuild("Select the build to start"); @@ -62,7 +62,7 @@ export class Build { .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); } - static async getAllBuild(text: string) { + static async getAllBuild(text: string): Promise { const buildName = []; const build = await Build.getBuild("select the build"); if (!build) return null; @@ -75,7 +75,7 @@ export class Build { return await window.showQuickPick(buildName, {placeHolder: text}); } - static async showLog(context: any) { + static async showLog(context: { id?: string; impl: any; }): Promise { let buildName: string; if (context) { buildName = context.impl.name; @@ -87,7 +87,7 @@ export class Build { Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); } - static async rebuild(context) { + static async rebuild(context: { id?: string; impl: any; }): Promise { let buildName: string; let parentBuild: string; if (context) { @@ -103,7 +103,7 @@ export class Build { Build.odo.executeInTerminal(Command.rebuild(parentBuild, buildName)); } - static async followLog(context) { + static async followLog(context: { id?: string; impl: any; }): Promise { let buildName: string; if (context) { buildName = context.impl.name; @@ -112,10 +112,10 @@ export class Build { if (!build) return null; else buildName = build; } - Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); + Build.odo.executeInTerminal(Command.followLog(buildName, '-build')); } - static async delete(context) { + static async delete(context: { id?: string; impl: any; }): Promise { let buildName; if (context) { buildName = context.impl.name; diff --git a/test/k8s/build.test.ts b/test/k8s/build.test.ts new file mode 100644 index 000000000..1035977bb --- /dev/null +++ b/test/k8s/build.test.ts @@ -0,0 +1,274 @@ +/*----------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as sinon from 'sinon'; +import { OdoImpl } from '../../src/odo'; +import { Build, Command } from '../../src/k8s/build'; +import { Progress } from '../../src/util/progress'; + +const expect = chai.expect; +chai.use(sinonChai); + +suite('K8s/build', () => { + let quickPickStub: sinon.SinonStub; + let sandbox: sinon.SinonSandbox; + let termStub: sinon.SinonStub; + let execStub: sinon.SinonStub; + const errorMessage = 'FATAL ERROR'; + const context = { + id: 'dummy', + impl: { + id: 'build/nodejs-copm-nodejs-comp-8', + metadata: undefined, + name: 'nodejs-copm-nodejs-comp-8', + namespace: 'myproject' + } + }; + + const buildData = `{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "build.openshift.io/v1", + "kind": "Build", + "metadata": { + "annotations": { + "openshift.io/build-config.name": "nodejs-copm-nodejs-comp" + }, + "creationTimestamp": "2019-07-19T13:29:52Z", + "labels": { + "app": "nodejs-comp" + }, + "name": "nodejs-copm-nodejs-comp-8", + "namespace": "myproject", + "resourceVersion": "60465", + "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/builds/nodejs-copm-nodejs-comp-8", + "uid": "4a5be709-aa29-11e9-99f2-5e5dae55d430" + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } + }`; + + setup(() => { + sandbox = sinon.createSandbox(); + termStub = sandbox.stub(OdoImpl.prototype, 'executeInTerminal'); + execStub = sandbox.stub(OdoImpl.prototype, 'execute').resolves({ stdout: "" }); + sandbox.stub(Progress, 'execFunctionWithProgress').yields(); + }); + + teardown(() => { + sandbox.restore(); + }); + + suite('start build', () => { + const context = { + id: "nodejs-comp-nodejs-app", + metadata: undefined, + namespace: null, + nodeCategory: "Kubernetes-explorer-node", + nodeType: "resource", + resourceId: "bc/nodejs-comp-nodejs-app" + }; + + const mockData = `{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "build.openshift.io/v1", + "kind": "BuildConfig", + "metadata": { + "annotations": { + "app.kubernetes.io/component-source-type": "git", + "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" + }, + "creationTimestamp": "2019-07-15T09:18:43Z", + "name": "nodejs-comp-nodejs-app", + "namespace": "myproject", + "resourceVersion": "116630", + "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-comp-nodejs-app", + "uid": "8a66b3ff-a6e1-11e9-8dbe-22967c349399" + }, + "status": { + "lastVersion": 8 + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } + }`; + + setup(() => { + execStub.resolves({ error: undefined, stdout: mockData, stderr: '' }); + quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + quickPickStub.resolves("nodejs-comp-nodejs-app"); + }); + + test('works from context menu', async () => { + const result = await Build.startBuild(context); + + expect(result).equals(`Build '${context.id}' successfully started`); + expect(execStub).calledWith(Command.startBuild(context.id)); + }); + + test('works with no context', async () => { + const result = await Build.startBuild(null); + + expect(result).equals(`Build '${context.id}' successfully started`); + expect(execStub).calledWith(Command.startBuild(context.id)); + }); + + test('returns null when no build selected', async () => { + quickPickStub.resolves(); + const result = await Build.startBuild(null); + expect(result).null; + }); + + test('wraps errors in additional info', async () => { + execStub.rejects(errorMessage); + + try { + await Build.startBuild(context); + } catch (err) { + expect(err).equals(`Failed to start build with error '${errorMessage}'`); + } + }); + }); + + suite('Show Log', () => { + + setup(() => { + execStub.resolves({ error: null, stdout: buildData, stderr: '' }); + sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + quickPickStub.onFirstCall().resolves("nodejs-copm-nodejs-comp-8"); + }); + + test('works from context menu', async () => { + await Build.showLog(context); + expect(termStub).calledOnceWith(Command.showLog(context.impl.name, '-build')); + }); + + test('works with no context', async () => { + await Build.showLog(null); + expect(termStub).calledOnceWith(Command.showLog('nodejs-copm-nodejs-comp-8', '-build')); + }); + + test('returns null when no build selected', async () => { + quickPickStub.onFirstCall().resolves(); + const result = await Build.showLog(null); + expect(result).null; + }); + }); + + suite('rebuild', () => { + + setup(() => { + sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + + }); + + test('works from context menu', async () => { + execStub.resolves({ error: null, stdout: "nodejs-copm-nodejs-comp", stderr: '' }); + await Build.rebuild(context); + expect(termStub).calledOnceWith(Command.rebuild("nodejs-copm-nodejs-comp", context.impl.name)); + }); + + test('works with no context', async () => { + execStub.onFirstCall().resolves({ error: null, stdout: buildData, stderr: '' }); + execStub.onSecondCall().resolves({ error: null, stdout: "nodejs-copm-nodejs-comp", stderr: '' }); + await Build.rebuild(null); + expect(termStub).calledOnceWith(Command.rebuild("nodejs-copm-nodejs-comp", "nodejs-copm-nodejs-comp-8")); + }); + + test('returns null when no build selected to rebuild', async () => { + execStub.onFirstCall().resolves({ error: null, stdout: buildData, stderr: '' }); + execStub.onSecondCall().resolves({ error: null, stdout: "nodejs-copm-nodejs-comp", stderr: '' }); + quickPickStub.resolves(); + const result = await Build.rebuild(null); + expect(result).null; + }); + }); + + suite('followLog', () => { + + setup(() => { + execStub.resolves({ error: null, stdout: buildData, stderr: '' }); + sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + }); + + test('works from context menu', async () => { + await Build.followLog(context); + expect(termStub).calledOnceWith(Command.followLog(context.impl.name, '-build')); + }); + + test('works with no context', async () => { + await Build.followLog(null); + expect(termStub).calledOnceWith(Command.followLog('nodejs-copm-nodejs-comp-8', '-build')); + }); + + test('returns null when no build selected', async () => { + quickPickStub.resolves(); + const result = await Build.followLog(null); + expect(result).null; + }); + }); + + suite('Delete', ()=> { + setup(() => { + execStub.resolves({ error: null, stdout: buildData, stderr: '' }); + sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); + quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + }); + + test('works from context menu', async () => { + const result = await Build.delete(context); + + expect(result).equals(`Build '${context.impl.name}' successfully deleted`); + expect(execStub).calledWith(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(Command.delete('nodejs-copm-nodejs-comp-8')); + }); + + test('returns null when no build selected to delete', async () => { + quickPickStub.resolves(); + const result = await Build.delete(null); + expect(result).null; + }); + + test('wraps errors in additional info', async () => { + execStub.rejects(errorMessage); + + try { + await Build.delete(context); + } catch (err) { + expect(err).equals(`Failed to delete build with error '${errorMessage}'`); + } + }); + }); + +}); \ No newline at end of file diff --git a/test/openshift/component.test.ts b/test/openshift/component.test.ts index 32fad753c..cbae2e2ce 100644 --- a/test/openshift/component.test.ts +++ b/test/openshift/component.test.ts @@ -389,174 +389,6 @@ suite('OpenShift/Component', () => { }); }); - // suite('start build', () => { - // const context = { - // id: "nodejs-comp-nodejs-app", - // metadata: undefined, - // namespace: null, - // nodeCategory: "Kubernetes-explorer-node", - // nodeType: "resource", - // resourceId: "bc/nodejs-comp-nodejs-app" - // }; - - // const mockData = `{ - // "apiVersion": "v1", - // "items": [ - // { - // "apiVersion": "build.openshift.io/v1", - // "kind": "BuildConfig", - // "metadata": { - // "annotations": { - // "app.kubernetes.io/component-source-type": "git", - // "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" - // }, - // "creationTimestamp": "2019-07-15T09:18:43Z", - // "labels": { - // "app": "nodejs-app", - // "app.kubernetes.io/component-name": "nodejs-comp", - // "app.kubernetes.io/component-type": "nodejs", - // "app.kubernetes.io/component-version": "latest", - // "app.kubernetes.io/name": "nodejs-app" - // }, - // "name": "nodejs-comp-nodejs-app", - // "namespace": "myproject", - // "resourceVersion": "116630", - // "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-comp-nodejs-app", - // "uid": "8a66b3ff-a6e1-11e9-8dbe-22967c349399" - // }, - // "spec": { - // "failedBuildsHistoryLimit": 5, - // "nodeSelector": null, - // "output": { - // "to": { - // "kind": "ImageStreamTag", - // "name": "nodejs-comp-nodejs-app:latest" - // } - // }, - // "postCommit": {}, - // "resources": {}, - // "runPolicy": "Serial", - // "source": { - // "git": { - // "ref": "master", - // "uri": "https://github.com/sclorg/nodejs-ex" - // }, - // "type": "Git" - // }, - // "strategy": { - // "sourceStrategy": { - // "from": { - // "kind": "ImageStreamTag", - // "name": "nodejs:latest", - // "namespace": "openshift" - // } - // }, - // "type": "Source" - // }, - // "successfulBuildsHistoryLimit": 5, - // "triggers": [] - // }, - // "status": { - // "lastVersion": 8 - // } - // }, - // { - // "apiVersion": "build.openshift.io/v1", - // "kind": "BuildConfig", - // "metadata": { - // "annotations": { - // "app.kubernetes.io/component-source-type": "git", - // "app.kubernetes.io/url": "https://github.com/sclorg/nodejs-ex" - // }, - // "creationTimestamp": "2019-07-15T10:00:53Z", - // "labels": { - // "app": "nodejs-app", - // "app.kubernetes.io/component-name": "", - // "app.kubernetes.io/component-type": "nodejs", - // "app.kubernetes.io/component-version": "latest", - // "app.kubernetes.io/name": "nodejs-app" - // }, - // "name": "nodejs-app", - // "namespace": "myproject", - // "resourceVersion": "135879", - // "selfLink": "/apis/build.openshift.io/v1/namespaces/myproject/buildconfigs/nodejs-app", - // "uid": "6e7a00dd-a6e7-11e9-8dbe-22967c349399" - // }, - // "spec": { - // "failedBuildsHistoryLimit": 5, - // "nodeSelector": null, - // "output": { - // "to": { - // "kind": "ImageStreamTag", - // "name": "nodejs-app:latest" - // } - // }, - // "postCommit": {}, - // "resources": {}, - // "runPolicy": "Serial", - // "source": { - // "git": { - // "ref": "master", - // "uri": "https://github.com/sclorg/nodejs-ex" - // }, - // "type": "Git" - // }, - // "strategy": { - // "sourceStrategy": { - // "from": { - // "kind": "ImageStreamTag", - // "name": "nodejs:latest", - // "namespace": "openshift" - // } - // }, - // "type": "Source" - // }, - // "successfulBuildsHistoryLimit": 5, - // "triggers": [] - // }, - // "status": { - // "lastVersion": 5 - // } - // } - // ], - // "kind": "List", - // "metadata": { - // "resourceVersion": "", - // "selfLink": "" - // } - // }`; - - // setup(() => { - // execStub.resolves({ error: undefined, stdout: mockData, stderr: '' }); - // quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - // quickPickStub.onFirstCall().resolves("nodejs-comp-nodejs-app"); - // }); - - // test('works from context menu', async () => { - // const result = await Component.startBuild(context); - - // expect(result).equals(`Build '${context.id}' successfully started`); - // expect(execStub).calledWith(Command.startBuild(context.id)); - // }); - - // test('works with no context', async () => { - // const result = await Component.startBuild(null); - - // expect(result).equals(`Build '${context.id}' successfully started`); - // expect(execStub).calledWith(Command.startBuild(context.id)); - // }); - - // test('wraps errors in additional info', async () => { - // execStub.rejects(errorMessage); - - // try { - // await Component.startBuild(context); - // } catch (err) { - // expect(err).equals(`Failed to start build with error '${errorMessage}'`); - // } - // }); - // }); - suite('del', () => { setup(() => { From 64e483b5a0dc40260a42eabc2d2c4716db0822f3 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Fri, 19 Jul 2019 17:02:43 -0700 Subject: [PATCH 09/18] Remove duplicate Delete command and show Delete command in last group --- package.json | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 60a59aed3..b6a17ddf6 100644 --- a/package.json +++ b/package.json @@ -394,11 +394,6 @@ "title": "Delete", "category": "OpenShift" }, - { - "command": "clusters.openshift.build.delete", - "title": "Delete build", - "category": "OpenShift" - }, { "command": "clusters.openshift.build.rebuild", "title": "Rebuild", @@ -548,22 +543,22 @@ }, { "command": "clusters.openshift.build.showLog", - "group": "2@1", + "group": "1@1", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, { "command": "clusters.openshift.build.followLog", - "group": "2@2", + "group": "1@2", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, { "command": "clusters.openshift.build.delete", - "group": "2@3", + "group": "2@0", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, { "command": "clusters.openshift.build.rebuild", - "group": "2@0", + "group": "1@0", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, { From 6a17cc300ad050a43cdb7220b5b9d0adec50b46d Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Mon, 22 Jul 2019 01:07:55 +0530 Subject: [PATCH 10/18] Fix vulnerabilities issue (#946) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffc46ae9b..e647734f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1570,9 +1570,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "log-symbols": { From a8eac570a209c8c3dbb1cbe2c771884cb19c35b3 Mon Sep 17 00:00:00 2001 From: Sudhir Verma Date: Mon, 22 Jul 2019 01:24:23 +0530 Subject: [PATCH 11/18] Added License for build.ts and deployment.ts (#948) --- src/k8s/build.ts | 5 +++++ src/k8s/deployment.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/k8s/build.ts b/src/k8s/build.ts index ec68777e5..0487b0ca4 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -1,3 +1,8 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + import { window } from "vscode"; import { OdoImpl, Odo } from "../odo"; import { Progress } from "../util/progress"; diff --git a/src/k8s/deployment.ts b/src/k8s/deployment.ts index 54223f7a2..3fc4f3c72 100644 --- a/src/k8s/deployment.ts +++ b/src/k8s/deployment.ts @@ -1,3 +1,8 @@ +/*----------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; import * as k8s from 'vscode-kubernetes-tools-api'; From cc4ac7fc23ccc23d987662e768c5cdf520f39c75 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Thu, 18 Jul 2019 22:38:58 -0700 Subject: [PATCH 12/18] Stub for 'Show Log' and 'Follow Log' commands --- package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.json b/package.json index b6a17ddf6..4c58d638a 100644 --- a/package.json +++ b/package.json @@ -378,6 +378,7 @@ "command": "clusters.openshift.build.showLog", "title": "Show Log", "category": "OpenShift" +<<<<<<< HEAD }, { "command": "clusters.openshift.build.followLog", @@ -398,6 +399,8 @@ "command": "clusters.openshift.build.rebuild", "title": "Rebuild", "category": "OpenShift" +======= +>>>>>>> Stub for 'Show Log' and 'Follow Log' commands } ], "keybindings": [ @@ -561,6 +564,11 @@ "group": "1@0", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, + { + "command": "clusters.openshift.build.showLog", + "group": "2@0", + "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" + }, { "command": "openshift.catalog.listComponents", "when": "view == openshiftProjectExplorer && viewItem == cluster && isLoggedIn", From c6226aa4df75e290384f89417a8c6945c8a587e5 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Sat, 20 Jul 2019 01:16:29 -0700 Subject: [PATCH 13/18] Fix reveiw issues --- package.json | 8 +- src/extension.ts | 4 +- src/k8s/build.ts | 224 +++++++++++++++++++++++++---------------- test/k8s/build.test.ts | 12 +-- 4 files changed, 154 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index 4c58d638a..2ef9d8b22 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,13 @@ "onCommand:openshift.component.folder.create", "onCommand:openshift.explorer.reportIssue", "onCommand:clusters.openshift.openProjectConsole", - "onCommand:clusters.openshift.useProject" + "onCommand:clusters.openshift.useProject", + "onCommand:clusters.openshift.build.start", + "onCommand:clusters.openshift.build.showLog", + "onCommand:clusters.openshift.build.followLog", + "onCommand:clusters.openshift.build.delete", + "onCommand:clusters.openshift.build.delete.palette", + "onCommand:clusters.openshift.build.rebuild" ], "main": "./out/src/extension", "contributes": { diff --git a/src/extension.ts b/src/extension.ts index 8328209f2..2bd29a752 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,7 +20,7 @@ import path = require('path'); import fsx = require('fs-extra'); import * as k8s from 'vscode-kubernetes-tools-api'; import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; -import { DeploymentConfigNodeContributor } from './k8s/deployment'; +import { BuildConfigNodeContributor } from './k8s/build'; import open = require("open"); import { Build } from './k8s/build'; @@ -109,7 +109,7 @@ export async function activate(context: vscode.ExtensionContext) { clusterExplorer.nodeSources.resourceFolder("Routes", "Routes", "Route", "route").if(isOpenShift).at("Network"), clusterExplorer.nodeSources.resourceFolder("DeploymentConfigs", "DeploymentConfigs", "DeploymentConfig", "dc").if(isOpenShift).at("Workloads"), clusterExplorer.nodeSources.resourceFolder("BuildConfigs", "BuildConfigs", "BuildConfig", "bc").if(isOpenShift).at("Workloads"), - new DeploymentConfigNodeContributor() + new BuildConfigNodeContributor() ]; nodeContributors.forEach(element => { clusterExplorer.registerNodeContributor(element); diff --git a/src/k8s/build.ts b/src/k8s/build.ts index 0487b0ca4..3ed78bd02 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -3,17 +3,68 @@ * Licensed under the MIT License. See LICENSE file in the project root for license information. *-----------------------------------------------------------------------------------------------*/ -import { window } from "vscode"; +import { QuickPickItem, window } from "vscode"; import { OdoImpl, Odo } from "../odo"; import { Progress } from "../util/progress"; +import * as vscode from 'vscode'; +import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; +import * as k8s from 'vscode-kubernetes-tools-api'; + +export class BuildConfigNodeContributor implements ClusterExplorerV1.NodeContributor { + contributesChildren(parent: ClusterExplorerV1.ClusterExplorerNode | undefined): boolean { + return !!parent && parent.nodeType === 'resource' && parent.resourceKind.manifestKind === 'BuildConfig'; + } + + async getChildren(parent: ClusterExplorerV1.ClusterExplorerNode | undefined): Promise { + const kubectl = await k8s.extension.kubectl.v1; + if (kubectl.available) { + const result = await kubectl.api.invokeCommand(`get build -o jsonpath="{range .items[?(.metadata.labels.buildconfig=='${(parent as any).name}')]}{.metadata.namespace}{','}{.metadata.name}{','}{.metadata.annotations.openshift\\.io/build\\.number}{\\"\\n\\"}{end}"`); + const builds = result.stdout.split('\n') + .filter((value) => value !== '') + .map((item: string) => new BuildNode(item.split(',')[0], item.split(',')[1], Number.parseInt(item.split(',')[2]))); + return builds; + } + return []; + } +} + +class BuildNode implements ClusterExplorerV1.Node, ClusterExplorerV1.ClusterExplorerResourceNode { + nodeType: "resource"; + readonly resourceKind: ClusterExplorerV1.ResourceKind = { + manifestKind: 'Build', + abbreviation: 'build' + }; + readonly kind: ClusterExplorerV1.ResourceKind = this.resourceKind; + public id: string; + public resourceId: string; + // tslint:disable-next-line:variable-name + constructor(readonly namespace: string, readonly name: string, readonly number: number, readonly metadata?: any) { + this.id = this.resourceId = `build/${this.name}`; + } + + async getChildren(): Promise { + return []; + } + + getTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem(this.name); + item.contextValue = 'openShift.resource.build'; + item.command = { + arguments: [this], + command: 'extension.vsKubernetesLoad', + title: "Load" + }; + return item; + } +} export class Command { - static startBuild(comp: string) { - return `oc start-build ${comp}`; + static startBuild(buildConfig: string) { + return `oc start-build ${buildConfig}`; } - static getBuild(build: string) { + static getBuilds(build: string) { return `oc get build -l buildconfig=${build} -o json`; } @@ -21,118 +72,121 @@ export class Command { return `oc logs ${build}${text}`; } - static rebuild(build: String, build1: String) { - return `oc start-build ${build} --from-build ${build1}`; + static rebuildFrom(resourceId: String) { + return `oc start-build --from-build ${resourceId}`; } static followLog(build: string, text: string) { - return `oc logs -f ${build}${text}`; + return `oc logs ${build}${text} -f`; } static delete(build: String) { return `oc delete build ${build}`; } - static buildConfig() { + static getBuildConfigs() { return `oc get buildConfig -o json`; } - - static getParentBuild(childBuild: string) { - return `oc get build ${childBuild} -o jsonpath="{.metadata.labels['buildconfig']}"`; - } } export class Build { - protected static readonly odo: Odo = OdoImpl.Instance; - - static async getBuild(text: string): Promise { - const buildConfigName = []; - const buildConfigData = await Build.odo.execute(Command.buildConfig()); - const buildConfigJson: JSON = JSON.parse(buildConfigData.stdout); - buildConfigJson['items'].forEach((key: any) => { - buildConfigName.push(key.metadata.name); - }); - if (buildConfigName.length === 0) throw Error('You have no build available to start'); - return await window.showQuickPick(buildConfigName, {placeHolder: text}); - } - - static async startBuild(context: { id: any; metadata?: any; namespace?: any; nodeCategory?: string; nodeType?: string; resourceId?: string; }): Promise { - let buildName: string; - if (context) buildName = context.id; - else buildName = await Build.getBuild("Select the build to start"); - if (!buildName) return null; - return Progress.execFunctionWithProgress(`Starting build`, async () => { - return Build.odo.execute(Command.startBuild(buildName)); - }).then(() => `Build '${buildName}' successfully started`) - .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); - } - - static async getAllBuild(text: string): Promise { - const buildName = []; - const build = await Build.getBuild("select the build"); - if (!build) return null; - const getBuild = await Build.odo.execute(Command.getBuild(build)); - const buildJson: JSON = JSON.parse(getBuild.stdout); - buildJson['items'].forEach(element => { - buildName.push(element.metadata.name); + protected static readonly odo: Odo = OdoImpl.Instance; + + static async getQuickPicks(cmd: string, errorMessage: string): Promise { + const names: string[] = []; + const result = await Build.odo.execute(cmd); + const json: JSON = JSON.parse(result.stdout); + if (json['items'].length === 0) { + throw Error(errorMessage); + } + json['items'].forEach((item: any) => { + item.label = item.metadata.name; }); - if (buildName.length === 0) throw Error('You have no build available'); - return await window.showQuickPick(buildName, {placeHolder: text}); + return json['items']; } - static async showLog(context: { id?: string; impl: any; }): Promise { - let buildName: string; + static async getBuildConfigNames(): Promise { + return Build.getQuickPicks( + Command.getBuildConfigs(), + 'You have no BuildConfigs available to start a build'); + } + + static async getBuildNames(buildConfig: string): Promise { + return Build.getQuickPicks( + Command.getBuilds(buildConfig), + 'You have no builds available'); + } + + static async selectBuild(context: any, text: string): Promise { + let build: string; if (context) { - buildName = context.impl.name; + build = context.impl.name; } else { - const build = await Build.getAllBuild("select the build too see the logs"); - if (!build) return null; - else buildName = build; + const buildConfig = await Build.selectBuldConfig("Select a BuildConfig to see the builds"); + if (buildConfig) { + const selBuild = await window.showQuickPick(this.getBuildNames(buildConfig), {placeHolder: text}); + build = selBuild ? selBuild.label : null; + } } - Build.odo.executeInTerminal(Command.showLog(buildName, '-build')); + return build; } - static async rebuild(context: { id?: string; impl: any; }): Promise { - let buildName: string; - let parentBuild: string; - if (context) { - buildName = context.impl.name; - const getParentBuild = await Build.odo.execute(Command.getParentBuild(buildName)); - parentBuild = getParentBuild.stdout; - } else { - buildName = await Build.getAllBuild("select too rebuild"); - if (!buildName) return null; - const getParentBuild = await Build.odo.execute(Command.getParentBuild(buildName)); - parentBuild = getParentBuild.stdout; + static async selectBuldConfig(placeHolderText: string): Promise { + const buildConfig: any = await window.showQuickPick(this.getBuildConfigNames(), {placeHolder: placeHolderText}); + return buildConfig ? buildConfig.label : null; + } + + static async startBuild(context: { id: any; }): Promise { + let buildName: string = context ? context.id : undefined; + let result: Promise; + if (!buildName) buildName = await Build.selectBuldConfig("Select a BuildConfig to start a build"); + if (buildName) { + result = Progress.execFunctionWithProgress(`Starting build`, () => Build.odo.execute(Command.startBuild(buildName))) + .then(() => `Build '${buildName}' successfully started`) + .catch((err) => Promise.reject(`Failed to start build with error '${err}'`)); } - Build.odo.executeInTerminal(Command.rebuild(parentBuild, buildName)); + return result; } - static async followLog(context: { id?: string; impl: any; }): Promise { - let buildName: string; - if (context) { - buildName = context.impl.name; - } else { - const build = await Build.getAllBuild("select the build too see the logs"); - if (!build) return null; - else buildName = build; + static async showLog(context: { impl: any; }): Promise { + const build = await Build.selectBuild(context, "Select a build too see the logs"); + if (build) { + Build.odo.executeInTerminal(Command.showLog(build, '-build')); } - Build.odo.executeInTerminal(Command.followLog(buildName, '-build')); } - static async delete(context: { id?: string; impl: any; }): Promise { - let buildName; + static async rebuild(context: { id?: string; impl: any; }): Promise { + let resourceId: string; if (context) { - buildName = context.impl.name; + resourceId = context.impl.name; } else { - const build = await Build.getAllBuild("select the build too delete"); - if (!build) return null; - else buildName = build; + const name = await Build.selectBuild(context, "select too rebuild"); + if (name) { + resourceId = name; + } + } + if (resourceId) { + Build.odo.executeInTerminal(Command.rebuildFrom(resourceId)); + } + return null; + } + + static async followLog(context: { impl: any; }): Promise { + const build = await Build.selectBuild(context, "Select a build too follow the logs"); + if (build) { + Build.odo.executeInTerminal(Command.followLog(build, '-build')); } - return Progress.execFunctionWithProgress(`Starting build`, async () => { - return Build.odo.execute(Command.delete(buildName)); - }).then(() => `Build '${buildName}' successfully deleted`) - .catch((err) => Promise.reject(`Failed to delete build with error '${err}'`)); + return null; + } + static async delete(context: { impl: any; }): Promise { + let result: null | string | Promise | PromiseLike = null; + const build = await Build.selectBuild(context, "Select a build too delete"); + if (build) { + result = Progress.execFunctionWithProgress(`Starting build`, () => Build.odo.execute(Command.delete(build))) + .then(() => `Build '${build}' successfully deleted`) + .catch((err) => Promise.reject(`Failed to delete build with error '${err}'`)); + } + return result; } } \ No newline at end of file diff --git a/test/k8s/build.test.ts b/test/k8s/build.test.ts index 1035977bb..cb8096dc0 100644 --- a/test/k8s/build.test.ts +++ b/test/k8s/build.test.ts @@ -153,7 +153,7 @@ suite('K8s/build', () => { setup(() => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); - sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); quickPickStub.onFirstCall().resolves("nodejs-copm-nodejs-comp-8"); }); @@ -178,7 +178,7 @@ suite('K8s/build', () => { suite('rebuild', () => { setup(() => { - sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); @@ -187,14 +187,14 @@ suite('K8s/build', () => { test('works from context menu', async () => { execStub.resolves({ error: null, stdout: "nodejs-copm-nodejs-comp", stderr: '' }); await Build.rebuild(context); - expect(termStub).calledOnceWith(Command.rebuild("nodejs-copm-nodejs-comp", context.impl.name)); + expect(termStub).calledOnceWith(Command.rebuildFrom(context.impl.name)); }); test('works with no context', async () => { execStub.onFirstCall().resolves({ error: null, stdout: buildData, stderr: '' }); execStub.onSecondCall().resolves({ error: null, stdout: "nodejs-copm-nodejs-comp", stderr: '' }); await Build.rebuild(null); - expect(termStub).calledOnceWith(Command.rebuild("nodejs-copm-nodejs-comp", "nodejs-copm-nodejs-comp-8")); + expect(termStub).calledOnceWith(Command.rebuildFrom("nodejs-copm-nodejs-comp-8")); }); test('returns null when no build selected to rebuild', async () => { @@ -210,7 +210,7 @@ suite('K8s/build', () => { setup(() => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); - sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); }); @@ -235,7 +235,7 @@ suite('K8s/build', () => { suite('Delete', ()=> { setup(() => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); - sandbox.stub(Build, 'getBuild').resolves("nodejs-copm-nodejs-comp"); + sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); }); From 4ac5de168a2a322788249c16b248fdead7b03956 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Sun, 21 Jul 2019 22:27:01 -0700 Subject: [PATCH 14/18] Fix test errors --- src/extension.ts | 2 +- src/k8s/build.ts | 8 ++++---- test/k8s/build.test.ts | 37 +++++++++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 2bd29a752..fca36f2d7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -25,7 +25,7 @@ import open = require("open"); import { Build } from './k8s/build'; let clusterExplorer: k8s.ClusterExplorerV1 | undefined = undefined; -import { Odo, OdoImpl } from './odo'; +import { OdoImpl } from './odo'; export let contextGlobalState: vscode.ExtensionContext; diff --git a/src/k8s/build.ts b/src/k8s/build.ts index 3ed78bd02..e400ab2de 100644 --- a/src/k8s/build.ts +++ b/src/k8s/build.ts @@ -93,7 +93,6 @@ export class Build { protected static readonly odo: Odo = OdoImpl.Instance; static async getQuickPicks(cmd: string, errorMessage: string): Promise { - const names: string[] = []; const result = await Build.odo.execute(cmd); const json: JSON = JSON.parse(result.stdout); if (json['items'].length === 0) { @@ -118,7 +117,7 @@ export class Build { } static async selectBuild(context: any, text: string): Promise { - let build: string; + let build: string = null; if (context) { build = context.impl.name; } else { @@ -138,7 +137,7 @@ export class Build { static async startBuild(context: { id: any; }): Promise { let buildName: string = context ? context.id : undefined; - let result: Promise; + let result: Promise = null; if (!buildName) buildName = await Build.selectBuldConfig("Select a BuildConfig to start a build"); if (buildName) { result = Progress.execFunctionWithProgress(`Starting build`, () => Build.odo.execute(Command.startBuild(buildName))) @@ -148,11 +147,12 @@ export class Build { return result; } - static async showLog(context: { impl: any; }): Promise { + static async showLog(context: { impl: any; }): Promise { const build = await Build.selectBuild(context, "Select a build too see the logs"); if (build) { Build.odo.executeInTerminal(Command.showLog(build, '-build')); } + return build; } static async rebuild(context: { id?: string; impl: any; }): Promise { diff --git a/test/k8s/build.test.ts b/test/k8s/build.test.ts index cb8096dc0..28c6aedac 100644 --- a/test/k8s/build.test.ts +++ b/test/k8s/build.test.ts @@ -82,6 +82,16 @@ suite('K8s/build', () => { resourceId: "bc/nodejs-comp-nodejs-app" }; + const noBcData = `{ + "apiVersion": "v1", + "items": [], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } + }`; + const mockData = `{ "apiVersion": "v1", "items": [ @@ -115,7 +125,7 @@ suite('K8s/build', () => { setup(() => { execStub.resolves({ error: undefined, stdout: mockData, stderr: '' }); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.resolves("nodejs-comp-nodejs-app"); + quickPickStub.resolves({label: "nodejs-comp-nodejs-app"}); }); test('works from context menu', async () => { @@ -132,7 +142,7 @@ suite('K8s/build', () => { expect(execStub).calledWith(Command.startBuild(context.id)); }); - test('returns null when no build selected', async () => { + test('returns null when no BuildConfig selected', async () => { quickPickStub.resolves(); const result = await Build.startBuild(null); expect(result).null; @@ -147,15 +157,26 @@ suite('K8s/build', () => { expect(err).equals(`Failed to start build with error '${errorMessage}'`); } }); + + test('throws error if there is no BuildConfigs to select', async () => { + execStub.resolves({ error: undefined, stdout: noBcData, stderr: '' }); + try { + await Build.startBuild(null); + } catch (err) { + expect(err).equals(`Failed to start build with error '${errorMessage}'`); + } + }); }); suite('Show Log', () => { setup(() => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); - sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); + const buidConfig = {label: "nodejs-copm-nodejs-comp"}; + sandbox.stub(Build, 'getBuildConfigNames').resolves([buidConfig]); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.onFirstCall().resolves("nodejs-copm-nodejs-comp-8"); + quickPickStub.onFirstCall().resolves(buidConfig); + quickPickStub.onSecondCall().resolves({label: "nodejs-copm-nodejs-comp-8"}); }); test('works from context menu', async () => { @@ -169,7 +190,7 @@ suite('K8s/build', () => { }); test('returns null when no build selected', async () => { - quickPickStub.onFirstCall().resolves(); + quickPickStub.onSecondCall().resolves(); const result = await Build.showLog(null); expect(result).null; }); @@ -180,7 +201,7 @@ suite('K8s/build', () => { setup(() => { sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + quickPickStub.resolves({label: "nodejs-copm-nodejs-comp-8"}); }); @@ -212,7 +233,7 @@ suite('K8s/build', () => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + quickPickStub.resolves({label: "nodejs-copm-nodejs-comp-8"}); }); test('works from context menu', async () => { @@ -237,7 +258,7 @@ suite('K8s/build', () => { execStub.resolves({ error: null, stdout: buildData, stderr: '' }); sandbox.stub(Build, 'getBuildNames').resolves("nodejs-copm-nodejs-comp"); quickPickStub = sandbox.stub(vscode.window, 'showQuickPick'); - quickPickStub.resolves("nodejs-copm-nodejs-comp-8"); + quickPickStub.resolves({label: "nodejs-copm-nodejs-comp-8"}); }); test('works from context menu', async () => { From ea877c8c80f4b627fb90cb80350d0a614b5f41b5 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Sun, 21 Jul 2019 22:46:17 -0700 Subject: [PATCH 15/18] Fix package.json merge conflicts --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 2ef9d8b22..f4e805a17 100644 --- a/package.json +++ b/package.json @@ -384,7 +384,6 @@ "command": "clusters.openshift.build.showLog", "title": "Show Log", "category": "OpenShift" -<<<<<<< HEAD }, { "command": "clusters.openshift.build.followLog", @@ -405,8 +404,6 @@ "command": "clusters.openshift.build.rebuild", "title": "Rebuild", "category": "OpenShift" -======= ->>>>>>> Stub for 'Show Log' and 'Follow Log' commands } ], "keybindings": [ From 5577f033d21bf11d4fef0f6b88495ad0dd9416af Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Sun, 21 Jul 2019 23:52:35 -0700 Subject: [PATCH 16/18] Fix start build test for the case without build configs --- test/k8s/build.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/k8s/build.test.ts b/test/k8s/build.test.ts index 28c6aedac..b15cd1913 100644 --- a/test/k8s/build.test.ts +++ b/test/k8s/build.test.ts @@ -159,12 +159,15 @@ suite('K8s/build', () => { }); test('throws error if there is no BuildConfigs to select', async () => { + quickPickStub.restore(); execStub.resolves({ error: undefined, stdout: noBcData, stderr: '' }); + let checkError: Error; try { - await Build.startBuild(null); - } catch (err) { - expect(err).equals(`Failed to start build with error '${errorMessage}'`); + const result = await Build.startBuild(null); + } catch(err) { + checkError = err as Error; } + expect(checkError.message).equals('You have no BuildConfigs available to start a build'); }); }); From cafd783ef2686d41d90a639d3ef3f69d899567e6 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Mon, 22 Jul 2019 00:08:49 -0700 Subject: [PATCH 17/18] Remove duplicat 'Show Log' command --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index f4e805a17..5ee6042a3 100644 --- a/package.json +++ b/package.json @@ -567,11 +567,6 @@ "group": "1@0", "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" }, - { - "command": "clusters.openshift.build.showLog", - "group": "2@0", - "when": "view == extension.vsKubernetesExplorer && viewItem =~ /openShift\\.resource\\.build.*/i" - }, { "command": "openshift.catalog.listComponents", "when": "view == openshiftProjectExplorer && viewItem == cluster && isLoggedIn", From 0b9055c4a66ce1628db55842ffb3328a31a169d7 Mon Sep 17 00:00:00 2001 From: Denis Golovin Date: Mon, 22 Jul 2019 00:10:38 -0700 Subject: [PATCH 18/18] Remove deployment.ts file, because code is moved to build.ts --- src/k8s/deployment.ts | 56 ------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 src/k8s/deployment.ts diff --git a/src/k8s/deployment.ts b/src/k8s/deployment.ts deleted file mode 100644 index 3fc4f3c72..000000000 --- a/src/k8s/deployment.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * 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 vscode from 'vscode'; -import { ClusterExplorerV1 } from 'vscode-kubernetes-tools-api'; -import * as k8s from 'vscode-kubernetes-tools-api'; - -export class DeploymentConfigNodeContributor implements ClusterExplorerV1.NodeContributor { - contributesChildren(parent: ClusterExplorerV1.ClusterExplorerNode | undefined): boolean { - return !!parent && parent.nodeType === 'resource' && parent.resourceKind.manifestKind === 'BuildConfig'; - } - - async getChildren(parent: ClusterExplorerV1.ClusterExplorerNode | undefined): Promise { - const kubectl = await k8s.extension.kubectl.v1; - if (kubectl.available) { - const result = await kubectl.api.invokeCommand(`get build -o jsonpath="{range .items[?(.metadata.labels.buildconfig=='${(parent as any).name}')]}{.metadata.namespace}{','}{.metadata.name}{','}{.metadata.annotations.openshift\\.io/build\\.number}{\\"\\n\\"}{end}"`); - const builds = result.stdout.split('\n') - .filter((value) => value !== '') - .map((item: string) => new Build(item.split(',')[0], item.split(',')[1], Number.parseInt(item.split(',')[2]))); - return builds; - } - return []; - } -} - -class Build implements ClusterExplorerV1.Node, ClusterExplorerV1.ClusterExplorerResourceNode { - nodeType: "resource"; - readonly resourceKind: ClusterExplorerV1.ResourceKind = { - manifestKind: 'Build', - abbreviation: 'build' - }; - readonly kind: ClusterExplorerV1.ResourceKind = this.resourceKind; - public id: string; - public resourceId: string; - // tslint:disable-next-line:variable-name - constructor(readonly namespace: string, readonly name: string, readonly number: number, readonly metadata?: any) { - this.id = this.resourceId = `build/${this.name}`; - } - - async getChildren(): Promise { - return []; - } - - getTreeItem(): vscode.TreeItem { - const item = new vscode.TreeItem(`#${this.number} ${this.name}`); - item.contextValue = 'openShift.resource.build'; - item.command = { - arguments: [this], - command: 'extension.vsKubernetesLoad', - title: "Load" - }; - return item; - } -} \ No newline at end of file