Skip to content

Commit b847944

Browse files
deploy and un deploy functionality (#3073)
* deploy and undeploy functionality Signed-off-by: msivasubramaniaan <msivasub@redhat.com> * shown response in the UI Signed-off-by: msivasubramaniaan <msivasub@redhat.com> --------- Signed-off-by: msivasubramaniaan <msivasub@redhat.com>
1 parent 48c7457 commit b847944

File tree

8 files changed

+464
-96
lines changed

8 files changed

+464
-96
lines changed
1.29 MB
Loading

package.json

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,11 @@
794794
"title": "Deploy",
795795
"category": "OpenShift"
796796
},
797+
{
798+
"command": "openshift.Serverless.undeploy",
799+
"title": "UnDeploy",
800+
"category": "OpenShift"
801+
},
797802
{
798803
"command": "openshift.Serverless.buildAndRun",
799804
"title": "Build and Run",
@@ -1115,6 +1120,10 @@
11151120
"command": "openshift.Serverless.deploy",
11161121
"when": "false"
11171122
},
1123+
{
1124+
"command": "openshift.Serverless.undeploy",
1125+
"when": "false"
1126+
},
11181127
{
11191128
"command": "openshift.Serverless.buildAndRun",
11201129
"when": "false"
@@ -1456,15 +1465,23 @@
14561465
},
14571466
{
14581467
"command": "openshift.Serverless.build",
1459-
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild)$/"
1468+
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild|localDeployFunctions)$/"
14601469
},
14611470
{
14621471
"command": "openshift.Serverless.buildAndRun",
1463-
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild)$/"
1472+
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild|localDeployFunctions)$/"
14641473
},
14651474
{
14661475
"command": "openshift.Serverless.run",
1467-
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctionsWithBuild)$/"
1476+
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctionsWithBuild|localDeployFunctions)$/"
1477+
},
1478+
{
1479+
"command": "openshift.Serverless.deploy",
1480+
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild|localDeployFunctions|deployFunctions)$/"
1481+
},
1482+
{
1483+
"command": "openshift.Serverless.undeploy",
1484+
"when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localDeployFunctions|deployFunctions)$/"
14681485
},
14691486
{
14701487
"command": "openshift.Serverless.stopRun",
@@ -1580,6 +1597,15 @@
15801597
"image": "https://raw.githubusercontent.com/redhat-developer/vscode-openshift-tools/main/images/gif/walkthrough/serverless-function/run.gif",
15811598
"altText": "Run function"
15821599
}
1600+
},
1601+
{
1602+
"id": "deployFunction",
1603+
"title": "Deploy the Function",
1604+
"description": "Deploys a function to the currently configured Knative-enabled cluster from your IDE.\nOnly a function which has the source code opened in the IDE can be pushed. Right-click on the function you want to deploy (look for its node in the Functions tree), open the context menu (right-click on the node) and click on \"Deploy\". The Output Channel will show up with the deploy command where you can see the logs.",
1605+
"media": {
1606+
"image": "https://raw.githubusercontent.com/redhat-developer/vscode-openshift-tools/main/images/gif/walkthrough/serverless-function/deploy.gif",
1607+
"altText": "Deploy function"
1608+
}
15831609
}
15841610
]
15851611
}

src/serveressFunction/build-run-deploy.ts

Lines changed: 181 additions & 37 deletions
Large diffs are not rendered by default.

src/serveressFunction/commands.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ export class ServerlessCommand {
5151
return commandText;
5252
}
5353

54+
static deployFunction(location: string,
55+
image: string,
56+
namespace: string,
57+
clusterVersion: ClusterVersion | null): CommandText {
58+
const commandText = new CommandText('func', 'deploy', [
59+
new CommandOption('-p', location),
60+
new CommandOption('-i', image),
61+
new CommandOption('-v')
62+
]);
63+
if (namespace){
64+
commandText.addOption(new CommandOption('-n', namespace))
65+
}
66+
if (clusterVersion) {
67+
commandText.addOption(new CommandOption('-r', ''))
68+
}
69+
return commandText;
70+
}
71+
72+
static undeployFunction(name: string): CommandText {
73+
const commandText = new CommandText('func', `delete ${name}`);
74+
return commandText
75+
}
76+
5477
static getClusterVersion(): CommandText {
5578
return new CommandText('oc get clusterversion', undefined, [
5679
new CommandOption('-o', 'josn')

src/serveressFunction/functionImpl.ts

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import * as path from 'path';
77
import * as fs from 'fs-extra';
88
import { Uri, window, workspace } from 'vscode';
99
import { FunctionContent, FunctionObject, FunctionStatus } from './types';
10-
import { ServerlessFunctionView } from './view';
1110
import { ServerlessCommand, Utils } from './commands';
1211
import { OdoImpl } from '../odo';
1312
import { BuildAndDeploy } from './build-run-deploy';
1413
import { stringify } from 'yaml';
15-
import { CliExitData } from '../cli';
14+
import { CliChannel, CliExitData } from '../cli';
1615
import * as cp from 'child_process';
1716
import { VsCommandError } from '../vscommand';
17+
import { CommandText } from '../base/command';
18+
import { DeploymentConfig } from '../k8s/deploymentConfig';
19+
import { ServerlessFunctionView } from './view';
1820

1921
export interface ServerlessFunction {
2022
getLocalFunctions(): Promise<FunctionObject[]>;
@@ -32,16 +34,18 @@ export class ServerlessFunctionImpl implements ServerlessFunction {
3234
return ServerlessFunctionImpl.instance;
3335
}
3436

35-
/*private async getListItems<T>(command: CommandText, fail = false) {
37+
private async getListItems(command: CommandText, fail = false) {
3638
const listCliExitData = await CliChannel.getInstance().executeTool(command, undefined, fail);
37-
const result = loadItems<T>(listCliExitData.stdout);
38-
return result;
39+
try {
40+
return JSON.parse(listCliExitData.stdout) as FunctionObject[];
41+
} catch(err) {
42+
return [];
43+
}
3944
}
4045

4146
private async getDeployedFunctions(): Promise<FunctionObject[]> {
42-
//set context value to deploy
43-
return this.getListItems<FunctionObject>(DeploymentConfig.command.getDeploymentFunctions());
44-
}*/
47+
return this.getListItems(DeploymentConfig.command.getDeploymentFunctions());
48+
}
4549

4650
async createFunction(language: string, template: string, location: string, image: string): Promise<CliExitData> {
4751
let funnctionResponse: CliExitData;
@@ -78,9 +82,8 @@ export class ServerlessFunctionImpl implements ServerlessFunction {
7882
}
7983

8084
async getLocalFunctions(): Promise<FunctionObject[]> {
81-
//const deployedFunctions = await this.getDeployedFunctions();
82-
const folders: Uri[] = [];
8385
const functionList: FunctionObject[] = [];
86+
const folders: Uri[] = [];
8487
if (workspace.workspaceFolders) {
8588
// eslint-disable-next-line no-restricted-syntax
8689
for (const wf of workspace.workspaceFolders) {
@@ -89,42 +92,50 @@ export class ServerlessFunctionImpl implements ServerlessFunction {
8992
}
9093
}
9194
}
92-
const currentNamespace: string = ServerlessFunctionView.getInstance().getCurrentNameSpace();
93-
// eslint-disable-next-line no-console
94-
console.log(currentNamespace);
95-
for (const folderUri of folders) {
96-
const funcStatus = FunctionStatus.LOCALONLY;
97-
const funcData: FunctionContent = await Utils.getFuncYamlContent(folderUri.fsPath);
98-
/*if (
99-
functionTreeView.has(funcData?.name) &&
100-
(!funcData?.deploy?.namespace || getCurrentNamespace === funcData?.deploy?.namespace)
101-
) {
102-
funcStatus = FunctionStatus.CLUSTERLOCALBOTH;
103-
}
104-
fs.watch(folderUri.fsPath, (eventName, filename) => {
105-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
106-
//functionExplorer.refresh();
107-
});
108-
if (funcData?.name) {
109-
const url =
110-
func.contextValue !== FunctionContextType.FAILNAMESPACENODE && funcData?.image.trim()
111-
? functionTreeView.get(funcData?.name)?.url
112-
: undefined;
113-
functionTreeView.set(funcData?.name, this.createFunctionNodeImpl(func, funcData, folderUri, funcStatus, url));
114-
}*/
115-
const functionNode: FunctionObject = {
116-
name: funcData.name,
117-
runtime: funcData.runtime,
118-
folderURI: folderUri,
119-
context: funcStatus,
120-
hasImage: await this.checkImage(folderUri),
121-
isRunning: BuildAndDeploy.getInstance().checkRunning(folderUri.fsPath)
95+
const deployedFunctions = await this.getDeployedFunctions();
96+
if (folders.length > 0) {
97+
for (const folderUri of folders) {
98+
const funcData: FunctionContent = await Utils.getFuncYamlContent(folderUri.fsPath);
99+
const funcStatus = this.getFunctionStatus(funcData, deployedFunctions);
100+
const functionNode: FunctionObject = {
101+
name: funcData.name,
102+
runtime: funcData.runtime,
103+
folderURI: folderUri,
104+
context: funcStatus,
105+
hasImage: await this.checkImage(folderUri),
106+
isRunning: BuildAndDeploy.getInstance().checkRunning(folderUri.fsPath)
107+
}
108+
functionList.push(functionNode);
109+
fs.watchFile(path.join(folderUri.fsPath, 'func.yaml'), (_eventName, _filename) => {
110+
ServerlessFunctionView.getInstance().refresh();
111+
});
112+
}
113+
}
114+
if (deployedFunctions.length > 0) {
115+
for (const deployedFunction of deployedFunctions) {
116+
if (functionList.filter((functionParam) => functionParam.name === deployedFunction.name).length === 0) {
117+
const functionNode: FunctionObject = {
118+
name: deployedFunction.name,
119+
runtime: deployedFunction.runtime,
120+
context: FunctionStatus.CLUSTERONLY
121+
}
122+
functionList.push(functionNode);
123+
}
122124
}
123-
functionList.push(functionNode);
124125
}
125126
return functionList;
126127
}
127128

129+
getFunctionStatus(funcData: FunctionContent, deployedFunctions: FunctionObject[]): FunctionStatus {
130+
if (deployedFunctions.length > 0) {
131+
const func = deployedFunctions.find((deployedFunction) => deployedFunction.name === funcData.name && deployedFunction.namespace === funcData.deploy?.namespace)
132+
if (func) {
133+
return FunctionStatus.CLUSTERLOCALBOTH;
134+
}
135+
}
136+
return FunctionStatus.LOCALONLY;
137+
}
138+
128139
async checkImage(folderUri: Uri): Promise<boolean> {
129140
const yamlContent = await Utils.getFuncYamlContent(folderUri.fsPath);
130141
return BuildAndDeploy.imageRegex.test(yamlContent?.image);
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*-----------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE file in the project root for license information.
4+
*-----------------------------------------------------------------------------------------------*/
5+
6+
/* eslint-disable max-classes-per-file */
7+
import { QuickPickItem, window, Disposable, QuickInput, QuickInputButton } from 'vscode';
8+
9+
interface QuickPickParameters<T extends QuickPickItem> {
10+
title: string;
11+
items: T[];
12+
activeItem?: T;
13+
placeholder: string;
14+
buttons?: QuickInputButton[];
15+
}
16+
17+
interface InputBoxParameters {
18+
title: string;
19+
value?: string;
20+
prompt: string;
21+
password: boolean;
22+
validate: (value: string) => string;
23+
buttons?: QuickInputButton[];
24+
placeholder?: string;
25+
reattemptForLogin?: boolean;
26+
}
27+
28+
class MultiStepInput {
29+
public current?: QuickInput;
30+
31+
async showQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({ title, items, activeItem, placeholder }: P) {
32+
const disposables: Disposable[] = [];
33+
try {
34+
return await new Promise<T | (P extends { buttons: (infer I)[] } ? I : never)>((resolve) => {
35+
const input = window.createQuickPick<T>();
36+
input.title = title;
37+
input.placeholder = placeholder;
38+
input.items = items;
39+
input.ignoreFocusOut = true;
40+
if (activeItem) {
41+
input.activeItems = [activeItem];
42+
}
43+
disposables.push(
44+
// eslint-disable-next-line @typescript-eslint/no-shadow
45+
input.onDidChangeSelection((items) => {
46+
resolve(items[0]);
47+
this.current.dispose();
48+
}),
49+
input.onDidHide(() => {
50+
resolve(null);
51+
}),
52+
);
53+
if (this.current) {
54+
this.current.dispose();
55+
}
56+
this.current = input;
57+
this.current.show();
58+
});
59+
} finally {
60+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
61+
disposables.forEach((d) => d.dispose());
62+
}
63+
}
64+
65+
async showInputBox<P extends InputBoxParameters>({
66+
title,
67+
value,
68+
prompt,
69+
password,
70+
validate,
71+
placeholder,
72+
reattemptForLogin,
73+
}: P) {
74+
const disposables: Disposable[] = [];
75+
try {
76+
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve) => {
77+
const input = window.createInputBox();
78+
input.title = title;
79+
input.value = value || '';
80+
input.prompt = prompt;
81+
input.password = password;
82+
input.ignoreFocusOut = true;
83+
input.placeholder = placeholder || '';
84+
let validating = validate('');
85+
if (reattemptForLogin) {
86+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
87+
input.validationMessage = 'Incorrect credentials, please try again';
88+
}
89+
disposables.push(
90+
input.onDidAccept(() => {
91+
// eslint-disable-next-line @typescript-eslint/no-shadow
92+
const { value } = input;
93+
input.enabled = false;
94+
input.busy = true;
95+
if (!validate(value)) {
96+
resolve(value);
97+
this.current.dispose();
98+
}
99+
input.enabled = true;
100+
input.busy = false;
101+
}),
102+
input.onDidChangeValue((text) => {
103+
const current = validate(text);
104+
validating = current;
105+
const validationMessage = current;
106+
if (current === validating) {
107+
input.validationMessage = validationMessage;
108+
}
109+
}),
110+
input.onDidHide(() => {
111+
resolve(null);
112+
}),
113+
);
114+
if (this.current) {
115+
this.current.dispose();
116+
}
117+
this.current = input;
118+
this.current.show();
119+
});
120+
} finally {
121+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
122+
disposables.forEach((d) => d.dispose());
123+
}
124+
}
125+
}
126+
127+
export const multiStep = new MultiStepInput();

0 commit comments

Comments
 (0)